在线程中使用select()不会对OS主机断开连接做出React

rjee0c15  于 12个月前  发布在  React
关注(0)|答案(2)|浏览(95)

我有主服务器代码,它使用选择和等待主机活动。我也有全局结构,它存储所有的客户端和他们的套接字。
//旧代码
当客户端数组中的一个主机断开连接时,主线程中的Select解除了对intself的阻塞。然后我尝试读取数据,当它为0时,客户端断开连接。为什么线程中的select不以同样的方式工作?当客户端交换数据时,它工作得很好,但当其中一个客户端断开连接时,没有React。
EDIT.
抱歉,我之前发布的代码,无法编译。这个想法是,只是为了获得信息,如果我的客户端处理的想法是正确的,如果这个想法应该工作。我已经创建了一个简单的例子,现在它的工作。所以,我必须审查我的原始代码。
工作示例:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include <arpa/inet.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <pthread.h>
#include <limits.h>
#include <errno.h>
#include <time.h>
#include <sys/wait.h>

#define MAXCLIENTS 1000
#define FRAMESIZE 40

struct Client
{
  int socket;
  char state;
};
struct Client clients[2];

struct ThreadArgs
{
  int srcClientIdx;
  int dstClientIdx;
};

int portNumber=8090;

void * SocketThread(void *args)
{
   printf("New thread created.\n");
   struct ThreadArgs *threadArgs = (struct ThreadArgs*)args;
   int sidx = threadArgs->srcClientIdx;
   int didx = threadArgs->dstClientIdx;
   int srcSocket = clients[sidx].socket;
   int dstSocket = clients[didx].socket;

   fd_set readfds;
   struct timeval timeout;

   while(1)
   {
      FD_ZERO(&readfds);
      FD_SET(srcSocket, &readfds);
      FD_SET(dstSocket, &readfds);

      printf("Waiting for activities in thread.\n");

      timeout.tv_sec = 60;
      timeout.tv_usec = 0;
      int activity = select(MAXCLIENTS+1, &readfds , NULL , NULL , &timeout);
      if ((activity < 0) && (errno!=EINTR))
         printf("Activity error.\n");
      else if(activity == 0)
         printf("Activity timeout.\n");

      if (FD_ISSET(srcSocket, &readfds))
      {
         unsigned char buffer[FRAMESIZE];
         int bytesCnt = read(srcSocket, buffer, sizeof(buffer));
         if(bytesCnt == 0)
         {
           printf("Client%d disconnected.\n",sidx);
           clients[sidx].state = 0;
           clients[sidx].socket = -1;
           pthread_exit(NULL);
         }
         else
           printf("Client%d activity.\n",sidx);
      }

      if (FD_ISSET(dstSocket, &readfds))
      {
         unsigned char buffer[FRAMESIZE];
         int bytesCnt = read(dstSocket, buffer, sizeof(buffer));
         if(bytesCnt == 0)
         {
           printf("Client%d disconnected.\n",didx);
           clients[didx].state = 0;
           close(clients[didx].socket);
           clients[didx].socket = -1;
           pthread_exit(NULL);
         }
         else
           printf("Client%d activity.\n",didx);
      }

   }
 }

 int ConfigureTCPIPconnection(int socket,struct sockaddr_in *serverAddr)
 {
   // Address family = Internet
   serverAddr->sin_family = AF_INET;
   //Set port number, using htons function to use proper byte order
   serverAddr->sin_port = htons(portNumber);
   //Incoming addresses
   serverAddr->sin_addr.s_addr = INADDR_ANY;

   //Set all bits of the padding field to 0
   memset(serverAddr->sin_zero, '\0', sizeof serverAddr->sin_zero);

   //Bind the address struct to the socket
   if(bind(socket, (struct sockaddr *)serverAddr, sizeof(struct sockaddr)) < 0)
   {
     printf("Binding failed. %s\n",strerror(errno));
     return 1;
   }

   printf("Bind sucessfull.\n");
   return 0;
 }

int CheckSocketActivity(fd_set *readfds)
{
   int activity = select( MAXCLIENTS + 1 , readfds , NULL , NULL , NULL);
   if ((activity < 0) && (errno!=EINTR))
      return 1;
   return 0;
}

