【问题标题】:Attaching a C/C++ thread to a socket将 C/C++ 线程附加到套接字
【发布时间】:2018-02-17 00:21:02
【问题描述】:

我的代码取自在线资源,如下所示:

//.. initialization and other things..
int connfd = 0;
connfd = accept(listenfd, (struct sockaddr*)&client_address,&client_address_len);
//... other code
 pthread_create(&tid, NULL, &foo, &connfd);

上面的代码,将调用带有参数 id 的函数 foo。现在每个线程都有自己的 foo 版本来运行参数。在 foo 中,我可以使用套接字向客户端发送数据。但是,我看不到如何(尽管它工作得很好),是什么链接了线程和套接字。我知道 id 已传递,但它只是一个整数。

这是 foo 函数的样子:

  void* SendFileToClient(void *var){
   int *connfd;
   connfd =(int*) var;
   std::cout << "Connection accepted and id:" << *connfd << std::endl;
   std::cout << "Connected to Client with IP: " <<   inet_ntoa(c_addr.sin_addr) << "Port:" << ntohs(c_addr.sin_port) << std::endl; // inet_ntoa converts binary form of IPv4 to IPv4 dotted numbers, ntohs does something silimar for ports
   int write_size = write(*connfd, fname,64);

   std::cout << write_size << "connfds: " <<  *connfd << std::endl;

   FILE *fp = fopen(fname,"rb");

   if(fp==NULL)
    {
        printf("File opern error");
        exit(EXIT_FAILURE);
    }

    /* Read data from file and send it */
    while(1)
    {
        /* First read file in chunks of 64 bytes */
        unsigned char buff[1024]={0};
        int nread = fread(buff,1,1024,fp);

        /* If read was success, send data. */
        if(nread > 0)
        {
            std::cout << "Sending..." << std::endl;
            write_size =  write(*connfd, buff, nread);
        }
        if (nread < 1024)
        {
            if (feof(fp)) //end of file indicator
            {
                std::cout << "End of file" << std::endl;
                std::cout << "File transfer completed for id:" << *connfd << std::endl;
            }
            if (ferror(fp))
                std::cout << "Error reading" << std::endl;
            break;
        }
    }
    std::cout << "Closing Connection for id:" << *connfd << std::endl;
    close(*connfd);
    shutdown(*connfd,SHUT_WR);
    sleep(2);
    return NULL;

}

在上面的代码中,调用

   write(*connfd, buff, nread);

知道通过 connfd 引用的套接字发送数据。

【问题讨论】:

  • 如果您投反对票,请指出什么是错误的,以帮助他人和我自己从否定中获得一些东西。
  • “连接线程和套接字”是什么意思?
  • 没有链接,不是真的。就线程而言,它将调用一个函数并传递一个值,该值恰好被该函数用作int,而int恰好是一个套接字标识符。
  • 没有看到其余的代码(因为它没有发布),我最担心的是id 的生命周期。如果它在接受连接的某些执行迭代中,它将在每次迭代时超出范围。将其地址作为传递参数的线程可能在此之前没有获得引用的int 值,当您最终获得时,您将处于 UB 领域。这可能非常微妙,因为它很容易看起来“有效”。只有当大量连接入站并且id 中的数据开始被覆盖时,事情才会开始看起来很奇怪。
  • @hadis 很容易。一个不需要太多结果的解决方案是通过intptr_t 推送连接描述符。然而,其中涉及相当多的转换,即使代码符合标准,也很容易混淆。另一种解决方案是简单地动态分配int 并将接受的连接存储在其中,然后将其作为用户参数传递给pthread_create。最后,让线程 proc 关闭描述符并释放内存。这些方法中的任何一种都不会简单地最小化竞争条件,它会消除它。

标签: c++ function sockets pthreads


【解决方案1】:

什么是链接线程和套接字

