在这种情况下,您可能会对libevent 感兴趣。它将为您完成select() 的工作,可能使用更好的界面,例如epoll()。
select() 的巨大缺点是使用了FD_... 宏,它将套接字数限制为fd_set 变量中的最大位数(从大约 100 到 256)。如果你有一个有 2 或 3 个连接的小型服务器,你会没事的。如果您打算在更大的服务器上工作,那么fd_set 很容易被溢出。
此外,使用select() 或poll() 可以避免服务器中的线程(即您可以poll() 您的套接字并知道您是否可以accept()、read() 或write()给他们。)
但如果你真的想像 Unix 那样做,那么你想在调用accept() 之前考虑fork()-ing。在这种情况下,您并不绝对需要 select() 或 poll()(除非您正在侦听许多 IP/端口并希望所有子节点都能够应答任何传入连接,但是您对这些有缺点......内核当你已经在处理一个请求时,可能会向你发送另一个请求,而只有一个accept(),如果不在accept()调用本身中,内核就会知道你很忙——好吧,它不完全那样工作,但是作为用户,这就是它为您工作的方式。)
使用fork() 在主进程中准备套接字,然后在子进程中调用handle_request() 以调用accept() 函数。这样,您可能有任意数量的端口和一个或多个子端口来监听每个端口。这是在 Linux 下快速响应任何传入连接的最佳方式(即作为用户,只要您有子进程等待客户端,这是即时的。)
void init_server(int port)
{
int server_socket = socket();
bind(server_socket, ...port...);
listen(server_socket);
for(int c = 0; c < 10; ++c)
{
pid_t child_pid = fork();
if(child_pid == 0)
{
// here we are in a child
handle_request(server_socket);
}
}
// WARNING: this loop cannot be here, since it is blocking...
// you will want to wait and see which child died and
// create a new child for the same `server_socket`...
// but this loop should get you started
for(;;)
{
// wait on children death (you'll need to do things with SIGCHLD too)
// and create a new children as they die...
wait(...);
pid_t child_pid = fork();
if(child_pid == 0)
{
handle_request(server_socket);
}
}
}
void handle_request(int server_socket)
{
// here child blocks until a connection arrives on 'server_socket'
int client_socket = accept(server_socket, ...);
...handle the request...
exit(0);
}
int create_servers()
{
init_server(80); // create a connection on port 80
init_server(443); // create a connection on port 443
}
请注意,handle_request() 函数在此处显示为处理一个请求。处理单个请求的优点是您可以使用 Unix 方式进行处理:根据需要分配资源,一旦请求得到答复,exit(0)。 exit(0) 会为你调用必要的close()、free() 等。
相反,如果您想连续处理多个请求,您需要确保在循环回accept() 调用之前释放资源。此外,几乎永远不会调用 sbrk() 函数来减少孩子的内存占用。这意味着它会时不时地增长一点。这就是为什么像 Apache2 这样的服务器被设置为在开始一个新的孩子之前回答每个孩子一定数量的请求(默认情况下它在 100 到 1,000 之间。)