linux通信模型
linux的通信模型,大致可以划分成网络IO模型和进/线程模型两大部分。
在了解这些模型前,我们先了解下一些前置知识。
- 进程的状态

- 阻塞调用与非阻塞调用
- 阻塞调用是指调用系统函数时,没得到结果的时候,进程是否会被阻塞。失效性不足。
- 非阻塞调用是指不能立刻得到结果之前,这个进程不会被阻塞,时间片内一直占用cpu资源。
- 阻塞与非阻塞这两个概念讨论的是发起系统调用的对象(例如用户态进程)。
- 进程的IO事件通常包括两个不同的阶段
UNIX下的五种IO模型
-
阻塞式I/O模型
这种IO模型,只能使用ppc或者tpc。并发连接多的时候,对系统资源占用比较多。
2. ##### 非阻塞式I/O模型
这种IO模型,有点浪费cpu资源,因为它会做大量的无用轮训。
3. ##### IO多路复用模型
这种IO模型是非阻塞同步的。select,poll,epoll就是实现这种IO多路复用思想的内核机制。
4. ##### 信号驱动式IO模型
5. ##### 异步IO模型

下面重点讲述下IO多路复用模型中的select机制和epoll的工作原理。
select
看一个小demo
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
|
<?php
//创建一个tcp socket服务器
$host = '0.0.0.0';
$port = 8888;
$tcp_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
//绑定IP端口
socket_bind($tcp_socket, $host, $port);
//启动监听
socket_listen($tcp_socket);
//select也要监听listen_socket上发生事件,简单demo只针对read事件。
$client = [ $tcp_socket ];
$write = [];
$exp = [];
// 开始死循环
while (true) {
//初始化&&更新需要监听的socket集合
$read = $client;
if (socket_select($read, $write, $exp, null) > 0) {
// 判断listen_socket有没有发生变化
if (in_array($tcp_socket, $read)) {
// 将客户端socket加入到client数组中
$client_socket = socket_accept($tcp_socket);
$client[] = $client_socket;
// 然后去重
$key = array_search($tcp_socket, $read);
unset($read[ $key ]);
}
// 查看是否有其他read事件。
if (count($read) > 0) {
foreach ($read as $socket_item) {
// 从可读取的fd中读取出来数据内容
$content = socket_read($socket_item, 2048);
// 循环client数组,将内容发送给其余所有客户端
foreach ($client as $client_socket) {
// 只发给其他客户端client。
if ($client_socket != $tcp_socket && $client_socket != $socket_item) {
socket_write($client_socket, $content, strlen($content));
}
}
}
}
}
}
|
epoll
epoll工作原理图大致如下:

其中两个函数比较关键
** ep_ptable_queue_proc **
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq; //[小节2.4.7]
if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
//初始化回调方法
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
//将ep_poll_callback放入等待队列whead
add_wait_queue(whead, &pwq->wait);
//将llink 放入epi->pwqlist的尾部
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
epi->nwait = -1; //标记错误发生
}
}
|
这个函数主要是实现关联epitem和具体的socket文件,而且注册了回调函数ep_poll_callback。
** ep_poll_callback **
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
|
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
int pwake = 0;
unsigned long flags;
struct epitem *epi = ep_item_from_wait(wait);
struct eventpoll *ep = epi->ep;
spin_lock_irqsave(&ep->lock, flags);
// 如果正在将事件传递给用户空间,我们就不能保持锁定
//(因为我们正在访问用户内存,并且因为linux f_op-> poll()语义)。
// 在那段时间内发生的所有事件都链接在ep-> ovflist中并在稍后重新排队。
if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
if (epi->next == EP_UNACTIVE_PTR) {
epi->next = ep->ovflist;
ep->ovflist = epi;
if (epi->ws) {
__pm_stay_awake(ep->ws);
}
}
goto out_unlock;
}
//如果此文件已在就绪列表中,很快就会退出
if (!ep_is_linked(&epi->rdllink)) {
//将epi就绪事件 插入到ep就绪队列
list_add_tail(&epi->rdllink, &ep->rdllist);
ep_pm_stay_awake_rcu(epi);
}
// 如果活跃,唤醒eventpoll等待队列和 ->poll()等待队列
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq); //当队列不为空,则唤醒进程
if (waitqueue_active(&ep->poll_wait))
pwake++;
out_unlock:
spin_unlock_irqrestore(&ep->lock, flags);
if (pwake)
ep_poll_safewake(&ep->poll_wait);
if ((unsigned long)key & POLLFREE) {
list_del_init(&wait->task_list); //删除相应的wait
smp_store_release(&ep_pwq_from_wait(wait)->whead, NULL);
}
return 1;
}
|
这个回调函数,主要作用于socket事件到达时触发。将该epitem存放于epoll_isntance的readylist链表中。
进程/线程模型
Reactor模式
首先我们先看一下最传统的通信模型,pps和tps。即一个连接一个线程。

这种通信形态是由于阻塞型IO的先天不足决定的。弊端显而易见,并发连接低下。
为了应对高并发业务场景,reactor就出现了。本质上,reactor模式就是IO多路复用模型加上进/线程模型的组合。解决了传统通信模型的短板。
reactor设计模式的类图大致如下

reactor模式的大致工作流程大致如下。

其中servicehandle(master进程)和Eventhandle(woker进程)可以使用不同的进程模型进行组合,
理论上有Reactor有四种形式。由于多master单worker进程模式先天不足。所以有3种典型的实现:
- 单servicehandle单线程(redis)
- 单servicehandle多线程
- 主从servicehandle多线程(nginx)