• 第一篇 上路吧,线程
    进程
    进程含有内存和资源。被进程拥有的内存,理论可高达3GB,资源则包括核心对象如file handles和线程,user资源如对话框和字符串,GDI资源如Device Context和brush。
    进程就像一本活页笔记夹,你可以在其中的活页上写东西,也可以擦掉内容甚至撕掉。活页夹只是持有那些东西而已。同理,进程本身并不能够执行,它只是提供一个安置内存和线程的地方。
    内存
    每个进程都关系到内存,内存就像是前面所说的活页笔记夹中的活页纸,它代表的意义完全的看在纸面上写些什么东西而定,内存大致分为:CODE Data Stack。
    Code 是程序的可执行部分,一定是只读性质,这是CPU唯一允许的内存。
    Data是你的程序中的所有变量(不包括函数中的局部变量),可以区分为全局变量和静态变量两种。当然线程也可以使用malloc和new动态分配内存。
    Stack是你调用函数时所用的堆栈空间,其中有局部变量。每个线程产生时配有一个堆栈。如果不需要,操作系统将他动态扩充。

    线程
    定义一个线程,需要的数据并不多。线程在“任何时刻下的状态”被定义在进程的某块内存中,以及CPU中的可涂改拍纸片上。其他的重要数据,如变量以及应用程序的调用堆栈,存储在进程中那些可被其他线程共享的内存中。

    Context Switch:当硬件计时器认为某个线程已经执行久了,就会发出一个中断,于是CPU取得目前这个线程的但前状态,也就是把所有寄存器内容拷贝到堆栈中,再把它从堆栈拷贝到一个CONTEXT结构中,这样存储了线程的状态。
    要切换线程时,操作系统应先切换线程所属的进程内存,然后恢复放在CONTEXT结构中的寄存器值。

    AttachThreadInput()
    PostThreadMessage()

    win32核心对象
    进程
    线程
    文件
    事件
    信号量
    互斥器
    管道

    GDI对象有单一的拥有者,而核心对象可以有一个以上的拥有者,甚至跨进程,核心对象保持了一个引用计数,以记录有多少handle对应此对象。对象中也记录了哪个进程或者线程是拥有者。
    线程对象与线程的不同:线程的handle是指向线程核心对象,而不是指向线程本身。closehandle唯一所做的事情是把引用计数减一,如果该值为0,对象就会自动被操作系统销毁。线程核心对象引用的那个线程也会令核心对象开启,因此
    线程对象引用计数是2,当调用closehandle时引用计数下降1,当线程结束时,引用计数再降1,只有两件事情都发生了的时候,这个对象才会被真正消除。

    获得线程结束代码 BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);
    结束线程 VOID ExitThread(DWORD dwExitCode)
    主线程:程序启动后就执行的那个线程称为主线程(primary thread),主线程有两个特点,第一,它必须负责GUI程序中主消息循环。
    第二,这一线程的结束会使得程序中的所有线程都被强迫结束,程序也因此而结束,其他线程没有机会做清理工作。
    在main和winmain结束的时候他会首先强迫其他所有的线程结束,当其他所有线程都结束后它在结束,程序执行完毕。
    在主线程中调用EXitThread(),会导致主线程结束而工作者线程继续存在。从而会跳过runtime library中的cleanup函数,因而没有将已经开启的文件清理掉。
    #pragma comment(lib, "USER32")
    #include
    侯捷所谓的表面积,意指必须被线程共享的数据结构。
    打印程序的设计架构
    1.显示打印对话框
    2.在heap中产生数据块
    3.初始化数据块
    4.启动线程
    5.存储线程的handle
    如果用户企图离开程序,GUI线程会先等待所有的worker线程都结束,下面的代码完成这个等待操作。
    for(int index = 0; index < gNumPrinting; index++)
    {
    DWORD status;
    do
    {
    //wait for thread to terminate
    MTVERIFY(GetExitCodeThread(gPrintJobs[index]), &status));
    sleep(10);
    }while(status == STILL_ACTIVE)
    }//end for

    成功的秘诀
    1.各个线程的数据要分离开来,避免使用全局变量。
    2.不要在线程之间共享GDI对象。
    3.确定你知道的线程状态,不要径自结束程序而不等待它们的结束。
    4.让主线程处理用户界面。

    waitForSingleObject()
    当线程正在执行时,线程对象处于未激发状态。当线程结束时,线程对象就被激发了。
    MsgWaitForMultipleObjects(DWORD nCount, LPHANDLE pHandles, BOOL fWaitAll, DWORD dwMillseconds, DWORD dwWakeMask);

    信号量Semaphores
    HANDLE CreateSemaphore()
    信号量可以被锁住n次,但你不会受到它的所有权,因为可以有多个线程同时锁定一个semaphore。一旦semaphore的现值
    到0,就表示资源已经耗尽。此时任何线程如果调用waitforsingleobject都必然要等待。直到某个锁定被解除为止。
    解除锁定ReleaseSemaphore(),这个函数将semaphore的现值增加1,并传回前一个现值。

    事件 Event
    CreaeEvent
    SetEvent
    ResetEvent
    plusEvent

    不要让线程成为脱缰野马 keeping Your Threads on a leash
    放弃一个线程 TerminateThread,该函数使得线程没有机会在结束前清理自己,可能造成内存泄露。

    线程优先权ThreadPriority
    win32优先权是以数值表现的,并以进程优先权类别,线程优先权层级和操作系统当时采用的动态提升作为计算基准,所有因素放在一起,最后获得一个0-31的数值。
    SetPriorityClass()//进程
    SetThreadPriority()//线程
    GetThreadPriority()


    Overlapped I/O 在你身后变戏法
    win32有三个基本函数来执行I/O: CreateFile ReadFile WriteFile
    CreateFile 可用来打开各式资源 文件,串行口并行口,命名管道

  • 第9章 套接字选项和IO控制命令
    9.1套接字选项
    int getsocketopt(SOCKET s, int level, int optname, char FAR* optal, int FAR* optlen);
    int setsocketopt(SOCKET s, int level, int optname, const char FAR* optval, int optlen);
    9.1.1 SOL_SOCKET选项级别
    1.SQ_ACCEPTCONN 如果TRUE为真表明套接字处于监听模式。
    2.SQ_BROADCAST 如果为TRUE表明套接字已经配置成广播消息进行发送。
    对UDP来说,存在着一个特殊的广播地址,所有广播数据均应发至该地址。这个地址便是255.255.255.255. INADDR_BROADCAST.
    3.SO_CONNECT_TIME 返回套接字建立连接的时间。
    4.SO_DEBUG 如果是TRUE就允许调试输出。
    5.SO_DONTLINGER 不拖延
    6.SO-DONTROUTE 不路由
    7.SO_ERROR 返回错误状态
    8.SO_EXCLUSIVEADDRUSE 套接字绑定的那个端口不能重新被另一个进程使用。
    9.SO_KEEPALIVE 如果是TRUE,套接字就会进行配置,在会话过程中发送“保持活动”消息。
    10.SO_LINGER 设置或者获取当前的拖延值。
    11.SO_MAX_MSG_SIZE
    12.SO_OOBINLINE 带外数据在普通数据流中返回。
    13.SO_PROTOCOL_INFO 套接字绑定的那种协议的特征。
    14.SO_RCVBUF 为每个套接字分别获取或者设置缓冲区长度。
    15.SO_REUSEADDR
    16.SO_SNDBUF 套接字被配置成可进行广播消息的发送。
    17.SO_TYPE 套接字类型
    18.SO_SNDTIMEO
    19.SO_RCVTIMEO
    20.SO_UPDATE_ACCEPT_CONTEXT

    9.1.4 IPPROTO_IP选项
    1.IP_OPTIONS 设置或者获取IP头内的IP选项
    安全和控制限制
    记录路由
    时间标志
    松散源路由选择
    严格源路由选择

    struct ip_option_hr
    {
    unsigned char code;
    unsigned char length;
    unsigned char offset;
    unsigned long addrs[0];
    };

    ZeroMemory((char*)&opthdr, sizeof(opthdr));
    opthdr.code = 0x7;
    opthdr.length = 39;
    opthdr.offset = 4;
    ret = setsockopt(s, IPPROTO_IP, IP_OPTIONS, (char*)&opthdr),sizeof(opthdr));
    2.IP_HDRINCL
    如果是TRUE,IP头就会随即将发送的数据一起提交,并从读取的数据中返回。
    3.IP_TOS ip服务类型
    4.IP_TTL IP协议的存在时间参数
    5.IP_MULTICAST_IF 获取或者设置打算从它上面发出多播数据的本地接口。
    6.IP_MULTICAST_TTL 为套接字获取或者设置多播数据包的存在时间。
    7.IP-MULTICAST_LOOP 发送至多播地址的数据被原封不动地反射回套接字的进入缓冲区。
    8.IP_ADD_MEMBERSHIP 在指定的IP组内为套接字赋予成员资格
    9.IP_DROP_MEMBERSHIP
    10.IP_DONTFRAGMENT 不对数据报分段
    9.1.5 IPPROTO_TCP选项级别
    流式套接字 地址家族 AF_INET
    TCP_NODELAY 禁止Nagle算法。

    http://xingzhesun.blogbus.com/files/1164702477.gif

  • Windows完成端口编程
    目录
    一 基本概念
    二 OVERLAPPED数据结构
    三 完成端口的内部机制
    创建完成端口
    完成端口线程的工作原理
    线程间数据传递
    线程的安全退出

    一 基本概念
    设备---windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等。所以我们不能看到**File函数就只想到文件设备。

    与设备通信有两种方式,同步方式和异步方式。同步方式下,当调用ReadFile函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。

    重叠I/O----顾名思义,当你调用了某个函数(比如ReadFile)就立刻返回做自己的其他动作的时候,同时系统也在对I/0设备进行你要求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O设备的。

    重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。

    完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。

    二 OVERLAPPED数据结构
    typedef struct _OVERLAPPED
    {
    ULONG_PTR Internal;//被系统内部赋值,用来表示系统状态
    ULONG_PTR InternalHigh;// 被系统内部赋值,传输的字节数
    union {
    struct
    {
    DWORD Offset;//和OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始
    DWORD OffsetHigh;//操作,如果不是对文件I/O来操作,则必须设定为0
    };
    PVOID Pointer;
    };
    HANDLE hEvent;//如果不使用,就务必设为0,否则请赋一个有效的Event句柄
    } OVERLAPPED, *LPOVERLAPPED;


    下面是异步方式使用ReadFile的一个例子
    OVERLAPPED Overlapped;
    Overlapped.Offset=345;
    Overlapped.OffsetHigh=0;
    Overlapped.hEvent=0;
    //假定其他参数都已经被初始化
    ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
    这样就完成了异步方式读文件的操作,然后ReadFile函数返回,由操作系统做自己的事情吧

    下面介绍几个与OVERLAPPED结构相关的函数
    等待重叠I/0操作完成的函数
    BOOL GetOverlappedResult (
    ANDLE hFile,
    LPOVERLAPPED lpOverlapped,//接受返回的重叠I/0结构
    LPDWORD lpcbTransfer,//成功传输了多少字节数
    BOOL fWait //TRUE只有当操作完成才返回,FALSE直接返回,如果操作没有完成,通过调//用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE
    );

    宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成,该宏对OVERLAPPED结构的Internal成员进行了测试,查看是否等于STATUS_PENDING值。


    三 完成端口的内部机制
    创建完成端口

    完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:
    HANDLE CreateIoCompletionPort(
    IN HANDLE FileHandle,
    IN HANDLE ExistingCompletionPort,
    IN ULONG_PTR CompletionKey,
    IN DWORD NumberOfConcurrentThreads );

    通常创建工作分两步:

    第一步,创建一个新的完成端口内核对象,可以使用下面的函数:

    HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
    {
    return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
    };

    第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:

    bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
    {
    HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
    return h==hCompPort;
    };

    说明
    1) CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄
    2) CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
    3) NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。
    创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。

    完成端口线程的工作原理
    完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:
    BOOL GetQueuedCompletionStatus(
    IN HANDLE CompletionPort,
    OUT LPDWORD lpNumberOfBytesTransferred,
    OUT PULONG_PTR lpCompletionKey,
    OUT LPOVERLAPPED *lpOverlapped,
    IN DWORD dwMilliseconds
    );

    这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。
    完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
    当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。
    基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用
    GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。

    线程间数据传递

    线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。
    CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。
    OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。
    CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。
    线程的安全退出
    很多线程为了不止一次的执行异步数据处理,需要使用如下语句

    while (true)
    {

    ....
    GetQueuedCompletionStatus(...);
    }

    那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。

  • 8.2.4 重叠模型
    s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    1.事件通知 将win32事件对象与WSAOVERLAPPED结构关联在一起。
    typedef struct WSAOVERLAPPED
    {
    DWORD Internal;
    DWORD InternalHigh;
    DWORD offset;
    DWORD OffsetHigh;
    WSAEVENT hEvent;
    } WSAOVERLAPPED, FAR*LPWSAOVERLAPPED;
    hEvent 它允许应用程序将一个事件对象句柄同一个套接字关联起来。

    BOOL WSAGetOverlappedResult(SOCKET S, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWari, LPDWORD lpdwFlags);
    void main()
    {
    WSABUF DataBuf;
    DWORD EventTotal = 0;
    WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
    WSAOVERLAPPED AcceptOverlapped;
    SOCKET ListenSocket, AcceptSocket;

    //step 1
    //start winsock and set up a listening socket

    //step 2
    //accepte an inbound connection

    AcceptSocket = accept(ListenSocket, NULL, NULL);

    //step 3
    //set up an overlapped structure

    EventArray[EventTotal]=WSACreateEvent();
    ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));

    AcceptOverlapped.hEvent = EventArray[EventTotal];

    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = buffer;

    EventTotal++;

    //step 4;
    //post a WSARecv request to begin receiving data on the socket.
    WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);

    //Process overlapped receives on the socket.
    while (TRUE)
    {
    //step 5
    //wait for overlapped io call to complete
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);

    //Index should be 0 because we have one event handle in eventarray.

    //step 6
    //reset the signaled event
    WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);

    //step 7
    //Determine the status of the overlapped request.
    WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);

    //First check to see whether the peer has closed the connection ,and if so , close the socket.
    if (BytesTransferred == 0)
    {
    printf("Closing socket %d\n", AcceptSocket);
    closesocket(AcceptSocket);
    WSACloseEvent(Index - WSA_WAIT_EVENT_0);
    return;
    }

    //Do something with the received data DataBuf contains the received data.
    ...

    //step 8
    //post another WSARecv() request on the socket

    Flags = 0;
    ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));

    AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];

    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = Buffer;

    WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);
    }
    }

    2.完成例程
    在一个重叠IO请求完成时由系统调用的函数。他们的基本设计宗旨是通过调用者的线程,为一个已完成的I请求提供服务。除此之外,应用程序可通过完成例程,继续进行重叠IO处理。
    void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);

    WSABUF DataBuf;
    SOCKET AcceptSocket;

    void main()
    {
    WSAOVERLAPPED Overlapped;

    //step 1
    //start winsock and set up a listening socket

    //step 2
    //accept a new connection
    AcceptSocket = accept(ListenSocket, NULL, NULL);


    //step 3
    //To get the overlapped IO processing started, first submit an overlapped WSARecv() request.

    Flags = 0;
    ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));

    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = buffer;

    EventTotal++;

    //step 4;
    //post a WSARecv request to begin receiving data on the socket.
    if(WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, WorkerRoutine)==SOCKET_ERROR)
    {
    if (WSAGetLastError()!=WSA_IO_PENDING)
    {
    printf("WSARecv() failed with error%d\n",WSAGetLastError());
    return;
    }
    }

    //since the WSAWaitForMutipleEvents()API requires waiting on one or more event objects, we will have to create a dummy event object, Aa
    //an alterative , we can use SleepEx() instead.
    EventArray[0]=WSACreateEvent();

    while (TRUE)
    {
    //step 5
    Index = WSAWaitForMultipleEvents(1,EventArray, FALSE, WSA_INFINITE, FALSE);

    //step 6
    if (Index == WAIT_IO_COMPLETION)
    {
    //An overlapped request completion routine just completed ,continue servicing more completion rountines.
    }
    else
    {
    //A bad error occurred --stop processing! if we were also processing an event object, this could be index to the event array.

    return;
    }
    }
    }

    void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags)
    {
    DWORD SendBytes, RecvBytes;
    DWORD Flags;
    if (Error !=0)||BytesTransferred ==0
    {
    //Either a bad error occurred on the socket or the socket was closed by peer closesocket(AcceptSocket);
    return;
    }

    //At this point, an overlapped WSARecv() request completed successfully. Now we can retrieve the received data that is contained in the variable DataBuf
    //After processing the received data, we need to post another overlapped WSARecv() or WSASend() request. For simplicity , we will post another WSARecv() requst.
    Flags = 0;
    ZeroMemory(&Overlapped,sizeof(WSAOVERLAPPED));

    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = Buffer;

    if (WSARecv(AcceptSocket, &DataBuf,1,&RecvBytes,&Flags, &Overlapped, WorkerRoutine)==SOCKET_ERROR)
    {
    if (WSAGetLastError()!=WSA_IO_PENDING)
    {
    printf("failed with error%d\n",WSAGetLastError());
    return;
    }
    }
    }

  • 8.2.2 WSAAsyncSelect
    接收以windows消息为基础的网络事件通知。
    int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
    FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT和FD_CLOSE.
    从锁定变成非锁定状态。
    #define WM_SOCKET WM_USER+1
    #define

    int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
    {
    SOCKET Listen;
    HWND Window;
    sockaddr InternetAddr;

    Window = CreateWindow();

    WSAStartup(...);

    Listen = socket(...);

    InternetAddr.sin_family = AF_INET;
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    InternetAddr.sin_port = htons(5150);

    bind(Listen, (PSOCKADDR)&InternetAddr,sizeof(InternetAddr));

    WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);

    listen(Listen, 5);

    //Translate and dispatch window messages until the application terminates
    }

    BOOL CALLBACK ServerWinProc(HWND hDlg, WORD wMsg,WORD wParam, DWORD lParam)
    {
    SOCKET Accept;
    switch(wMsg)
    {
    case WM_PAINT:
    break;
    case WM_SOCKET:

    //Determine whether an error occurred on the socket by using the WSAGETSLECTERRROR() macro
    if (WSAGETSELECTERROR(lParam))
    {
    closesocket(wParam);
    break;
    }

    //Determine what event occurred on the socket
    switch(WSAGETSELECTEVENT(lParam))
    {
    case FD_ACCEPT:
    Accept = accept(wParam, NULL, NULL);

    //Prepare accepted socket for read, write and close notification.
    WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
    break;
    case FD_READ:
    //Receive data from the socket in wparam;
    break;
    case FD_WRITE:
    //The socket in param is ready for sending data;
    break;
    case FD_CLOSE:
    //The connection is now closed
    closesocket(wParam);
    break;
    }
    break;
    }
    return true;
    }
    产生FD_WRITE通知的三种条件
    1.使用connect或者WASConnect,一个套接字首次建立了连接。
    2.使用accept or WSAAccept,套接字被接受以后。
    3.若send WSASend,sendto WSASendto操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区的空间变得可用。