要求:此服务器是个并发服务器,能同时服务多个客户
解决方案:调用fork(),生成子进程,让每个子进程服务于一个客户
导致的潜在问题:子进程退出后会变成僵尸进程,占用内存空间,要求父进程调用wait()来处理,但不能同步调用wait(),因为这会导致无法实现并发
解决方案:采用信号机制,异步调用wait(),当子进程退出时,内核会像其父进程发送一个SIGCHLD信号,因此我们注册一个信号处理函数,当收到SIGCHILD信号时捕获这个信号并调用wait(),这又带来了一个新问题
潜在问题:进程收到信号并调用信号处理函数时会中断正在运行的系统调用,有些系统在处理完信号后并不重新启动被中断的系统调用,这就要求我们自己启动
潜在问题:当父进程在同一时刻收到多个SIGCHLD信号或信号阻塞期间收到多个SIGCHLD时,他只调用一次信号处理函数,这就导致了有未被及时处理的子进程
解决方案:采用waitpid(),非阻塞方式循环处理,如果有僵尸子进程则处理,如果没有则循环结束
处理可能发生的异常情况
异常情况1:服务器端子进程被关闭
服务器端行为:给客户端发送一个FIN
客户端情况:客户进程此时阻塞于fgets()函数,不知道服务器子进程已经关闭。tcp协议栈收到FIN,tcp协议栈认为对方单项关闭,仍可向对方发送数据,允许调用write()函数,并能成功返回,都是协议栈的锅,收到FIN只能代表对方已经单项关闭,无法知道服务器子进程已经关闭,即全关闭,这就导致了客户端不能立即知道服务器子进程已经关闭,当调用read()时,read()会返回0,此时客户端才知道服务器子进程已关闭。
此情况发生时write()函数的情况:tcp协议栈收到FIN,tcp协议栈认为对方单项关闭,仍可向对方发送数据,允许调用write()函数,并能成功返回,此时由于服务器子进程已关闭,tcp协议栈会发送一个RST给客户端,但客户端进程不知道这个过程,若收到RST后再次调用writen(),内核会向客户进程发送SIGPIPE信号,进程会被终止
解决办法:就本程序而言,认为read()返回0时就代表不能向服务器发送数据,一旦收到FIN,read()就会返回0,但当收到FIN时,客户端阻塞于fgets(),解决此问题的办法是使用select()
处理被中断的系统调用:系统阻塞于某个系统调用时若收到信号,系统调用会被中断返回,erron会被设置为EINTR,此时需要我们自行重新调用被中断的函数
服务器端服务函数的设计
不同的服务器有不同的服务函数,但一般首先都是要读取套接字数据,再根据服务要求处理数据,并将处理结果返回给客户,客户可能要求多次服务,所以服务函数应该设计为被动退出,采用循环,read()返回0代表客户端不再发送请求,循环结束,此时服务函数方可返回