windows 线程接受套接字上的连接

windows thread to accept a connection on a socket

我熟悉 pthreads,但不熟悉 Windows 线程。在 Linux 中,可以启动一个新线程:

pthread_t tid;
int rc = pthread_create(&tid, NULL, Threadfn, &newsocket);
assert (rc == 0);
//<snip>//

Threadfn可以轻松重构Socket:

void *Threadfn(void *vargp){
    pthread_detach(pthread_self());
    int *Socket = (int *) vargp;
    print("Socket is %d\n", *Socket);
    // recv/read/send etc.. 
    pthread_exit(NULL);
}

我们如何在 Windows 个线程中执行此操作?

我创建线程:

HANDLE thread = CreateThread(NULL, 0, Somethready, &ClientSocket, 0, NULL);

但我似乎在使用 Somethready 时遇到了麻烦:

DWORD WINAPI Somethready(void *vargp) {
    printf("Thread got evoked\n");
    SOCKET *clientSocket = (SOCKET *)vargp;
    SOCKET ClientSocket = *clientSocket;
    printf("In thread, ClientSocket: %d\n", ClientSocket);
    /*
    char RecvBuf[bufsize];
    memset(RecvBuf, 0, bufsize);
    int n = recv(ClientSocket, RecvBuf, bufsize,0);
    print("We got %d bytes, we got %s\n", n, RecvBuf);
    */
    return 0;
}

我好像没弄对:

ClientSocket: 184
Thread got evoked
In thread, ClientSocket: -1 // <<-- this 

我做错了什么? IOW,我怎样才能正确地将 ClientSocket 传递给 windows 线程?

谢谢!

编辑 1

ClientSocket 是这样形成的:

while (1) {
    SOCKET ClientSocket = INVALID_SOCKET;
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("ClientSocket: %d\n", ClientSocket);
    HANDLE thread = CreateThread(NULL, 0, Somethready, &ClientSocket, 0, NULL);
}

编辑 2

感谢您的回复。我有点震惊,因为我以前从未在 Linux 中遇到过这种情况 - 不知何故,变量不会消失得那么快,至少在我尝试时是这样。然而,这在我第一次尝试 Windows 线程时就渗透了。这是一个宝贵的教训。

问题: 我注意到如果我在调用线程后立即添加一个轻微的延迟(如下所示),似乎表现正常,我们没有堆分配来稍后清理,这使得它更具吸引力。我很好奇这是否可以接受,或者这是一场等待中的灾难。谢谢!

while (1) {
    SOCKET ClientSocket = INVALID_SOCKET;
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("ClientSocket: %d\n", ClientSocket);
    HANDLE thread = CreateThread(NULL, 0, Somethready, &ClientSocket, 0, NULL);
    Sleep(30); // < -- this 
}

How can I ensure ClientSocket stays a while before it goes off the heap

常见的模式是:

while (1) {
  SOCKET ClientSocket = INVALID_SOCKET;
  ClientSocket = accept(ListenSocket, NULL, NULL);

  ...

  printf("ClientSocket: %d\n", ClientSocket);
  {
    SOCKET * psd = malloc(sizeof *psd); /* allocate in the parent */
    *psd = ClientSocket;
    HANDLE thread = CreateThread(NULL, 0, Somethready, psd, 0, NULL);
  }

并在线程内执行:

DWORD WINAPI Somethready(void *vargp) {
  printf("Thread got evoked\n");
  SOCKET *pClientSocket = (SOCKET *)vargp;
  SOCKET ClientSocket = *pClientSocket;
  printf("In thread, ClientSocket: %d\n", ClientSocket);

  ...

  free(pClientSocket);  /* deallocate in the child */

  return 0;
}

顺便说一句,您进入的陷阱并非特定于 windows 的线程,但与 POSIX 线程完全相同。

已发布的 Windows 代码和已发布的 Linux 代码中的一个问题是,您传递给 newly-spawned 线程的参数是指向parent-thread 的堆栈,并且在子线程有机会启动 运行 并查看之前,该局部变量可能已从堆栈中弹出(因此可能被其他数据覆盖)它。

所以解决问题的方法就是保证子线程看的时候数据仍然有效。您可以通过多种方式执行此操作:

1) 简单(通常也是最好)的方法:不是在堆栈上分配套接字(作为局部变量),而是从堆中分配它:

