5种服务器网络编程模型讲解
1.同步阻塞迭代模型
同步阻塞迭代模型是最简单的一种IO模型。
其核心代码如下:
上面的程序存在如下一些弊端:
1)如果没有客户端的连接请求,进程会阻塞在accept系统调用处,程序不能执行其他任何操作。(系统调用使得程序从用户态陷入内核态,具体请参考:程序员的自我修养)
2)在与客户端建立好一条链路后,通过read系统调用从客户端接受数据,而客户端合适发送数据过来是不可控的。如果客户端迟迟不发生数据过来,则程序同样会阻塞在read调用,此时,如果另外的客户端来尝试连接时,都会失败。
3)同样的道理,write系统调用也会使得程序出现阻塞(例如:客户端接受数据异常缓慢,导致写缓冲区满,数据迟迟发送不出)。
2.多进程并发模型
同步阻塞迭代模型有诸多缺点。多进程并发模型在同步阻塞迭代模型的基础上进行了一些改进,以避免是程序阻塞在read系统调用上。
多进程模型核心代码如下:
上述程序在accept系统调用时,如果没有客户端来建立连接,择会阻塞在accept处。一旦某个客户端连接建立起来,则立即开启一个新的进程来处理与这个客户的数据交互。避免程序阻塞在read调用,而影响其他客户端的连接。
3.多线程并发模型
在多进程并发模型中,每一个客户端连接开启fork一个进程,虽然linux中引入了写实拷贝机制,大大降低了fork一个子进程的消耗,但若客户端连接较大,则系统依然将不堪负重。通过多线程(或线程池)并发模型,可以在一定程度上改善这一问题。
在服务端的线程模型实现方式一般有三种:
(1)按需生成(来一个连接生成一个线程)
(2)线程池(预先生成很多线程)
(3)Leader follower(LF)
为简单起见,以第一种为例,其核心代码如下:
服务端分为主线程和工作线程,主线程负责accept()连接,而工作线程负责处理业务逻辑和流的读取等。因此,即使在工作线程阻塞的情况下,也只是阻塞在线程范围内,对继续接受新的客户端连接不会有影响。
第二种实现方式,通过线程池的引入可以避免频繁的创建、销毁线程,能在很大程序上提升性能。但不管如何实现,多线程模型先天具有如下缺点:
1)稳定性相对较差。一个线程的崩溃会导致整个程序崩溃。
2)临界资源的访问控制,在加大程序复杂性的同时,锁机制的引入会是严重降低程序的性能。性能上可能会出现“辛辛苦苦好几年,一夜回到解放前”的情况。
4.IO多路复用模型之select/poll
多进程模型和多线程(线程池)模型每个进程/线程只能处理一路IO,在服务器并发数较高的情况下,过多的进程/线程会使得服务器性能下降。而通过多路IO复用,能使得一个进程同时处理多路IO,提升服务器吞吐量。
在Linux支持epoll模型之前,都使用select/poll模型来实现IO多路复用。
以select为例,其核心代码如下:
select IO多路复用同样存在一些缺点,罗列如下:
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE 1024)
 - 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
 - select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
 - select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。
 
相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。
拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。
5.IO多路复用模型之epoll
epoll IO多路复用:一个看起来很美好的解决方案。 由于文章:高并发网络编程之epoll详解中对epoll相关实现已经有详细解决,这里就直接摘录过来。
由于epoll的实现机制与select/poll机制完全不同,上面所说的 select的缺点在epoll上不复存在。
设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?
在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:
1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3)调用epoll_wait收集发生的事件的连接
如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
下面来看看Linux内核具体的epoll机制实现思路。
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。
而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

epoll数据结构示意图
从上面的讲解可知:通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。
OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。
第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。
第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。
第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。
最后,附上一个epoll编程实例。(此代码作者为sparkliang)
| 
 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187 
188 
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 
200 
201 
202 
203 
204 
205 
206 
207 
208 
209 
210 
211 
212 
213 
214 
215 
216 
217 
218 
219 
220 
221 
222 
223 
224 
225 
226 
227 
228 
229 
230 
231 
232 
233 
234 
235 
236 
237 
238 
239 
240 
 | 
 
//    
// a simple echo server using epoll
 in linux   
//    
// 2009-11-05   
// 2013-03-22:修改了几个问题,1是/n格式问题,2是去掉了原代码不小心加上的ET模式; 
// 本来只是简单的示意程序,决定还是加上 recv/send时的buffer偏移 
// by sparkling   
//    
#include <sys/socket.h>   
#include <sys/epoll.h>   
#include <netinet/in.h>   
#include <arpa/inet.h>   
#include <fcntl.h>   
#include <unistd.h>   
#include <stdio.h>   
#include <errno.h>   
#include <iostream>   
using
namespace
std;
  
#define MAX_EVENTS 500   
struct
myevent_s
  
{
  
   
int
fd;
  
   
void
(*call_back)(int
fd,
int
events,
void
*arg);
  
   
int
events;
  
   
void
*arg;
  
   
int
status;
// 1: in epoll wait list, 0 not
 in   
   
char
buff[128];
// recv data buffer   
   
int
len,
s_offset;
  
   
long
last_active;
// last active time   
};
  
