一、网络模块需要处理哪些事情
网络编程主要关注客户端与服务端交互的四个问题:
1、连接建立
2、消息到达
3、消息发送
4、连接断开
以上四个问题归结到底是网络IO,IO函数主要有两个作用:
1、检测IO是否就绪
2、进行IO操作—accept、connect、read、write
accept、connect是关于网络的连接建立。accept检测全连接队列中是否有数据,如果有则从中取出一个节点,返回一个对象clientfd,以及客户端的IP地址。connect是对于客户端而言,如果客户端收到ACK,即连接建立成功。(对应三次握手)
read是把数据从内核态read buffe拷贝到用户态中,当期为0代表read buffer中读到EOF
write是把数据从用户态拷贝到内核态write buffe中
读和写的区分:以“读书”和“写字”为例子。所谓读书:就是书籍上的文字通过人的视觉系统,经神经系统在大脑里产生主观映像;写字就是大脑里产生的主观映像通过神经系统控制运动器官——手在纸上写出字。“读”与“写”是把大脑作为中心,由外界向中心输入,就是“读”;由中心向外界输出就是“写”。现在“读”与“写”的问题就变成了输入和输出的关系了。
以电脑CPU为中心。从外界的设备(键盘,磁盘等等)向CPU传递信息就是“读操作”,由CPU向外界设备(屏幕,磁盘等等)传递信息就是“写操作”。
例如对于全双工通信的TCP协议
二、reactor网络设计模型
IO多路复用的主要功能是检测多条连接的IO是否就绪,但不具备具体IO操作的功能(比如读写数据)。常见的IO多路复用器有select、poll,epoll,他们是对IO的管理,检测接入的IO,触发IO事件;注意这三个都是同步IO。
reactor网络设计模型是把IO就绪检测的功能交由IO多路复用器实现,针对事件进行进行IO操作,不同的事件调用不同的回调函数。
int nready = epoll_wait(reactor.epfd, events, EVENTS_LEN, timeouts); int i = 0; for (i=0; i< nready; i++){ if (events[i].events & EPOLLIN){//触发读事件 listenfd --> 新连接到达--> accept clientfd --> 这条连接发送了数据 --> read }else{ //触发写事件 connectfd --> 连接建立成功,可向第三方(如数据库)发送数据 clientfd --> 这条连接写缓冲区可写 -->write } }
之所以把IO就绪检测的功能交由IO多路复用器,是因为对于服务端而言,某一时刻只有少量的连接有数据交互,如果让IO函数自己检测,在阻塞IO时,每个连接都需要一个线程;非阻塞IO时,每个连接都需要通过while在应用层进行检测。
三、网络模块与业务的关系
- 创建socket,生成listenfd,通过bind绑定,通过listen监听。
- 把listenfd注册到reactor,注册读事件(客户端与服务端建立连接)。
- 如果listenfd读事件触发,调用accept接收连接生成clientfd,然后把clientfd注册到reactor中,注册读事件(客户端向服务端发送数据)。
- 当客户端发送数据时,会触发clientfd写事件相应的回调函数。 读数据—解析(协议)数据—处理业务相关的计算—加密打包—回复数据
四、redis、memcached、nginx
1、redis
redis采用了事件驱动的模型,在启动时会创建一个I/O多路复用的事件处理器(即reactor),然后将所有客户端请求都注册到该事件处理器中。当有新连接到达或者已有连接有数据可读写时,事件处理器就会触发相应的回调函数进行处理。
由于redis是单线程运行的,因此只需要一个reactor即可满足其并发处理需求。通过合理地利用CPU资源和异步非阻塞IO技术,redis可以实现高效地处理大量并发请求。
需要注意的是,当Redis遇到某些长时间执行的命令时,可能会导致其他请求被阻塞而影响整个系统性能。比如三个客户端同时向服务端发送数据,服务端是按先后顺序接收处理。这种就会出现如果先到的数据处理比较耗时,会导致后续客户端出现饥饿现象。优化方式是:
1、如果IO耗时,把IO操作放到IO线程池处理,主线程处理computer。
2、如果computer耗时,采用分治思想或者不同时间复杂度的算法
2、memcached
memcached是一个基于事件驱动的内存缓存服务器,它使用多个reactor来处理并发连接。
具体来说,memcached使用一个主线程和多个工作线程。每个工作线程都有自己的reactor,用于处理客户端连接和请求。当有新连接到达时,主线程会将其分配给某个工作线程,并且该工作线程的reactor会负责处理这个连接的所有事件。
在默认情况下,memcached的工作线程数等于CPU核心数。
3、ngnix
Nginx是一个基于事件驱动的服务器,它通过多个reactor处理并发连接。
具体来说,Nginx使用一个主进程和多个worker进程。每个worker进程都有自己的reactor,用于处理客户端连接和请求。当有新连接到达时,主进程会将其分配给某个worker进程,并且该worker进程的reactor会负责处理这个连接的所有事件。
在默认情况下,Nginx的worker进程数等于CPU核心数。
- 创建 Listenfd bind 监听
- worker 进程都有 Listenfd
- worker 进程争夺 accept 锁
- 谁争夺到了锁,谁就有接收新连接的权利
- 接收的连接放入该 worker
4、总结
redis是单线程单reactor,可以把IO放入线程池,也可以把业务计算部分放入线程池。
如果业务之间交互比较多,但加锁简单,可以用多线程memcached
如果业务之间交互比较少,可以用多进程ngnix