int main(int argc, char *argv[])
{
  fd_set readfds;
  int serverSocket,newSocket;
  pthread_t td; //thread descriptor
  struct sockaddr_in serverAddr, newAddr={0};
  socklen_t addr_size;

  clients[0].state = 0;
  clients[0].socket = -1;
  clients[1].state = 0;
  clients[1].socket = -1;

  if((serverSocket = socket(PF_INET, SOCK_STREAM, 0)) == 0)
  {
    printf("Server socket creation failed.\n");
    exit(1);
  }

  if(ConfigureTCPIPconnection(serverSocket,&serverAddr))
  {
    printf("Binding failed.\n");
    exit(2);
  }

  if(listen(serverSocket,MAXCLIENTS))
  {
    printf("Listen error.\n");
    exit(3);
  }

  printf("Listening...\n");

  while(1)
  {
    FD_ZERO(&readfds); //clear fd set
    FD_SET(serverSocket, &readfds); //add server socket to fd set
    if(clients[0].state == 1)
      FD_SET(clients[0].socket, &readfds); //add active clients to fd set
    if(clients[1].state == 1)
      FD_SET(clients[1].socket, &readfds);

    printf("Waiting for activities.\n");

    if(!CheckSocketActivity(&readfds))
    {
      if(FD_ISSET(serverSocket, &readfds))
      {
        if((newSocket = accept(serverSocket, (struct sockaddr *)&newAddr, (socklen_t*)&addr_size))<0) //create new socket
          printf("New socket error. %s\n",strerror(errno));
        else
        {
          printf("New socket connected.\n");
          if(clients[0].state == 0)
          {
            printf("Client0 added.\n");
            clients[0].state = 1;
            clients[0].socket = newSocket;
          }
          else if(clients[1].state == 0)
          {
            printf("Client1 added.\n");
            clients[1].state = 1;
            clients[1].socket = newSocket;
          }
        }
      }
        else
        {
           for(int i=0;i<2;i++)
           {
              int srcSock = clients[i].socket;
              if (FD_ISSET(srcSock, &readfds))
              {
                 unsigned char buffer[FRAMESIZE];
                 int bytesCnt = read(srcSock, buffer, sizeof(buffer));
                 if(bytesCnt == 0)
                 {
                   printf("Client%d disconnected.\n",i);
                   clients[i].state = 0;
                   close(clients[i].socket);
                   clients[i].socket = -1;
                 }
                 else
                 {
                   if(clients[0].state == 1 && clients[1].state == 1)
                   {
                      int srcIndex,dstIndex;
                      //some other stuff
                      clients[0].state = 2;
                      clients[1].state = 2;
                      if(i == 0)
                      {
                        srcIndex = 0;
                        dstIndex = 1;
                      }
                      else
                      {
                        srcIndex = 1;
                        dstIndex = 0;
                      }
                      printf("Creating new thread.\n");
                      struct ThreadArgs threadArgs = {srcIndex,dstIndex};
                      if(pthread_create(&td, NULL, SocketThread, &threadArgs) != 0)
                         printf("Failed to create thread.\n");
                      if (pthread_detach(td))
                         printf("Thread detach error.\n");
                    }
                    else
                      printf("Two clients required for creating thread.\n");
                 }
              }
           }
        }
     }
  }
}

字符串

thtygnil

thtygnil1#

我假设你发布了你正在处理的代码,因为你发布的代码不应该编译。

  • 更正1(可能不是问题的根源)

select()手册页:

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

字符串
所有的FD_*()宏都接受fd_set*参数。在main()中,你在几个地方传递了fd_set而不是fd_set*

  • 更正2(可能是问题的根源)

SocketThread()中,这段代码可能是问题的原因:

if (FD_ISSET(dstSocket, &readfds))
   {
      unsigned char buffer[FRAMESIZE];
      bytesCnt = read(srcSock, buffer, sizeof(buffer));
      if(bytesCnt == 0)
         **//this is not detected**
      else
        //any other activities are detected
   }


您可能需要read(dstSocket, buffer, sizeof(buffer));

cmssoen2

cmssoen22#

(请参见下文,以查看您的问题的完整答案,再次,一旦您的代码已显示)
你说:
当客户端数组中的一个主机断开连接时,主线程中的Select解除了对intself的阻塞。然后我尝试读取数据,当它为0时,客户端断开连接。为什么线程中的select不以同样的方式工作?当客户端交换数据时,它工作得很好,但当其中一个客户端断开连接时,没有React。
拜托,你没有说客户端是如何断开连接的。它只是关闭套接字吗?它会关闭吗?我在套接字上没有看到任何close(2),那么为什么你知道是谁关闭了连接(或者调用shutdown(2)来表示连接中不会再出现数据)?还有,你是否每个线程只有一个套接字?你是否在线程之间共享套接字?你实际上是在客户端阅读服务器套接字吗?这是用同一个线程完成的吗?你每个线程都有一个select(),还是只有一个线程阅读套接字并将消息分发给其他线程来处理?我需要坚持提供一个最小的完整的例子来说明你为什么要问这个问题,因为它似乎是XY问题的一个示例。

