一. 网络编程应该关注的问题
- 连接建立
- 连接断开
- 消息到达
- 消息发送
二. 网络IO的职责
2.1 操作 IO
只能使用 io 函数来进行操作;分为两种操作方式:阻塞 io 和非阻塞 io;
2.1.1 操作方式
阻塞和非阻塞
差异:在数据未就绪时是否返回
连接建立:
listen():在 listen() 函数中,第二个参数指定了已连接但未接受(accept)的队列的最大长度。这个参数告诉操作系统在拒绝新的连接之前,能够接受多少个连接。
accept(): 非阻塞下,如果没有客户端与服务端建立连接,那么accept会返回-1。
连接断开:
主动
close(); shutdown()
客户端shutdown关闭了写端,那么服务端就是读端关闭了。
被动
0 = read(): 服务端的读端关闭了
-1 = write() && errno = EPIPE :服务端的写端关闭了
连接到达
read:
read = -1 返回EWOULDBLOCK : 读缓冲区没有数据。
read = -1 返回EINTR : 被中断打断了
消息发送:
write:
write= -1 返回EWOULDBLOCK : 写缓冲区满了数据。
write = -1 返回EINTR : 被中断打断了
检测 IO
io 函数本身可以检测 io 的状态;但是只能检测一个 fd 对应的状态;io 多路复用可以同时检测多个 io 的状态;区别是:io函
数可以检测具体状态;io 多路复用只能检测出可读、可写、错误、断开等笼统的事件;
检测IO就是检测IO的就绪状态,也就是 IO多路复用(检测多个连接的就绪状态)
主要介绍epoll
- epoll_create() 会在内核创建一个红黑树和一个双端list等
- epoll_ctl() 注册事件,当添加一个事件时候,和网卡的驱动建立回调关系,相应事件被触发,那么就调用回调函数,将触发的事件拷贝到rdlist双向链表中
- epoll_wait() 会把rdlist中的就绪事件拷贝出来到用户态。epoll_wait的第四个参数是超时时间,如果设置0,epoll_wait 函数将立即返回,不会等待任何事件。这意味着它会立即轮询已注册的文件描述符,查看是否有就绪的事件,如果没有立即就绪的事件,它将返回并允许程序继续执行,而不会阻塞等待。设置为-1会一直阻塞等待,设置为正数表示阻塞等待这样一段时间,再返回。
reactor
IO多路复用 + 非阻塞IO
由IO多路复用来检测IO,使用非阻塞IO操作IO.
- reactor为什么搭配非阻塞IO?
既然IO多路复用已经帮我们检测数据可用的,那为什么还要使用非阻塞IO呢,阻塞的不是一样吗?
原因:
多线程环境中, listedfd通常用于去取建立连接的连接,如果同时放到多个epoll去管理,当TCP三次握手建立完成后,这个新的连接放入到内核里面的全连接队列,他会发送信号告诉这多个epoll有新的连接建立, 如果此时在某一个线程里面已经accept出来了,如果此时使用的是阻塞IO,其他的线程就会阻塞在此处,进而无法执行。
边缘出发下,必须使用非阻塞IO,边缘触发的话,在一次事件循环要把readbuffer读空,如果不读空,只有在下次事件触发才能读取剩下的。如果此时readbuffer已经为空了,但是我们使用的是阻塞IO的话,此时程序就会被阻塞。综上所述,边缘触发无论是不是使用reactoer模型,必须使用非阻塞IO
select bug:当某个socket的接收缓冲区有新数据分节到达,然后select 会报告这个socket描述符可读,但是,协议栈检验时,将数据丢弃了,那么此时调用read将无数据可读,如果此时socket设置为阻塞,则会阻塞线程。
2 是不是IO多路复用都要搭配非阻塞IO?
三.单reactor/多reactor的一些开源项目分析理解
redis -> 单reactor
- kv,v支持多种数据结构,在内存中操作数据
- 命令处理是单线程的
redis为什么使用单reactor?
命令处理是单线程的,核心业务逻辑是单线程的;操作具体命令时间复杂度低
redis怎么处理reactor?
…
redis做了哪些优化?
使用了多线程:read+decode;encode + write 抛到IO线程去处理。
Memcached
- KV ,内存数据库(相对简单的KV)
- 命令处理是多线程的
怎么处理reactor?
主reactor负责接受连接,子reactor的个数由CPU核心的多少决定;然后通过管道pipe的方式, 。
Memcached通常采用单个事件处理器(Reactor)来管理连接和事件,同时使用多个工作线程来处理实际的I/O操作和数据处理。这种模型有助于处理大量并发连接,提高系统的性能和吞吐量。主Reactor负责监听和分派事件,工作线程负责处理实际的I/O操作,这种设计可以更好地利用多核系统的资源,并提高整个系统的并发处理能力