套接字编程 - 多个连接:分叉还是FD_SET?

时间:2015-11-24 09:28:46

标签: c sockets server client fork

我试图了解套接字编程和处理多个连接时的不同做法。 特别是当服务器需要为多个客户端提供服务时。

我看过一些代码示例;有些人使用fd_set而其他人使用fork()系统调用。

粗略地说:

FD_SET

//Variables
fd_set fds, readfds;

//bind(...)
//listen(...)
FD_ZERO(&fds);
FD_SET(request_socket, &fds);

while(1) {
    readfds = fds;
    if (select (FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0)
        //Something went wrong

    //Service all sockets with input pending
    for(i = 0; i < FD_SETSIZE; i++) {
        if (FD_ISSET (i, &readfds)) {
            if (i == request_socket) {
               /* Connection request on original socket. */
               int new;
               size = sizeof (clientname);
               new = accept (request_socket, (struct sockaddr *) &clientname, &size);
                if (new < 0)
                    //Error

                fprintf (stderr, "Server: connect from host %s, port %hd.\n", inet_ntoa (clientname.sin_addr), ntohs (clientname.sin_port));
                FD_SET (new, &fds);
          }
          else {
              /* Data arriving on an already-connected socket. */
              if (read_from_client (i) < 0) {  //handles queries
                  close (i);
                  FD_CLR (i, &fds);
              }
          }//end else

fork()

//bind()
//listen()

while(1) {
    //Connection establishment
    new_socket = accept(request_socket, (struct sockaddr *) &clientaddr, &client_addr_length);

    if(new_socket < 0) {
        error("Error on accepting");
    }

    if((pid = fork()) < 0) {
        error("Error on fork");
    }
    if((pid = fork()) == 0) {
        close(request_socket);
        read_from_client(new_socket);
        close(new_socket);
        exit(0);
    }
    else {
        close(new_socket);
    }
}

我的问题是:两种做法(fd_setfork)之间的差异是什么?一个比另一个更合适吗?

2 个答案:

答案 0 :(得分:2)

您可以根据从客户端收到连接后必须执行的IO操作的性质,在两种方法之一select()fork()之间进行选择。

需要注意的是,许多IO系统调用都是阻塞的。虽然某个客户端的IO上的程序被阻止(例如,需要连接到数据库,读取磁盘上的文件等的请求),但它无法满足其他客户端的请求。如果使用fork()创建新流程,则每个流程都可以独立阻止,而不会阻碍其他连接的进度。虽然为每个客户启动流程似乎有效,但多个流程更难协调,并消耗更多资源。没有正确或错误的方法,这完全取决于权衡。

您可以阅读“事件与线程”以了解要考虑的各种权衡:请参阅:Event Loop vs Multithread blocking IO

select()系统调用方法(您称之为FD_SET方法)也称为轮询。使用此过程,进程可以一次等待多个文件描述符事件,在那里休眠,并在FD_SET中指定的文件描述符之一上出现活动时被唤醒。您可以阅读手册页以选择详细信息(man 2 select)。一旦新数据到达任何感兴趣的套接字,这将允许服务器进程逐位读取多个客户端(但仍然是一次)。尝试在没有可用数据的套接字上调用read()会阻止 - select只是确保您只对那些有数据的人执行此操作。它通常在一个循环中调用,以便进程返回下一个工作。以该样式编写程序通常会强制一个人迭代地处理请求,并且要小心,因为你想避免在单个进程中阻塞。

fork()man 2 fork)创建子进程。子进程是使用父进程中打开的文件描述符的副本创建的,这解释了系统调用返回时的所有fd-closing业务。一旦你有一个子进程来处理客户端的套接字,那么你可以用阻塞调用编写简单的线性代码而不影响其他连接(因为那些将由服务器的其他子进程并行处理)。

答案 1 :(得分:1)

这两种做法的主要区别在于用于处理多个连接的进程数。使用select,单个进程(实际上是单个线程)可以处理来自多个客户端的并发连接。当我们使用基于fork的方法时,会为每个新连接创建一个新进程。因此,如果有N个并发客户端连接,则将有N个进程来处理这些连接。

当我们使用select时,我们不需要担心共享内存或同步,因为一切都在同一执行线程中发生。

另一方面,当我们使用select时,我们需要在编码时更加小心,因为同一个执行线程将处理多个客户端。在基于fork的方法中,子进程必须只处理一个客户端,因此它往往更容易实现。

当我们使用基于fork的方法时,由于创建了更多进程,我们最终会使用更多的系统资源。

方法的选择取决于应用程序 - 预期的连接数,连接的性质(持续或持续时间短),是否需要在连接处理程序之间共享数据等。