我试图了解套接字编程和处理多个连接时的不同做法。 特别是当服务器需要为多个客户端提供服务时。
我看过一些代码示例;有些人使用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_set
和fork
)之间的差异是什么?一个比另一个更合适吗?
答案 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的方法时,由于创建了更多进程,我们最终会使用更多的系统资源。
方法的选择取决于应用程序 - 预期的连接数,连接的性质(持续或持续时间短),是否需要在连接处理程序之间共享数据等。