C语言中带有'select()'的多客户端的套接字编程

时间:2013-12-01 02:24:37

标签: c sockets networking select

这是关于多客户端套接字编程的问题。

我正在考虑如何制作单个客户端和服务器程序 对多客户端,我遇到了如何实现这一点。 但即使我在寻找一切,也存在一种混乱。

  1. 我想用select()实现,因为它比fork更重。 但我有很多全局变量不能共享,所以我没有考虑使用线程。

  2. 所以要使用select(),我可以掌握有关FD_functions的一般知识,但在这里我有我的问题,因为一般在网站上的例子中,它只显示多客户端服务器程序..

  3. 因为我在客户端和服务器程序中使用顺序recv()和send() 当它是单个客户端和服务器时,它的工作非常好,但是 我不知道如何为多重静音更改它。 客户端是否也必须解锁? select()的所有要求是什么?

    我在服务器程序上做的事情是多客户端

    1)我使用SO_REUSEADDR

    为重用地址设置了socket选项

    2)并使用fctl()将我的服务器设置为非阻塞模式和O_NONBLOCK。

    3)并将超时参数设为零。

    并在上面正确使用FD_functions。

    但是,当我从第二个客户端运行一个或多个客户端程序时, 客户端程序块,不被服务器接受。

    我想原因是因为我把我的服务器程序的主要功能部分 进入'recv was> 0'的情况。

    例如使用我的服务器代码

    (我使用temp并读取为fd_set,在这种情况下读取为master)

    int main(void)
    {
    
      int conn_sock, listen_sock;
      struct sockaddr_in s_addr, c_addr;
      int rq, ack;
      char path[100];
      int pre, change, c;
      int conn, page_num, x;
      int c_len = sizeof(c_addr);
      int fd;
      int flags;
      int opt = 1;
      int nbytes;
      fd_set read, temp;
    
      if ((listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
      {
        perror("socket error!");
        return 1;
      }
    
      memset(&s_addr, 0, sizeof(s_addr));
    
      s_addr.sin_family = AF_INET;
      s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
      s_addr.sin_port = htons(3500);
      if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1)
      {
    
        perror("Server-setsockopt() error ");
        exit(1);
    
      }
      flags = fcntl(listen_sock, F_GETFL, 0);
      fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK);
    
      //fcntl(listen_sock, F_SETOWN, getpid());
    
      bind(listen_sock, (struct sockaddr*) &s_addr, sizeof(s_addr));
    
      listen(listen_sock, 8);
    
      FD_ZERO(&read);
      FD_ZERO(&temp);
    
      FD_SET(listen_sock, &read);
      while (1)
      {
    
        temp = read;
    
        if (select(FD_SETSIZE, &temp, (fd_set *) 0, (fd_set *) 0,
            (struct timeval *) 0) < 1)
        {
          perror("select error:");
          exit(1);
        }
    
        for (fd = 0; fd < FD_SETSIZE; fd++)
        {
          //CHECK all file descriptors
    
          if (FD_ISSET(fd, &temp))
          {
    
            if (fd == listen_sock)
            {
    
              conn_sock = accept(listen_sock, (struct sockaddr *) &c_addr, &c_len);
              FD_SET(conn_sock, &read);
              printf("new client got session: %d\n", conn_sock);
    
            }
            else
            {
    
              nbytes = recv(fd, &conn, 4, 0);
              if (nbytes <= 0)
              {
                close(fd);
                FD_CLR(fd, &read);
              }
              else
              {
    
                if (conn == Session_Rq)
                {
    
                  ack = Session_Ack;
                  send(fd, &ack, sizeof(ack), 0);
    
                  root_setting();
    
                  c = 0;
                  while (1)
                  {
                    c++;
                    printf("in while loop\n");
                    recv(fd, &page_num, 4, 0);
    
                    if (c > 1)
                    {
                      change = compare_with_pre_page(pre, page_num);
    
                      if (change == 1)
                      {
                        page_stack[stack_count] = page_num;
                        stack_count++;
                      }
                      else
                      {
                        printf("same as before page\n");
                      }
                    } //end of if
                    else if (c == 1)
                    {
                      page_stack[stack_count] = page_num;
                      stack_count++;
                    }
    
                    printf("stack count:%d\n", stack_count);
                    printf("in page stack: <");
    
                    for (x = 0; x < stack_count; x++)
                    {
                      printf(" %d ", page_stack[x]);
                    }
    
                    printf(">\n");
    
                    rq_handler(fd);
    
                    if (logged_in == 1)
                    {
                      printf("You are logged in state now, user: %s\n",
                          curr_user.ID);
                    }
                    else
                    {
                      printf("not logged in.\n");
                      c = 0;
                    }
    
                    pre = page_num;
                  } //end of while
                } //end of if
              }
            } //end of else
          } //end of fd_isset
        } //end of for loop  
      } //end of outermost while
    }
    

    如果代码解释需要:我将要使用的代码是, 制作一种网页来实现服务器的“浏览器”。 我想让每个客户端获取服务器的会话以获得登录页面左右。

    但执行结果如上所述。 那是为什么?

    1. 客户端程序中的套接字也必须是非阻塞模式 与非阻塞服务器程序一起使用select()?

    2. 或者我应该使用fork或thread来制作多客户端并使用select进行管理? 我说这个的原因是,在我考虑了很多关于这个问题之后, 'select()'似乎只适用于多客户端聊天程序......那么多 'forked'或'threaded'客户端可以在聊天室中等待。 你怎么想?... 是否也可以选择正常的多客户端程序?

    3. 如果有什么我错过了让我的多客户端程序正常工作, 请给我一些你的知识或正确使用select的一些要求。 我之前不知道多客户端通信是不是很容易:) 我也考虑过使用epoll,但我认为我需要首先了解选择好。

      感谢阅读。