// set event   
void
EventSet(myevent_s
*ev,
int
fd,
void
(*call_back)(int,
int,
void*),
void
*arg)
  
{
  
   
ev->fd
=
fd;
  
   
ev->call_back
=
call_back;
  
   
ev->events
=
0;
  
   
ev->arg
=
arg;
  
   
ev->status
=
0; 
   
bzero(ev->buff,
sizeof(ev->buff)); 
   
ev->s_offset
=
0;
  
   
ev->len
=
0; 
   
ev->last_active
=
time(NULL);
  
}
  
// add/mod an event to epoll   
void
EventAdd(int
epollFd,
int
events,
myevent_s
*ev)
  
{
  
   
struct
epoll_event
epv
=
{0,
{0}};
  
   
int
op;
  
   
epv.data.ptr
=
ev;
  
   
epv.events
=
ev->events
=
events;
  
   
if(ev->status
==
1){
  
       
op
=
EPOLL_CTL_MOD;
  
   
}
  
   
else{
  
       
op
=
EPOLL_CTL_ADD;
  
       
ev->status
=
1;
  
   
}
  
   
if(epoll_ctl(epollFd,
op,
ev->fd,
&epv)
<
0)
  
       
printf("Event
 Add failed[fd=%d], evnets[%d]\n",
ev->fd,
events);
  
   
else
  
       
printf("Event
 Add OK[fd=%d], op=%d, evnets[%0X]\n",
ev->fd,
op,
events);
  
}
  
// delete an event from epoll   
void
EventDel(int
epollFd,
myevent_s
*ev)
  
{
  
   
struct
epoll_event
epv
=
{0,
{0}};
  
   
if(ev->status
!=
1)
return;
  
   
epv.data.ptr
=
ev;
  
   
ev->status
=
0; 
   
epoll_ctl(epollFd,
EPOLL_CTL_DEL,
ev->fd,
&epv);
  
}
  
int
g_epollFd;
  
myevent_s
g_Events[MAX_EVENTS+1];
// g_Events[MAX_EVENTS] is used
 by listen fd   
void
RecvData(int
fd,
int
events,
void
*arg);
  
void
SendData(int
fd,
int
events,
void
*arg);
  
// accept new connections from clients
   
void
AcceptConn(int
fd,
int
events,
void
*arg)
  
{
  
   
struct
sockaddr_in
sin;
  
   
socklen_t
len
=
sizeof(struct
sockaddr_in);
  
   
int
nfd,
i;
  
   
// accept   
   
if((nfd
=
accept(fd,
(struct
sockaddr*)&sin,
&len))
==
-1)
  
   
{
  
       
if(errno
!=
EAGAIN
&&
errno
!=
EINTR)
  
       
{
  
       
} 
       
printf("%s:
 accept, %d",
__func__,
errno);
  
       
return;
  
   
}
  
   
do
  
   
{
  
       
for(i
=
0;
i
<
MAX_EVENTS;
i++)
  
       
{
  
           
if(g_Events[i].status
==
0)
  
           
{
  
               
break;
  
           
}
  
       
}
  
       
if(i
==
MAX_EVENTS)
  
       
{
  
           
printf("%s:max
 connection limit[%d].",
__func__,
MAX_EVENTS);
  
           
break;
  
       
}
  
       
// set nonblocking 
       
int
iret
=
0; 
       
if((iret
=
fcntl(nfd,
F_SETFL,
O_NONBLOCK))
<
0) 
       
{ 
           
printf("%s:
 fcntl nonblocking failed:%d",
__func__,
iret); 
           
break; 
       
} 
       
// add a read event for receive
 data   
       
EventSet(&g_Events[i],
nfd,
RecvData,
&g_Events[i]);
  
       
EventAdd(g_epollFd,
EPOLLIN,
&g_Events[i]);
  
   
}while(0);
  
   
printf("new
 conn[%s:%d][time:%d], pos[%d]\n",
inet_ntoa(sin.sin_addr),
 
           
ntohs(sin.sin_port),
g_Events[i].last_active,
i);
  
}
  
// receive data   
void
RecvData(int
fd,
int
events,
void
*arg)
  
{
  
   
struct
myevent_s
*ev
=
(struct
myevent_s*)arg;
  
   
int
len;
  
   
// receive data 
   
len
=
recv(fd,
ev->buff+ev->len,
sizeof(ev->buff)-1-ev->len,
0);  
  
   
EventDel(g_epollFd,
ev); 
   
if(len
>
0) 
   
{ 
       
ev->len
+=
len; 
       
ev->buff[len]
=
'\0';
  
       
printf("C[%d]:%s\n",
fd,
ev->buff);
  
       
// change to send event   
       
EventSet(ev,
fd,
SendData,
ev);
  
       
EventAdd(g_epollFd,
EPOLLOUT,
ev);
  
   
}
  
   
else
if(len
==
0)
  
   
{
  
       
close(ev->fd);
  
       
printf("[fd=%d]
 pos[%d], closed gracefully.\n",
fd,
ev-g_Events);
  
   
}
  
   
else
  
   
{
  
       
close(ev->fd);
  
       
printf("recv[fd=%d]
 error[%d]:%s\n",
fd,
errno,
strerror(errno));
  
   
}
  
}
  
