c - 在C中使用 'select()'为多客户端进行套接字编程

标签 c sockets networking select

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

当我在考虑如何制作单个客户端和服务器程序时
对于多个客户,我遇到了如何实现这一点。
但是,即使我正在搜索所有内容,仍然存在某种困惑。

  • 我正在考虑使用select()实现,因为它比fork轻。
    但是我有很多全局变量不可以共享,所以我没有考虑使用线程。
  • 等,以便使用select(),我可以掌握要使用的FD_functions的一般知识,但是这里有我的问题,因为通常在网站上的示例中,它仅显示多客户端服务器程序...

  • 由于我在客户端以及服务器程序中使用顺序recv()和send()
    当它是单个客户端和服务器时,它确实可以很好地工作,但是
    我不知道如何针对多语言更改它。
    客户端是否也必须取消阻止?
    select()的所有要求是什么?

    我在服务器程序上做的多客户端操作

    1)我使用SO_REUSEADDR将套接字选项设置为重用地址

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

    3)并将timeout参数设为零。

    并在上面之后正确使用FD_functions。

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

    我想原因是因为我把服务器程序的主要功能部分放在了
    进入“recv为> 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
    }
    

    如果需要进行代码说明:我将要使用此代码,
    制作某种网页来为服务器实现“浏览器”。
    我想让每个客户端都获得服务器的 session 以获取登录页面左右。

    但是执行的结果是,正如我上面所说的。
    这是为什么?
  • 客户端程序中的套接字也必须为非阻塞模式
    与非阻塞服务器程序一起使用以使用select()?
  • 还是应该使用fork或线程制作多客户端并通过select管理?
    我这样说的原因是,在我对这个问题进行了很多考虑之后,
    “select()”似乎仅适用于多客户端聊天程序...那么多
    可以在诸如聊天室之类的“派生”或“线程化”客户端中挂接。
    你怎么想?...
    对于普通的多客户端程序,是否还可以选择select或使用合适的东西?

  • 如果我错过了让多客户端程序正常运行的某些功能,
    请给我一些有关您的知识或正确使用select的一些要求。
    我之前不知道多客户端通信不是那么容易:)
    我也考虑过使用epoll,但我认为我需要首先了解选择的技巧。

    谢谢阅读。

    最佳答案

    除了您要从单客户端转到多客户端的事实外,目前还不清楚是什么在阻止您。

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

    要回答您的问题:

  • 首先,您确定套接字需要非阻塞模式吗?除非您有充分的理由这样做,否则阻塞套接字也适用于多客户端网络。
  • 通常,在C中,基本上有两种处理多客户端的方法:forkselect。两者并没有真正一起使用(或者我不知道怎么用:-))。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。丧ss.。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。使用轻量级线程的模型本质上是asynchronous programming(我是否提到过,它还取决于您所说的“异步”吗?),对于您似乎要做的事情可能有些过大(在C++中,一个很好的例子是Boost.Asio)。

  • 您可能已经知道,与多个客户打交道时的主要问题是I/O操作(例如read)正在阻塞,何时有新客户或客户说了些什么就不通知我们。
    fork的方法很简单:服务器套接字(接受连接的套接字)在主进程中,并且每次接受新客户端时,它都会派生一个全新的进程来监视此新客户端:此新进程将专注于它。由于每个客户端只有一个进程,因此我们不在乎I/O操作是否处于阻塞状态。
    select方法使我们可以在同一过程中监视多个客户端:这是一个多路复用器,告诉我们何时在我们提供给它的套接字上发生什么事情。在服务器端的基本思想是,首先将服务器套接字放置在select的read_fds FD_SET上。每次select返回时,您都需要对其进行特殊检查:如果服务器套接字在read_fds集中设置(使用FD_ISSET(...)),则意味着您有一个新的客户端正在连接:您可以在之后调用accept您的服务器套接字来创建连接。
    然后,您必须将所有客户端套接字放入您提供给select的fd_set中,以监视其上的任何更改(例如,传入消息)。

    我不太确定您对select的不了解,这是为了进行充分的解释。但是长话短说,select是进行单线程同步网络的一种干净整洁的方法,它可以绝对地同时管理多个客户端,而无需使用任何fork或线程。但是请注意,如果您绝对要使用select处理非阻塞套接字,则必须处理不会阻塞的额外错误情况(Wikipedia示例很好地说明了这一点,因为他们必须检查errno是否为' t EWOULDBLOCK)。但这是另一个故事。

    编辑:好的,再加上一些代码,更容易知道出了什么问题。
  • select的第一个参数应为nfds + 1,即“三个集合中任何一个中编号最高的文件描述符加1”(参见手册),而不是FD_SETSIZE,它是FD_SET的最大大小。通常是拥有最后一个accept的客户端套接字(或开头的服务器套接字)。
  • 您不应该为这样的循环执行“检查所有文件描述符”。 FD_SETSIZE,例如在我的机器上,等于1024。这意味着一次select返回,即使您只有一个客户端,也将在循环中传递1024次!您可以将fd设置为0(例如在Wikipedia示例中),但是由于0是stdin,1 stdout和2 stderr,除非您要监视其中之一,否则可以将其直接设置为服务器套接字的fd(因为可能是在给定的套接字号始终增加的情况下,第一个受监视的套接字会不断增加,并进行迭代直到它等于“nfds”(当前最高的fd)。
  • 不确定它是否是强制性的,但是在每次调用select之前,您应该清除(例如,使用FD_ZERO),然后使用要监视的所有套接字(即服务器套接字和所有客户端套接字)重新填充读取的fd_set。 。再一次,激发自己以Wikipedia为例。
  • 关于c - 在C中使用 'select()'为多客户端进行套接字编程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20308048/

    相关文章:

    快速异步网络。出现错误时如何重试?

    c++ - 端口打开,但没有人可以连接

    c - N命令管道 "Inter-process "

    C 命名约定 : hidden variables/macros

    c - memcpy 和 ntohl 不同的输出

    java - 套接字的多线程问题

    c++ - 在 Linux 中使用原始套接字时,802.3 header 长度始终为 256

    python - 无法安装 Kivy : Cython/GCC error

    C 编程,需要帮助将用户输入放入 for 循环中

    sockets - 在执行 SSL_read 或 SSL_write 操作之前使用 SSL_want?