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

时间:2019-05-09 05:00:56

标签: c windows multithreading winsock2

我熟悉pthread,但是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 
}

2 个答案:

答案 0 :(得分:2)

  

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线程完全相同。

答案 1 :(得分:2)

发布的Windows代码和发布的Linux代码中的一个问题是,传递给新生成的线程的参数是指向父线程堆栈上的局部变量的指针,并且该局部变量很可能会在子线程有机会开始运行并对其进行查看之前,已将其从堆栈中弹出(因此可能被其他一些数据覆盖)。

因此,解决该问题的方法是确保子线程查看数据时数据仍然有效。有几种方法可以做到这一点:

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;
}

这将很好地起作用,因为保证在有人调用SOCKET之前,分配给堆的pClientSocket对象(由free()指向)不会被破坏,并且此代码离开将其内容复制到局部变量ClientSocket之后,由子线程来完成。

唯一的陷阱是,如果忘记在堆分配的套接字上调用free()(例如在错误处理提早返回的情况下),很容易意外地造成内存泄漏。对此要小心。

2)廉价破解方式。这涉及到一些潜在的不安全/行为不确定的强制转换,但实际上它起作用,因此很多人都这样做。在这种方法中,我们仅将SOCKET的值直接填充到无效指针中即可。我不建议这样做,但出于完整性考虑:

// 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)我太聪明了,因为程序员是我自己的好方法:在这种方法中,生成子线程之后,我们使用条件变量来阻止主线程的执行,直到子线程表明它已开始运行,并且不再使用指向主线程的栈变量的指针。我将用伪代码编写此代码,因为我不是在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;
}