0.非并发
while(1)
{
clientsock=accept();
while(1)
{
str=recv.clientsock() //block
if(str)
send.clientsock(str);
else
{
close(clientsock);
break;
}
}
}
一次只服务一个客户,阻塞在recv上,浪费时间。
1.process-per-connection
适合长连接,而且计算相应的工作量远远大于fork(),比如数据库服务器。
做法为只要收到一个连接就fork一个子进程,让子进程来处理这个连接,执行handler程序。
2.thread-per-connection
上一个的线程版。开销依然不适合短连接使用,而且收到系统中线程数上限的限制。
3.perfork
对1的优化,见UNP
4.perthread
对2的优化,见UNP
以上五种都是阻塞式网络编程,一般程序都会阻塞在read上,解决方法有两种
- 用两个线程/进程,一个用来读一个用来写(分离IO)
- I/O复用(这里涉及两个问题,非阻塞IO和应用层buffer)
5.基本的单线程Rector
I/O和事件处理都在同一个线程中完成,这个线程做着 POLL->READ ACTIVE CHANNEL->DEAL CHANNEL->POLL 的循环,与普通multiplexing的区别在于把业务逻辑部分分离出来。
6.Rector+thread per task
在Rector的基础上,收到请求后创建一个新的线程去计算,这样分离了I/O线程和工作线程,但还是有创建和销毁线程所造成的额外开销,而且不保证返回请求的顺序(比如同一个连接中有多个请求的情况)。
7.Rector+work thread
在6的基础上改为分配一个线程去处理一个连接上的所有请求,这样可以保证不会乱序返回。
8.Rector+Thread pool
引入线程池解决线程创建个销毁的开销。不过这种方法对I/O突发的适应力不强,一个Rector可能处理不过来。
9.Main Rector+Sub Rector
每个Sub Rcetor上都执行5中的操作,但是对突发计算的适应力不强,本质上类似于单线程,因为一个连接由一个Sub Rector全权处理,单个连接上的计算任务并没有做到并发处理。
10.one loop per thread + thread pool
8,9的结合,Main Rector 负责接收新连接,并且分发给Sub Rector 处理,Sub Rector们共用Thread Pool,这样对于突发I/O和突发计算都有一定的适应能力。