课程目标:
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);
对于客户端而言,怎么知道链接建立成功,主要通过connect返回值(三次握手时是否收到服务端的ack)
对于服务端而言,当recv返回0时,说明对端关闭,实际上是在内核缓存区读到了一个EOF,自然读操作结束。
对于写操作,用户能做的只有将数据写到内核缓存区,至于数据怎么到达对端,到达与否,用户无须干预,这是协议栈做的事情。
IO多路复用只能检测多个链接,却不能操作
对于accept检测全连接队列
对于clientfd检测读缓存区,从而判断是否可读等
对于connect检测写事件,写事件触发,说明连接建立成功
int n = epoll_wait(epfd, evs, 512, timeouts);
for (int i = 0; i < n; i++) {
epoll_event ev = evs[i];
if (ev & epollin) {
listenfd 读事件 新连接到达 accept
clientfd 这条连接发送数据了 read
} else {
connectfd 写事件 连接建立成功了
clientfd 这条连接写缓存区可写了 write
}
}
1.注册io就绪的事件 注册io多路复用 事件 包含callback 在callback中操作事件io
2.epoll_wait收集事件,处理事件,事件循环
reactor构成:1.事件封装 2.事件的注册、注销 3.事件循环
reactor就是把对IO的操作转换为对事件的处理
reactor使用epoll来检测IO,而具体的IO操作还需要对应的系统调用来做!!!
如果不用epoll
的话,对于阻塞IO,每个连接都需要一个线程,对于非阻塞IO,需要我们在应用层使用while
检测。
TCP是全双工的,读端关闭后,仍然可以写数据
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去读将会阻塞线程!
是不是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是使用多线程
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:https://ke.qq.com/course/417774?flowToken=1020253