课程目标:
1.网络模块要处理哪些事情
2.reactor是怎么处理这些事情的
3.reactor怎么封装
4.网络模块与业务逻辑的关系
5.怎么优化reactor?
io函数 函数调用 都有两个作用:io检测 是否就绪 io操作
1. int clientfd = accept(listenfd, &addr, &len); 检测 全连接队列是否有数据
2. int n = read(clientfd, buf, sz);
3. int n = write(clientfd, buf, sz);
read就是将东西从read buffer(读缓冲区)中读出。write就是将东西从write buffer(写缓冲区)中写入。
对于客户端而言,怎么知道链接建立成功,主要通过connect返回值(三次握手时是否收到服务端的ack)
上面函数都是同步io。对于select\poll\epoll都是同步io,reactor是事件驱动。
io多路复用就是检测io是否就绪。但是他不进行io操作。就是就绪了就来通知你
reactor就是把对io的操作转化成对事件的处理。所以在reactor中io是同步的,事件是异步的。异步io是用的io-using
reactor就是用io多路复用来同时检测多个io是否就绪,io操作由io函数来做。这是因为在我们服务器在同一时刻只有少量的客户端是跟服务器有交互的。所以交互就由io函数来做。
为什么reactor把io就绪检测的功能丢给io多路复用来做而不是让 io函数自己检测?
因为如果是的话
阻塞的io:每个连接都需要一个线程
非阻塞io:while在应用层检测,就是每一时刻都检测。
在面试的时候问什么是reactor,经常跟io多路复用搞混。注意io多路复用只负责检测io是否就绪。reactor也要处理io。怎么操作io呢:
for(int i=0;i<n;i++){
epoll_event ev=evs[i];
if(ev&epollin){
callback();
}
}
io多路复用怎么检测链接
TCP是全双工的,这也是跟UDP的一个区别。就是对于一端既可以去读,也可以去写。
这个EPOLLRDHUP就是读端关闭,EPOOLLHOP就是都关闭了。
对于断开连接四次挥手,就是将一端进行关闭:
reactor步骤
对于reactor,我们用io多路复用进行事件是否就绪的监听,具体事件处理用io函数:这里epoll_wait就是io多路复用,他负责检测io是否就绪。
reactor步骤:
reactor把io的处理转化为对事件的处理:
1.注册io 就绪的事件 - 注册io多路复用中事件。包含callback在callback中操作具体 io,这个callback就是回调函数。就是上面读写事件。
2. epoll_wait收集事件,处理事件事件循环
构成部分:
1.事件封装callback
2.事件的注册、注销
3.事件循环
one eventloop per thread
一个线程最多只有一个epoll对象
reactor为什么搭配非阻塞IO?
1.多线程环境 将一个listenfd放到多个epoll去处理,会出现问题。当连接到来时,多个epoll都会被触发,但只有一个线程的accpet会返回,其他线程如果使用阻塞IO,则会一直阻塞(因为事件被其他线程处理了)。
2.边缘触发下 读事件触发时,read循环把read buffer读空。如果使用的是阻塞IO,当read buffer为空后,会一直阻塞。
3. select bug 当一个数据到达时,select将会报告读事件,但是可能这个数据没有通过校验和检测所以丢弃了,而select已经上报读事件了,如果此时用阻塞的io read去读将会阻塞线程!
所以reactor,由io多路复用共和非阻塞io组成,io多路复用来检测io,非阻塞io来操作io。
是不是IO多路复用一定要搭配非阻塞IO?
不是!
比如MySQL,使用select接收连接,每条连接一个线程,阻塞只会阻塞这条连接的线程
在比如libevent,可以加一个系统调用 看缓存区中有多少数据(相当于一个检测的作用),但是效率比较低
int n = EVBUFFER_MAX_READ_DEFAULT;
if (ioctl(fd, FIONREAD, &n) < 0)
return -1;
return n;
网络模块与业务逻辑的关系
怎么优化reactor?
redis 单reactor,nginx,memcached都是多reactor,nginx是使用多进程,而memcached是使用多线程.
redis
下面是redis:
因为redis是单reactor,所以如果同时三个client来的话,他底层是一个一个处理的,如果对于第一个处理io和计算过程很慢的话,后面就得一直等着。所以就将io操作用线程池操作。计算则采用不用算法优化。
redis是操作数据的,为了不加锁所以才单进程。
nginx
1.创建listenfd bind监听
2. worker 进程都有listenfd
3. worker进程争夺accept 锁
4. 谁争夺到了锁,谁就有接收新连接的权利
5. 接收的连接放入该worker
memcached
总结:
reactor要处理多个客户端,所以将多个客户端的io检测部分放到io多路复用中去。如果由就绪的了,就以事件的方式通知用不同的回调函数来处理事件。