网络编程中io职责及Redis、memcached、Nginx、如何处理reactor

在网络编程中,不论使用什么框架,我们都需要关注以下四个问题:连接建立、连接断开、消息到达、消息发送。
网络io职责有操作io和检测io,我们将通过操作io这个过程去解答网络编程需要关注的问题。
io的操作方式可见网络编程中网络IO与select、poll、epoll原理及使用

1.非阻塞io对于以上问题的处理

连接建立:server调用connect函数去接受client请求(被动)或者server作为客户端去请求数据库(主动),在主动建立中,会多次调用connect函数,第一次connect返回值肯定为-1,此时errno返回的是EINPROGRESS表示正在建立中,连接建立成功后,errno返回EISCONN,连接建立成功。在被动建立发生,server需要首先进行sockert、bind、listen等操作,之后等待client连接,三次握手成功后在accept去全连接队列检测就绪的fd,accept取出clientfd,至此连接建立成功。

连接断开:同样分为主动和被动,主动断开分为close和shutdown,close是全部关闭,shutdown是关闭读端或者写端,是tcp通信由全双工变为半双工;被动断开是指client断开连接了server如何去处理,当调用read时等于0,说明是server的读端关闭了,当调用write时候等于-1同时errno = EPIPE,则说明server的写端关闭了,这样在四次挥手时候,读端虽然关闭了server仍然可以将数据写发送,之后再close。

数据到达:用户态调用read去检测内核空间的read_buffer,若返回-1,errno此时为EWOULDBLOCK,表明此时read_buffer没有数据可读,errno若为EINTR表明调用被其他打断。返回值>0,调用成功。

数据发送:server调用write,与数据到达类似。

2.单检测io框架对于以上问题的处理

类似epoll这样的io多路复用框架,只检测io状态和不操作io。这种模式下开头的问题处理流程如下:

连接建立:
接收连接:server调用socket、bind、listen创建listenfd,使用io多路复用去监听listenfd的读事件,连接建立成功的时候全连接队列会产生一个节点,发送信号告诉epoll(其他框架相同)之后epoll就会触发对应的流程。
主动连接:此时server相当于客户端,tcp三次握手中io多路复用去监听最后一次写事件(ack包发送成功)则代表连接建立成功。

连接断开
被动断开:以epoll为例,epoll错误码errno为EPOLLRDHUP,说明服务端的读端已经关闭了,errno == EPOLLHUP说明读写段全部关闭,

消息到达
epoll监听fd的读事件,如果read_buffer有数据就会告知epoll触发EPOLLIN事件,之后进行read的io操作

消息发送
epoll监听fd的写事件,如果write_buffer有空间,则可写,之后进行write的操作。

3.io多路复用—epoll为例

epoll上图为epoll的原理结构,其有三个系统调用,epoll_create在linux内核协议栈创建两个数据结构:红黑树、双端队列。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epfd是epoll_create创建的,op是对红黑树具体的操作,EPOLL_CTL_ADD 、EPOLL_CTL_MOD 、EPOLL_CTL_DEL 分别为红黑树添加、修改、删除节点,当进行添加节点时候,epoll_ctl会跟网卡驱动建立一个回调关系(ep_poll_callback),将节点数据拷贝到双端队列中,epoll时间复杂度降为.O(1)原因是epoll_wait操作的双端队列复杂度为.O(1)

int epoll_wait( int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll_wait作用是将双端队列数据拷贝到用户态中,其第二个参数是需要填充用户态数组,用来接收内核空间就绪队列的东西,第三个参数maxevents是预期拷贝的事件,epoll_wait返回值是实际拷贝的事件数量,第四个参数timeout:epoll_wait没有阻塞和非阻塞的状态,但timeout取-1表现出来的就是阻塞的特性,只有就绪队列有事件才去返回,就绪队列没有事件会一直阻塞在epoll_wait中。timeout取0,表现出来就是非阻塞特性,就绪队列没时间则返回0。timeout取1000,则在epoll_wait阻塞1S中,没有事件则返回0.

4.reactor(one eventloop per thread)原理

reactor是将网络编程中对io的操作转化为对事件的处理,reactoe包括io多路复用和非阻塞io,io多路复用用于检测io,非阻塞io只操作io,
rector为什么使用非阻塞io?
多线程环境下,将一个listenfd放到多个epoll(多个线程)中去检测,对于全连接队列,一个listenfd到达,其中一个抢到了,如果使用阻塞io那么其他thread就会一直等待,
边缘触发下,假设read_buffer进来500个数据,读事件调用read读了300,则read_buffer中剩下的200个只能等下一次客户端过来数据触发读事件才会被读走,一般会循环将read_buffer读空,但read并不知道read_buffer实际有多少,如果采用阻塞io那么会一直停留在read这里。
io多路复用选用select时:
当某个socket接收缓冲区有新数据分节到达,然后select报告这个socket描述符可读,但随后,协议栈检查到这个新分节检验和错误,然后丢弃这个分节,这时候调用read则无数据可读,如果socket没有被设置nonblocking,此read将阻塞当前线程。

4.1单reactor应用——Redis

Redis使用单线程实现业务逻辑是因为其key-value结构的value支持多种数据结构,操作具体命令时间复杂度低,Redis使用单reactor处理流程是创建listenfd、bind listen、listenfd注册读事件、读事件触发回调accept、clientfd注册读事件、读事件触发,之后进行read,通过decode解协议,通过compute执行解出来的具体命令,之后进行encode压协议,传给write。Redis针对reactor进行了优化将图中的read和decode放到io线程处理,write和encode放进io线程处理。
在这里插入图片描述

4.2 多reactor应用——memcached

memcached也是key-value的内存数据库,因为其value数据结构是单一的,使用多线程处理效率非常高。memcached处理reactor的流程是主线程中有一个reactor,作用是负责接收连接,并将这些连接通过pipe负载均衡的加入到多个reactor线程中,在每一个reactor线程中会处理不同的memcached命令的业务逻辑。
在这里插入图片描述

4.3单reactor另一种优化——Nginx

在master进行监听绑定窗口,通过fork进程方式fork出多个进程,每一个worker中都有一个epoll对象,存在一个listenfd有多个worker要去管理它,这样就存在一个惊群的问题,要如何解决惊群问题呢?这里多个worker会有一个共享内存,将锁放进共享内存中,多个worker去争夺这把锁,谁抢到了就去处理这个listenfd。在用户层做一个负载均衡,当一个worker接收到一定限度后就会关闭接收窗口。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值