注:我还没有解释为什么知道客户端如何与服务器断开连接如此重要。当您使用TCP连接时,请求可以被缓冲,答案也可以。因为等待服务器的前一个响应来发出下一个请求太糟糕了。(它清空了通道,导致非常糟糕的性能)通常会批处理请求,并对来自服务器的响应进行分隔,以区分是否实际完成了协议。我想说的是,如果客户端只是close(2) s连接,它不仅仅是告诉内核发送文件的结尾(FIN段)到连接的另一端,但它也向内核发出信号,表示不愿意在此连接上接收更多数据,这将使客户端的TCP拒绝(并发送一个警告或 * 像一些Windows实现一样 * 设置一个窗口大小为0,它将永远不会再次打开-导致服务器重置,因为在FIN段发送后不允许进行任何Windows设置)两种我都见过。问题是仍然要从服务器发送到客户端的数据丢失了(客户端无法读取它,如果它关闭了连接,则关闭后套接字描述符不可用,即使关闭失败并出现错误),这条注解的原因是,如果客户端代码只有close(2) d连接,那么他做得不好,这可能是他错误地假设事情是如何发生的地方(注解结束

通常情况下,连接会阻塞,直到你收到一些数据.接收0数据意味着没有数据到达,或者在select(2)调用选择了一个没有更多数据的套接字描述符之后,你关闭了套接字。但是这必须在某个地方关闭,你既没有显示close(2)也没有显示shutdown(2)。你也没有描述在套接字上检测到的是什么。如果你从select(2)唤醒,你会得到文件描述符和结果。from read is not 0 what value does the read return?你知道-1是这样一个可能的值,这意味着你有一个readin错误吗?你检查select返回的值吗?(在超时时,我的记忆告诉我select(0)返回0,所以你检查超时吗?)
在对你的代码进行监视之后,你首先应该考虑到select中的错误检查是不完整的:

int activity = select(MAXCLIENTS+1, &readfds , NULL , NULL , &timeout);
      if ((activity < 0) && (errno!=EINTR))
         printf("Activity error.\n");
      else if(activity == 0)
         printf("Activity timeout.\n");

字符串
应该是(更好:):

int activity = select(MAXCLIENTS+1, &readfds , NULL , NULL , &timeout);
      if (activity < 0) {
         if (errno!=EINTR) {
             printf("Activity error.\n");
             continue; /* go back to the loop start, or break the
                        * loop, or exit or something
                        * as if you continue processing, you'll have
                        * no idea on what happen.  Probably both sockets
                        * will appeared as readable, untouched, as you
                        * have not succeeded in the select() call,  */
          } else {     /* error, and not a signal interruption, you
                        * should exit the loop, as signal doesn't
                        * work */
              printf("some select error: %s\n", strerror(errno)); 
              break;
          }
      } else if(activity == 0) {
         printf("Activity timeout.\n");
         /* in this case you are free to continue the loop down, but
          * there's no reason for that, you can do some housekeeping
          * and go up to the loop again */
         do_my_housekeeping();
         continue;
      }


我实际上不明白为什么一个线程必须读取一对套接字,这使得你的例子既不简单也不完整(你不做你的线程对数据做的事情,只是丢弃它)
我见过的唯一糟糕的代码(这让我投了一票)是你忽略了来自select()的错误并继续处理,可能你的非线程代码在这一点上是不同的,这让你认为当使用线程时,select的行为不同。你应该阅读关于线程是如何实现的(在McKusick的书 《FreeBSD操作系统的设计和实现》 中有一个很好的参考,也许你应该理解select(2)系统调用在多线程程序中没有作用。当你运行多线程应用程序,这应该已经发现了你的兔子,发现错误在你的代码中,而不是在select(2)中。我首先假设你的代码是正确的,基于你没有显示任何代码。这让我投了一个反对票,因为(恕我直言)我没有关注这个问题。但是现在你的代码在那里,它是不正确的,我想失去我的反对票。:)

这个错误是非常非常频繁的,所以我不会否决你的问题,更多的,我会投赞成票,相反,但只有在你发布了一个小的(好吧,不是极简主义者,当我为我的学生实现服务器时,你必须看到我的示例代码,我不使用超过100行的代码)

**第二个注意事项:**你不会检测到你的socket(你会检测到它,但只有一次,当你在下一个循环中继续选择关闭的描述符时,FD_SET()再次检测它。这也是一个错误,你不应该在检测到socket的时候立即将它添加到FD_SET()中(如果你有多个线程,这是非常重要的,因为你将馈送一个关闭的描述符,这会让你跌倒,但如果其他线程创建一个新的socket,它会导致你存储在socket描述符变量中的相同值。程序中的不同线程共享它们的文件描述符。

相关问题