近一年做了些linux服务端网络编程的项目,这里需要整理一下服务器端常见的编程模型。这片博文https://blog.youkuaiyun.com/qq_29108585/article/details/78177278 整理的思路比较清晰。另外,在经典书籍《Unix网络编程 卷1》第三版的第30章“客户/服务器程序设计范式”也对这方面做了比较完美的总结。
模型1: 同步阻塞迭代模型(或简称 迭代模式)
用一个进程处理客户端请求,总是在完成某个客户的请求之后才会转向下一个客户请求,这样的服务器程序非常少见。其伪代码大体如下:调用的api都是阻塞式。
socket();
bind();
listen();
for(;;){
clifd = accept(...); //开始接受客户端来的连接
read(clifd,buf,...); //从客户端读取数据
dosomthingonbuf(buf);
write(clifd,buf) //发送数据到客户端
close(); //关闭当前客户端的连接
}
弊端:
1)如果没有客户端的连接请求,进程会长时间阻塞在accept系统调用处。
2)在与客户端建立好一条链路后,通过read系统调用从客户端接受数据,而客户端合适发送数据过来是不可控的。如果客户端迟迟不发生数据过来,则程序会阻塞在read调用,此时,如果另外的客户端来尝试连接时,都会失败。
3)write系统调用也会使得程序出现阻塞(例如:客户端接受数据异常缓慢,导致写缓冲区满,数据迟迟发送不出)。
模型2:多进程并发模型(为每个客户开辟一个子进程)
这种模型使得服务器能够同时为多个客户服务,每个进程一个客户。客户数目的唯一限制是 操作系统对当前用户能够拥有的子进程的最大数目。
缺点:为每个客户现场fork一个子进程比较耗费CPU时间,在20世纪80年代后期,一个繁忙的服务器每天也就处理几千个客户时,这点CPU时间是可以接受的。然而,web应用的爆发式增长改变了人们的态度。繁忙的web服务器每天需要处理百万计的客户。
大体代码结构: 一下代码来自《unix网络编程 卷 1》5.10章节,与第三十章的对应部分的代码类似,只不过下面的更简洁一些。
int listenfd, connfd;
pid_t childpid;
void sig_chld(int);//当接受到SIGCHLD(子进程结束)信号之后,执行的信号处理函数,处理僵尸子进程
listenfd = socket();
bind(listenfd,..);
listen(listenfd,..);
Signal(SIGCHLD,sig_chld);//绑定信号处理函数
for(; ; ;)
{
conndf = accept(listenfd,...);//阻塞式
if(childpid = Fork()) == 0)// child process
{
close(listenfd); //close listening socket(这是从父进程复制过来的,要关掉)
handle(connfd,...);//处理请求
}
close(connfd); //父进程直接关闭connfd,以为connfd已经传递给子进程处理了
}
注意: 用这种方法时,不要忘记捕捉SIGCHLD信号,处理僵尸进程,防止占用多余的系统资源。上面例子的具体讲解可以看5.1--5.10章节。
另外,30.6章节,给出了预先创建进程池来优化这种模型的方法,深入探讨可以参考相应的内容。
。。。待续