connfd。它是套接字的文件描述符。是的,它“只是一个整数”,但是您的操作系统(处理所有与套接字相关的函数调用)知道如何处理它,并为您执行所有必要的映射。这就是为什么它通过accept 调用为您提供该值:作为与未来套接字函数调用一起使用的句柄。

正如其他地方所提到的,请注意您当前正在不安全地将connfd 传递给线程;你应该传入一个副本……要么编码到线程参数中,伪装成指针(通过uintptr_t),要么传入一些新的动态分配对象,然后你可以将指针传递给线程。

或者,理想情况下,放弃 pthreads 并使用std::thread,因为现在已经是 2018 年了,而且该功能已经使用了很长一段时间。这允许将任意数量的参数传递给foo,因此您只需按值传递connfd 并保留它。

【讨论】:

  • @EJP:我说的是原版int。如果更清楚,我们可以删除变量名称的提及 - 关键是 FD 负责。
  • 我的意思是他没有将它传递给线程。他通过了它的地址,这就是问题所在。
  • @EJP:虽然我当然不否认存在问题,但问题并不涉及问题。它询问是什么链接线程和套接字。这就是我要解决的问题(没有双关语)
  • @EJP:解决这个问题的正确方法是什么?代码不是我真正写的,是在网上找的,作为起点。
  • @hadis 参考 EJP 答案下的 cmets
【解决方案2】:

您似乎错误地描绘了线程。线程只是调用和执行函数的一种方式。该程序调用foo 函数,但允许它以某种并发的方式运行,与对相同或不同函数的其他调用共享执行时间(和多个cpu,如果你有的话)。这种执行方式是线程,它由操作系统管理,与其他进程类似。

您的程序只是调用一个常规函数 'foo' 并传递一个参数,该参数恰好是 confd 文件描述符。这是foo 函数唯一需要知道的关于套接字的事情,以便将数据写入其中。该程序还通过调用 pthread_create 告诉操作系统它想将该函数作为线程执行。函数本身并不关心它是如何执行的。

当函数退出时,操作系统停止线程执行。

因此,套接字和线程之间实际上没有任何联系。只是功能...

【讨论】:

    【解决方案3】:

    write 函数不关心句柄是否映射到套接字、文件或激光枪,它只知道如何将内容写入缓冲区并发出缓冲区已准备好刷新的信号。在创建套接字、打开文件或激光枪时,安排了它如何被刷新。

    【讨论】:

      【解决方案4】:
      int *connfd;
      connfd =(int*) var;
      

      你不应该这样做。您必须尽快复制 FD:

      int connfd = *(int*)var;
      

      否则下次接受后会改变。每次使用它时,您都在尊重 *connfd。如果accept() 再次返回,它将返回一个不同的 FD,您的线程现在将使用错误的 FD;并且您将丢失原始FD;所以你也会有一个套接字泄漏。

      更好的是,将它传递到线程负责释放的一块动态内存中。

      【讨论】:

      • 虽然你是对的,但我并不认为“尽快复制 FD”就足够了,除非你以某种方式保护原始 connfd。相反,将副本传递到线程开始。
      • @LightnessRacesinOrbit 我同意这一点。从intintptr_t 的转换,然后转换为void*,然后在thread-proc 上撤消所有这些,这将是一种方法。另一种无强制转换的解决方案是动态分配、存储、启动和无线程方法。要么解决现有的竞争条件。
      • @EJP 不,不.. 它由 value 传递。,转换为 void*。基本上,您将描述符填充到指针中,然后在线程端取消填充;不要用指针指向描述符。
      • @EJP:如果你被void* 卡住,那么pthread_create(&amp;tid, NULL, &amp;foo, (void*)(uintptr_t)connfd); (ish)。另一端是const int connfd = (uintptr_t)theVoidPtr; (ish)
      • 当然这是 C++,所以 OP 应该简单地使用 std::thread 并按值传递。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-16
      • 2012-09-07
      • 2018-10-16
      • 2011-03-15
      • 1970-01-01
      相关资源
      最近更新 更多