草庐IT

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

coder 2024-06-09 原文

我熟悉 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 
}

最佳答案

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

关于c - Windows线程接受套接字上的连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56052469/

有关c - Windows线程接受套接字上的连接的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  3. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  4. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  5. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  6. ruby - 无法在 60 秒内获得稳定的 Firefox 连接 (127.0.0.1 :7055) - 2

    我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类

  7. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  10. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

随机推荐