поток Windows, чтобы принять соединение на сокете - PullRequest
2 голосов
/ 09 мая 2019

Я знаком с 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 
}

Ответы [ 2 ]

2 голосов
/ 09 мая 2019

Проблема как в опубликованном коде 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 непосредственно в указатель void.Я не рекомендую это, но для полноты:

// 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;
}
2 голосов
/ 09 мая 2019

Как я могу гарантировать, что ClientSocket останется некоторое время, прежде чем оно выйдет из кучи

Общий шаблон:

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.

...