// main thread
while (1) {
    SOCKET * pClientSocket = (SOCKET *) (malloc(sizeof(SOCKET)));  // allocate a SOCKET on the heap
    if (pClientSocket == NULL) {printf("malloc() failed!?\n"); break;}

    *pClientSocket = accept(ListenSocket, NULL, NULL);
    if (*pClientSocket == INVALID_SOCKET) {
        free(pClientSocket);  // avoid memory leak!
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("ClientSocket: %d\n", *pClientSocket);
    HANDLE thread = CreateThread(NULL, 0, Somethready, pClientSocket, 0, NULL);
    if (thread == NULL)
    {
       closesocket(*pClientSocket);  // avoid socket leak!
       free(pClientSocket);  // avoid memory leak!
       printf("CreateThread failed!?\n");
    }
}

// child thread
DWORD WINAPI Somethready(void *vargp) {
    printf("Thread got evoked\n");
    SOCKET *pClientSocket = (SOCKET *)vargp;
    SOCKET ClientSocket   = *pClientSocket;  // make a copy of the heap-allocated SOCKET object into a local variable
    free(pClientSocket);                     // then free the heap-allocated SOCKET to avoid a memory leak

    printf("In thread, ClientSocket: %d\n", ClientSocket);
    [...]

    closesocket(ClientSocket);  // don't forget to close the socket when we're done
    return 0;
}

那会很好用,因为 heap-allocated SOCKET 对象(由 pClientSocket 指向)保证在有人调用 free() 之前不会被销毁,在将其内容复制到局部变量 ClientSocket.

之后,此代码将其留给子线程执行

唯一可能遇到的问题是,如果您忘记在 heap-allocated 套接字上调用 free()(例如,在 error-handling early-return case),所以你需要小心。

2) cheap-hack 方式。这涉及一些潜在的 unsafe/undefined-behavior-invoking 转换,但它在实践中有效,所以很多人都这样做。在这种方法中,我们只是将 SOCKET 的值直接填充到 void-pointer 中。我不推荐它,但为了完整性:

// main thread
while (1) {
    SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("ClientSocket: %d\n", *ClientSocket);
    HANDLE thread = CreateThread(NULL, 0, Somethready, (void *) ClientSocket, 0, NULL);
    if (thread == NULL)
    {
       closesocket(ClientSocket);  // avoid socket leak
       printf("CreateThread failed!?\n");
    }
}

// child thread
DWORD WINAPI Somethready(void *vargp) {
    printf("Thread got evoked\n");
    SOCKET ClientSocket = (SOCKET)vargp;  

    printf("In thread, ClientSocket: %d\n", ClientSocket);
    [...]

    closesocket(ClientSocket);  // don't forget to close the socket itself when we're done
    return 0;
}

3) I'm-too-clever-a-programmer-for-my-own-good 方法:在这种方法中,在生成子线程后,我们使用条件变量来阻止主线程的执行,直到子线程表明它已经启动 运行 并且不再使用 pointer-to-the-main-thread's-stack-variable。我打算用伪代码写这个,因为我不在 windows 机器上测试它,但这应该给你一个大概的想法:

// global variables (or if you don't like global variables, you 
// could put these into a struct, along with the SOCKET object, 
// and pass a pointer-to-the-struct to the child thread instead)
CONDITION_VARIABLE wait_for_child_thread;
CRITICAL_SECTION   critical_section;

InitializeCriticalSection(&critical_section);
InitializeConditionVariable(&wait_for_child_thread);

// main thread
while (1) {
    SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("ClientSocket: %d\n", *ClientSocket);
    HANDLE thread = CreateThread(NULL, 0, Somethready, &ClientSocket, 0, NULL);
    if (thread != NULL)
    {  
       // Gotta wait here until the child thread wakes us up,
       // otherwise we risk invalidating (&ClientSocket) before he has used it!
       EnterCriticalSection(&critical_section);
       SleepConditionVariableCS(&wait_for_child_thread, &critical_section, INFINITE);
       LeaveCriticalSection(&critical_section);
    }
    else
    {  
       printf("CreateThread failed!?\n");
    }
}

// child thread
DWORD WINAPI Somethready(void *vargp) {
    printf("Thread got evoked\n");
    SOCKET * pClientSocket = (SOCKET *)vargp;
    SOCKET ClientSocket    = *pClientSocket;  // copy from main-thread's stack to our own stack

    // Now that we've made the copy, tell the main thread he can continue execution
    WakeConditionVariable(&wait_for_child_thread);

    printf("In thread, ClientSocket: %d\n", ClientSocket);
    [...]

    closesocket(ClientSocket);  // don't forget to close the socket itself when we're done
    return 0;
}