// send data   
void
SendData(int
fd,
int
events,
void
*arg)
  
{
  
   
struct
myevent_s
*ev
=
(struct
myevent_s*)arg;
  
   
int
len;
  
   
// send data   
   
len
=
send(fd,
ev->buff
+
ev->s_offset,
ev->len
-
ev->s_offset,
0); 
   
if(len
>
0)
  
   
{ 
       
printf("send[fd=%d],
 [%d<->%d]%s\n",
fd,
len,
ev->len,
ev->buff); 
       
ev->s_offset
+=
len; 
       
if(ev->s_offset
==
ev->len) 
       
{ 
           
// change to receive event 
           
EventDel(g_epollFd,
ev);
  
           
EventSet(ev,
fd,
RecvData,
ev);
  
           
EventAdd(g_epollFd,
EPOLLIN,
ev);
  
       
} 
   
}
  
   
else
  
   
{
  
       
close(ev->fd);
  
       
EventDel(g_epollFd,
ev);
  
       
printf("send[fd=%d]
 error[%d]\n",
fd,
errno);
  
   
}
  
}
  
void
InitListenSocket(int
epollFd,
short
port)
  
{
  
   
int
listenFd
=
socket(AF_INET,
SOCK_STREAM,
0);
  
   
fcntl(listenFd,
F_SETFL,
O_NONBLOCK);
// set non-blocking   
   
printf("server
 listen fd=%d\n",
listenFd);
  
   
EventSet(&g_Events[MAX_EVENTS],
listenFd,
AcceptConn,
&g_Events[MAX_EVENTS]);
  
   
// add listen socket   
   
EventAdd(epollFd,
EPOLLIN,
&g_Events[MAX_EVENTS]);
  
   
// bind & listen   
   
sockaddr_in
sin;
  
   
bzero(&sin,
sizeof(sin));
  
   
sin.sin_family
=
AF_INET;
  
   
sin.sin_addr.s_addr
=
INADDR_ANY;
  
   
sin.sin_port
=
htons(port);
  
   
bind(listenFd,
(const
sockaddr*)&sin,
sizeof(sin));
  
   
listen(listenFd,
5);
  
}
  
int
main(int
argc,
char
**argv)
  
{
  
   
unsigned
short
port
=
12345;
// default port   
   
if(argc
==
2){
  
       
port
=
atoi(argv[1]);
  
   
}
  
   
// create epoll   
   
g_epollFd
=
epoll_create(MAX_EVENTS);
  
   
if(g_epollFd
<=
0)
printf("create
 epoll failed.%d\n",
g_epollFd);
  
   
// create & bind listen socket,
 and add to epoll, set non-blocking   
   
InitListenSocket(g_epollFd,
port);
  
   
// event loop   
   
struct
epoll_event
events[MAX_EVENTS];
  
   
printf("server
 running:port[%d]\n",
port);
  
   
int
checkPos
=
0;
  
   
while(1){
  
       
// a simple timeout check here,
 every time 100, better to use a mini-heap, and add timer event   
       
long
now
=
time(NULL);
  
       
for(int
i
=
0;
i
<
100;
i++,
checkPos++)
// doesn't check listen fd   
       
{
  
           
if(checkPos
==
MAX_EVENTS)
checkPos
=
0;
// recycle   
           
if(g_Events[checkPos].status
!=
1)
continue;
  
           
long
duration
=
now
-
g_Events[checkPos].last_active;
  
           
if(duration
>=
60)
// 60s timeout   
           
{
  
               
close(g_Events[checkPos].fd);
  
               
printf("[fd=%d]
 timeout[%d--%d].\n",
g_Events[checkPos].fd,
g_Events[checkPos].last_active,
now);
  
               
EventDel(g_epollFd,
&g_Events[checkPos]);
  
           
}
  
       
}
  
       
// wait for events to happen
   
       
int
fds
=
epoll_wait(g_epollFd,
events,
MAX_EVENTS,
1000);
  
       
if(fds
<
0){
  
           
printf("epoll_wait
 error, exit\n");
  
           
break;
  
       
}
  
       
for(int
i
=
0;
i
<
fds;
i++){
  
           
myevent_s
*ev
=
(struct
myevent_s*)events[i].data.ptr;
  
           
if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN))
// read event   
           
{
  
               
ev->call_back(ev->fd,
events[i].events,
ev->arg);
  
           
}
  
           
if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT))
// write event   
           
{
  
               
ev->call_back(ev->fd,
events[i].events,
ev->arg);
  
           
}
  
       
}
  
   
}
  
   
// free resource   
   
return
0;
  
} 
  
 | 
                    
                
                
            
        
浙公网安备 33010602011771号