从未调用过 WSASend 完成例程

WSASend completion routine has never been called

我正在研究 Overlapped IO,突然发现我似乎是唯一一个不能鼓励完成回调工作的人(所有声明都是关于:它工作但我不喜欢它)。

我的应用程序的想法是:客户端(telnet localhost 27015)连接到服务器,服务器开始向客户端推送大量数据。而且我从来没有调用过 CompletionCallback。

代码如下:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <atomic>

#pragma comment(lib, "ws2_32.lib")
#define DATA_BUFSIZE 16384

class CSync
{
private:
    CRITICAL_SECTION  m_cs;

public:
    CSync()     { ZeroMemory(&m_cs, sizeof(m_cs)); InitializeCriticalSection(&m_cs); }
    ~CSync()    { DeleteCriticalSection(&m_cs);   ZeroMemory(&m_cs, sizeof(m_cs)); }
    inline void Lock()   { EnterCriticalSection(&m_cs); }
    inline void Unlock() { LeaveCriticalSection(&m_cs); }
    inline BOOL WINAPI TryLock() { return TryEnterCriticalSection(&m_cs); }
};

class ScopedLock
{
public:
    ScopedLock(CSync& lock) : m_lock(lock) { m_lock.Lock(); }
    ~ScopedLock() { m_lock.Unlock(); }

private:
    CSync m_lock;
};


class SendServer
{
private:
    SOCKET                socket;
    //  std::atomic<bool>     busy;
    char                  buffer[2][DATA_BUFSIZE];
    CSync                 syncer;
    WSABUF                wsabuf;
    OVERLAPPED            overlapped;
    //  HANDLE                socketEvent;
    std::atomic_flag      busy;
    char                  toBuffer; // 0 or 1
    DWORD                 sent;

public:
    SendServer(SOCKET& sock);
    virtual ~SendServer();
    bool Write(char* buff);
};


static void __stdcall Produce(SendServer *server);
void CALLBACK CompletionCallback(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);



static bool run = 1;

int main(int argc, char* argv[])
{
    WSADATA wsd;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET AcceptSocket = INVALID_SOCKET;

    int err = 0;
    int rc;

    // Load Winsock
    rc = WSAStartup((2, 2), &wsd);
    if (rc != 0) {
        printf("Unable to load Winsock: %d\n", rc);
        return 1;
    }

    // Make sure the hints struct is zeroed out
    SecureZeroMemory((PVOID)& hints, sizeof(struct addrinfo));

    // Initialize the hints to obtain the 
    // wildcard bind address for IPv4
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    rc = getaddrinfo(NULL, "27015", &hints, &result);
    if (rc != 0) {
        printf("getaddrinfo failed with error: %d\n", rc);
        return 1;
    }

    ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
        //socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        return 1;
    }

    rc = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (rc == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        return 1;
    }

    rc = listen(ListenSocket, 1);
    if (rc == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        return 1;
    }
    // Accept an incoming connection request
    AcceptSocket = accept(ListenSocket, NULL, NULL);
    if (AcceptSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        return 1;
    }

    printf("Client Accepted...\n");

    SendServer server(AcceptSocket);
    HANDLE h[1];

    h[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&Produce, &server, 0, NULL);

    getchar();
    run = 0;
    WaitForMultipleObjects(1, h, TRUE, INFINITE);

    return 0;
}

void __stdcall Produce(SendServer *server)
{
    char buf[] = "------------------------------------------------------------------------------------------";
    char s = 0;

    while (run) {
        buf[0] = '0' + s++;

        if (s > 9)
            s = 0;

        server->Write(buf);
        Sleep(10);
    }
}

void CALLBACK CompletionCallback(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
    ((SendServer*)(lpOverlapped->hEvent))->Write(NULL);
}

SendServer::SendServer(SOCKET& sock) : toBuffer(0)
{
    socket = sock;
    ZeroMemory(buffer, DATA_BUFSIZE << 1);
    busy.clear();
}

SendServer::~SendServer()
{
    shutdown(socket, 2);
    closesocket(socket);
}

bool SendServer::Write(char* buff)
{
    ScopedLock lock(syncer);
    int size = strlen(buffer[toBuffer]), toAdd = 0;

    if (buff == NULL) {
        busy.clear();
        SecureZeroMemory(buffer[!toBuffer], DATA_BUFSIZE);
    }
    else {
        toAdd = strlen(buff);
        if (size + toAdd < DATA_BUFSIZE) {
            memcpy_s(buffer[toBuffer] + size, toAdd, buff, toAdd);
            size += toAdd;
            buffer[toBuffer][size] = 0;
            return TRUE;
        }
        else {
            printf("\nCan't add anymore!\n");
        }
    }

    if (size > 0 && !busy.test_and_set()) {
        wsabuf.buf = (char*)buffer[toBuffer];
        wsabuf.len = size;

        SecureZeroMemory(&overlapped, sizeof OVERLAPPED);
        overlapped.hEvent = this;

        toBuffer = !toBuffer;
        size = WSASend(socket, &wsabuf, 1, &sent, 0, &overlapped, CompletionCallback);
        if (size == 0) {
            //return Write(NULL);
        }
        if (WSA_IO_PENDING != WSAGetLastError()) {
            return FALSE;
        }
    }
    return TRUE;
}

谢谢。

alertable wait 期间调用完成回调。你没有可提醒的等待,所以完成回调排队但永远没有机会 运行.

WaitForMultipleObjects 更改为包含 WaitForMultipleObjectsEx 的循环,将 Sleep 更改为 SleepEx,并将 TRUE 作为 bAlertable 参数传递。

这在 the WSASend documentation

中有解释

The completion routine follows the same rules as stipulated for Windows file I/O completion routines. The completion routine will not be invoked until the thread is in an alertable wait state such as can occur when the function WSAWaitForMultipleEvents with the fAlertable parameter set to TRUE is invoked.