把微软的那个代码改了一下。 vc 6下通过。需要添加一个很大的.txt 文件  我这里用了一个2m 的文件。我想达到的目的是。 读一个2m 的文件当有连接连上来,我就向这个端口 投递2m 的数据。 采用的是多次投递,过程在主线程里面压的。 方法很烂。 但目前看来还是管用的。 现在看来的结果是 
第一 客户端没有乱序。
第二 当客户端故意断掉的时候 workthread 会有问题。因为显示了 GetQueuedCompletionStatus failed with error 64 
我认为已经退出了。但服务程序还是可以继续。....怎么解决??
还有能不能给点更专业的写法?

解决方案 »

  1.   

    // Module Name: iocmplt.cpp
    //
    // Description:
    //
    //    This sample illustrates how to develop a simple echo server Winsock
    //    application using the completeion port I/O model. This 
    //    sample is implemented as a console-style application and simply prints
    //    messages when connections are established and removed from the server.
    //    The application listens for TCP connections on port 5150 and accepts them
    //    as they arrive. When this application receives data from a client, it
    //    simply echos (this is why we call it an echo server) the data back in
    //    it's original form until the client closes the connection.
    //
    // Compile:
    //
    //    cl -o iocmplt iocmplt.cpp ws2_32.lib
    //
    // Command Line Options:
    //
    //    iocmplt.exe 
    //
    //    Note: There are no command line options for this sample.#include <winsock2.h>
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    #define PORT 5150
    #define DATA_BUFSIZE 8192typedef struct
    {
    OVERLAPPED Overlapped;           // &Ouml;&Oslash;&micro;&thorn;io
    WSABUF DataBuf;                  // WSABUF
    CHAR *Buffer; // &Otilde;&aelig;&Ecirc;&micro;&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;&Auml;&Uacute;&Egrave;&Yacute;
    DWORD BytesSEND;                 // &Euml;&Iacute;&sup3;&ouml;&Egrave;&yen;&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;&Ecirc;&yacute;&Aacute;&iquest;
    DWORD BytesRECV;                 // &Ecirc;&Otilde;&micro;&frac12;&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;&Ecirc;&yacute;&Aacute;&iquest;
    } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
    typedef struct 
    {
    SOCKET Socket;
    } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);char *Pdata = NULL;void main(void)
    {
    SOCKADDR_IN InternetAddr;
    SOCKET Listen;
    SOCKET Accept;
    HANDLE CompletionPort;
    SYSTEM_INFO SystemInfo;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
    unsigned int i;
    DWORD RecvBytes;
    DWORD Flags;
    DWORD ThreadID;
    WSADATA wsaData;
    DWORD Ret;
    FILE * datafile;

    unsigned int filelen = 0;

    datafile = fopen("c.txt", "rb");
    fseek(datafile, 0, 2);
    filelen = ftell(datafile);

    Pdata = new char [filelen +1];
    Pdata[filelen] = 0;
    fseek(datafile, 0, 0);
    filelen = 0;


    while ( fread(Pdata+filelen, 1000, 1, datafile) == 1)
    {
    filelen+=1000;
    }


    Pdata[filelen] = 0;
    if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
    {
    printf("WSAStartup failed with error %d\n", Ret);
    return;
    }

    // Setup an I/O completion port.
    if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
    {
    printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());
    return;
    }

    // Determine how many processors are on the system.

    GetSystemInfo(&SystemInfo);

    // Create worker threads based on the number of processors available on the
    // system. Create two worker threads for each processor.     


    for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
    {
    HANDLE ThreadHandle;

    // Create a server worker thread and pass the completion port to the thread.

    if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
    0, &ThreadID)) == NULL)
    {
    printf("CreateThread() failed with error %d\n", GetLastError());
    return;
    }

    // Close the thread handle
    CloseHandle(ThreadHandle);
    }

    // Create a listening socket   &frac12;¨&Aacute;&cent;&Otilde;ì&Igrave;&yacute;socket

    if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
    WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
    {
    printf("WSASocket() failed with error %d\n", WSAGetLastError());
    return;


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

    // °&Ntilde;&para;&Euml;&iquest;&Uacute;bind &Eacute;&Iuml;&Egrave;&yen;

    if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
    {
    printf("bind() failed with error %d\n", WSAGetLastError());
    return;
    }

    // Prepare socket for listening

    if (listen(Listen, 5) == SOCKET_ERROR)
    {
    printf("listen() failed with error %d\n", WSAGetLastError());
    return;
    }

    // Accept connections and assign to the completion port.

      

  2.   

    while(TRUE)  // &Oacute;&Agrave;&Ocirc;&para;&iquest;&ordf;&Ecirc;&frac14;&Ntilde;&shy;&raquo;·
    {
    if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
    {
    printf("WSAAccept() failed with error %d\n", WSAGetLastError());
    return;
    }
    // accept &Ograve;&raquo;&cedil;&ouml;client


    unsigned int send_times = 0;

    while ( send_times < filelen) {



    if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR,
    sizeof(PER_HANDLE_DATA))) == NULL)
    {
    printf("GlobalAlloc() failed with error %d\n", GetLastError());
    return;
    }

    if (send_times == 0) {

    if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
    0) == NULL)
    {
    printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
    return;
    }
    }

    // Create per I/O socket information structure to associate with the 
    // WSARecv call below.

    if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
    {
    printf("GlobalAlloc() failed with error %d\n", GetLastError());
    return;
    }

    ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); // &frac12;&laquo;io&para;&Euml;&iquest;&Uacute; &Ouml;&Atilde;&Icirc;&ordf;&iquest;&Otilde;
    PerIoData->BytesSEND = 0; // send ×&Ouml;&frac12;&Uacute;&Icirc;&ordf;0
    PerIoData->BytesRECV = 0; // &Ecirc;&Otilde;&micro;&frac12;×&Ouml;&frac12;&Uacute;
    PerIoData->DataBuf.len = 1000; // Buffer&micro;&Auml;&sup3;¤&para;&Egrave;
    PerIoData->Buffer = Pdata + send_times;
    PerIoData->DataBuf.buf = PerIoData->Buffer; // &Ecirc;&Otilde;&micro;&frac12;&micro;&Auml;Buffer;

    Flags = 0;
    //
    //

    if (WSASend(Accept, &(PerIoData->DataBuf), 1, &PerIoData->BytesSEND, 0,
    &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)

    {
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
    printf("WSARecv() failed with error %d\n", WSAGetLastError());
    return;
    }
    }

    send_times += 1000;  
    }
    send_times = 0;
    }
    }DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID) // ·&thorn;&Icirc;&ntilde;&sup1;¤×÷&Iuml;&szlig;&sup3;&Igrave;
    {
    HANDLE CompletionPort = (HANDLE) CompletionPortID; // &acute;&Oacute;&sup2;&Icirc;&Ecirc;&yacute;&raquo;&ntilde;&Egrave;&iexcl;&Iacute;ê&sup3;&Eacute;&para;&Euml;&iquest;&Uacute;&para;&Ocirc;&Iuml;óhandle
    DWORD BytesTransferred;
    LPOVERLAPPED Overlapped;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
    DWORD SendBytes, RecvBytes;
    DWORD Flags;

    while(TRUE) // &Euml;&Agrave;&Ntilde;&shy;&raquo;·
    {

    if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, // &raquo;&ntilde;&Egrave;&iexcl;&para;&Oacute;&Aacute;&ETH;&Ouml;&ETH;iocp&Ouml;&ETH;&micro;&Auml;×&acute;&Igrave;&not;
    (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
    {
    printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
    return 0;
    }


    // First check to see if an error has occured on the socket and if so
    // then close the socket and cleanup the SOCKET_INFORMATION structure
    // associated with the socket.

    if (BytesTransferred == 0) // &micro;±·&cent;&Euml;&Iacute;&frac12;&Oacute;&Ecirc;&Otilde;&Ecirc;&yacute;&frac34;&Yacute;&Icirc;&ordf;0
    {
    printf("Closing socket %d\n", PerHandleData->Socket);

    if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
    {
    printf("closesocket() failed with error %d\n", WSAGetLastError());
    return 0;
    }

    GlobalFree(PerHandleData); // &Ccedil;&aring;&sup3;&yacute;&micro;&yen;&Iuml;ò&Ecirc;&yacute;&frac34;&Yacute;
    GlobalFree(PerIoData); // &Ccedil;&aring;&sup3;&yacute;&Ecirc;&yacute;&frac34;&Yacute;
    continue;
    }

    if ( BytesTransferred == PerIoData->BytesSEND) {
    GlobalFree(PerHandleData); // &Ccedil;&aring;&sup3;&yacute;&micro;&yen;&Iuml;ò&Ecirc;&yacute;&frac34;&Yacute;
    GlobalFree(PerIoData); // &Ccedil;&aring;&sup3;&yacute;&Ecirc;&yacute;&frac34;&Yacute;
    continue;
    }

    if ( BytesTransferred < PerIoData->BytesSEND) {  // ·&cent;&Euml;&Iacute;&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;&ETH;&iexcl;&Oacute;&Uacute;&Ograve;&ordf;&Euml;&Iacute;&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;
    ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
    //PerIoData->BytesRECV = ;
    PerIoData->BytesSEND = PerIoData->BytesSEND - BytesTransferred;

    PerIoData->DataBuf.len = PerIoData->BytesSEND; // Buffer&micro;&Auml;&sup3;¤&para;&Egrave;
    PerIoData->Buffer = PerIoData->Buffer + BytesTransferred;
    PerIoData->DataBuf.buf = PerIoData->Buffer; // &Ecirc;&Otilde;&micro;&frac12;&micro;&Auml;Buffer;

    if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &(PerIoData->BytesSEND), 0,
    &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
    {
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
    printf("WSASend() failed with error %d\n", WSAGetLastError());
    return 0;
    }
    }
    }
    }   


    }
      

  3.   

    问个问题:如果服务器在回送客户端一个很大的buffer,这个时候,程序设置的标志是IoWrite,可正好客户端发送了一份数据过来了。那么这个时候,GetQueuedCompletionStatus捕获到的是写返回还是客户端发送的返回怎么辨别???
      

  4.   

    晓得了,每次多投递一个WSARecv放在那里,如果有recv事件,调用的因该是那个Recv关联的LPWSAOVERLAPPED,和发送的LPWSAOVERLAPPED是可以区别开的。。
      

  5.   

    在一个连接中调用两次WSASend,一次发送了40960字节的0到客户端,一次是1,服务器每次GetQue返回的都是完整发送,所以客户端也没有乱序接收,没有模拟出ERROR_IO_PENDING情况。现在对tcp的乱序很迷惑ing。
    我的想法;如果第一次投递到套接字s上的数据大于了数据缓冲区,那么操作系统锁定该区域,发送,这时候如果用户再投递一次WSASend,如果这次投递数据正好可以copy到缓冲区,则,系统会按照顺序发送还是?这时候两次数据的发送顺序是???
    期待高人
    我的理解,重复投递,如果不人为的干预肯定会出现,000011110000客户端收到这样数据的情况??
      

  6.   

    很显然,多次投递是保证了次序的。 第一次投递 都没有内存可以copy 了。第二次为什么能成功?只是我对我那个错误 很疑惑。
      

  7.   

    这时候如果用户再投递一次WSASend,如果这次投递数据正好可以copy到缓冲区 如果copy 了 应该就是没有保证顺序。 多次投递应该不可以的
      

  8.   

    我的意思是第二次投递很小的数据块。
    刚才我在局域网里面模拟测试了,但是没有模拟出需要多次发送的情况。第一次发送了120M的数据,连续发送第二个的时候就会出现WSA_IO_PANDING的错误,但等到GetQue的时候都是发送完成了的返回。如果大于120M,发送,则发送直接失败,返回超出发送队列的错误。
    刚和ProgrameMan(我要学汇编)兄聊了会,我也认可ProgrameMan(我要学汇编)兄的说法,发送是线性的,是可以保持发送的顺序的。但是我还是有点怀疑,如果第一次发送的0没有发送完,那我第二次发送的1是不是就一直不会从GetQue那里得到返回信息,直到第一次的0循环几次发送完毕,才轮到第二次的1返回发送状态呢??
    有实际操作经验的人吗??
      

  9.   

    如果保证了顺序 就应该是你这样说的。但也有矛盾的地方 如果第一次发送的0没有发送完 , 也会返回的。 返回的字数少于总要发送的字符,这时候再调一次send.  这样说来,它应该在队列的最后了, 从我的程序结果来看不是的。 我猜队列有优先级的,先进的对象,优先级比较高,可以一直在队列前面。  无责任猜想
      

  10.   

    我和 toxyboy(长的比较无奈) 讨论过程中我所阐述的观点的确还存在一些没有搞懂的地方,因此不排除错误的可能性。不知哪位实际测试过的人能献身说法。
      

  11.   

    没有彻底搞懂顺序的问题。
    我的解决方案是:设置接收和发送双缓冲区,自己来控制接收和发送的顺序,每次只投递一个WSARecv和一个WSASend,这样不管实际投递是不是有序的,我都不用关心了。
    期待高人给个测试的说法。
      

  12.   

    一个收一个发的确可以解决问题。但效率不高。而且当有很多数据要发的时候,要么用定长buff 多次来发送。要么变长buff 发送。 当受到==0 的时候,还不能直接删除对象。 比较麻烦阿
      

  13.   

    用CancelIO,设置参数,每次投递都将计数器加一或者减一,等到要删除的时候,如果计数器为0则删除,要不就等到检测到返回为0的时候在删除。
    呵呵,我的代码给太多人了,我怕泛滥了,到不是怕大家看,而是怕水平不好影响了大家的思路。。
      

  14.   

    show me [email protected]超时的代码最好也有