1 个答案:

答案 0 :(得分:5)

除了您想要从单客户端转到多客户端之外,还不清楚是什么阻止了您。

你确定你完全理解select应该如何运作?手册(Linux上的man 2 select)可能会有所帮助,因为它提供了一个简单的示例。您还可以查看Wikipedia

回答你的问题:

  1. 首先,您确定您的套接字需要非阻塞模式吗?除非你有充分的理由这样做,否则阻塞套接字对于多客户端网络来说也很好。

  2. 通常,基本上有两种方法可以处理C中的多客户端:forkselect。这两个并没有真正使用(或者我不知道如何:-))。使用轻量级线程的模型本质上是asynchronous programming(我提到它还取决于你对'异步'的意思吗?)并且可能对你的行为有点过分(C ++中的一个很好的例子是{{3 }})。

  3. 正如您可能已经知道的那样,处理多个客户端时的主要问题是I / O操作(如read)正在阻塞,不会让我们知道何时有新客户端,或者当客户说了些什么。

    fork方式非常简单:服务器套接字(接受连接的套接字)在主进程中,每次接受新客户端时,它都会分配一个全新的进程来监视这个新客户:这个新流程将专注于它。由于每个客户端有一个进程,我们不关心i / o操作是否阻塞。

    select方式允许我们在同一个进程中监视多个客户端:它是一个多路复用器告诉我们什么时候我们给它的套接字发生了什么。服务器端的基本思想是首先将服务器套接字放在select的read_fds FD_SET上。每次select返回时,您需要对其进行特殊检查:如果服务器套接字设置在read_fds集中(使用FD_ISSET(...)),则表示您有一个新的客户端连接:您可以然后在服务器套接字上调用accept以创建连接。 然后,您必须将所有客户端套接字放入您为select提供的fd_sets中,以便监控其上的任何更改(例如,传入消息)。

    我不确定你对select所不了解的内容,所以这就是大解释。但长话短说,select是一种干净利落的单线程同步网络方式,它可以在不使用任何fork或线程的情况下同时完全管理多个客户端。请注意,如果您绝对想要使用select来处理非阻塞套接字,则必须处理不会阻塞的额外错误条件(维基百科示例显示它很好,因为它们必须检查如果errno不是EWOULDBLOCK)。但这是另一个故事。

    编辑:好的,使用更多代码可以更容易地知道错误。

    1. select的第一个参数应该是nfds + 1,即“三个集合中任意一个中编号最大的文件描述符加1”(参见手册),而不是FD_SETSIZE,这是最大值一个FD_SET。通常它是最后一个accept - ed客户端套接字(或开头的服务器套接字)拥有它。
      1. 您不应该为此循环执行“检查所有文件描述符”。 FD_SETSIZE,例如在我的机器上,等于1024.这意味着一旦select返回,即使你只有一个客户端,你将在循环中传递1024次!您可以将fd设置为0(如维基百科示例中所示),但由于0为stdin,1 stdout和2 stderr,除非您正在监控其中一个,你可以直接将它设置为服务器套接字的fd(因为它可能是第一个被监视的套接字,给定套接字数量总是增加),并迭代直到它等于“nfds”(当前最高的fd)。
    2. 不确定它是强制性的,但在每次调用select之前,您应该清除(例如使用FD_ZERO)并使用您要监视的所有套接字(即您的服务器套接字)重新填充读取的fd_set以及所有客户的插座)。再一次,启发自己的维基百科示例。