redis、nginx、memcached等网络编程模型详解
suiw9 2024-12-17 16:12 100 浏览 0 评论
说到网络编程,就要把下面四个方面处理好。
一、网络连接
分为两种:服务端处理接收客户端的连接,服务端作为客户端连接第三方服务
来自客户端的连接,监听accept有收到EPOLLIN事件,或者当前服务器连接上游服务器,进行connect时返回-1,errno为EINPROGRESS,此时再收到EPOLLOUT事件就代表连接上了,因为三次握手最后是需要回复ack给上游服务器。(Connect非阻塞 ,在图中箭头出表示建立成功(需要注册写事件:最终客户端还要给服务器发送个ack确认请求才能建立成功))
int clientfd=accept(listenfd, addr, sz);
//举例为非阻塞io, 阻塞io成功直接返回0
int connectfd=socket(AF_INET,SOCK_STREAM,0);
int ret=connect(connectfd,(struct sockaddr *)&addr,sizeof(addr));
// ret==-1 && errno==EINPROGERESS 正在建立连接
// ret==-1 && errno==EISCONN 连接建立成功
二、 网络断开
当客户端断开时,服务端read返回0,或者收到EPOLLRDHUP事件,如果服务端要支持半关闭状态,就关闭读端shutdown(SHUT_RD),如果不需要支持,直接close即可,大部分都是直接close,一般close前也会进行类似释放资源的操作,如果这步操作比较耗时,可以异步处理,否则导致服务端出现大量的close_wait状态。
如果是服务端主动断开连接时,通过shutdown(SHUT_WR)发送FIN包给客户端,**此时再调用write会返回-1,errno为EPIPE,代表写通道已经关闭。**这里要注意close和shutdown的区别,close时如果fd的引用不为0,是不会真正的释放资源的,比如fd1=dup(fd2),close(fd2)不会对fd1造成影响,而shutdown则跳过了前面的引用计数检查,直接对网络进行操作,多线程多进程下用close比较好。断开连接时,如果发现接收缓冲区还有数据,直接丢弃,并回复RST包,如果是发送缓冲区还有数据,则会取消nagle进行发送,末尾加上FIN包,如果开启了SO_LINGER,则会在linger_time内等待FIN包的ack,这样保证发送缓冲区的数据被对端接收到。
//主动关闭
close(fd);
shutdown(fd,SHUT_RDWR);
//主动关闭本地读端,对端写段关闭
shutdown(fd,SHUT_RD);
//主动关闭本地写端,对端读端关闭
shutdown(fd,SHUT_WR);
//被动:读端关闭
//有的网络编程需要支持半关闭状态
int n=read(fd,buf,sz);
if(n==0)
{
close_read(fd);
//write();
//close(fd);
}
//被动: 写端关闭
int n=write(fd,buf,sz);
if(n==-1 && errno==EPIPE)
{
close_write(fd);
//close(fd);
}
三、 消息到达
如果read大于0,接收数据正常,处理对应的业务逻辑即可,如果read等于0,说明对端发送了FIN包,如果read小于0,此时要根据errno进行下一步的判断处理,如果是EWOULDBLOCK或者EAGAIN,说明接收缓冲区还没有数据,直接重试即可,如果是EINTR,说明被信号中断了,因为信号中断的优先级比系统调用高,此时也是重试read即可(read从内核态到用户态:正向错误(被信号打断),如EWOULDBLOCK EINRT 还可以正常的读下一次,其他错误直接close),如果是ETIMEDOUT,说明探活超时了,每个socket都有一个tcp_keepalive_timer,当超过tcp_keepalive_time没有进行数据交换时,开始发送探活包,如果探测失败,间隔tcp_keepalive_intvl时间发送下一次探活包,最多连续发送tcp_keepalive_probes次,如果都失败了,则关闭连接,返回ETIMEDOUT错误。
这些探活都是在传输层进行的,如果应用层的进程有死锁或者阻塞,它是检测不到的,这种情况需要在应用层自行加入心跳包机制来进行检测。一般客户端与数据库之间,反向代理与服务器直接直接用探活机制就行,但数据库之间主从复制以及客户端与服务器之间需要加入心跳机制,以防进程有阻塞。
int n=read(fd,buf,sz);
if(n<0)
{
//n==-1
if(errno==EINTR || errno==EWOULDBLOCK)
break;
close(fd);
}
else if(n==0)
{
close(fd);
}
else
{
//处理buf
}
四、 消息发送
第四个是消息发送,write大于0,消息放入了发送缓冲区,write小于0,同样要分errno的情况处理,如果错误码为EWOULDBLOCK,说明发送缓冲区还装不下你要发送的数据,需要重试,如果是EINTR,说明write系统调用被信号中断了,同样进行重试处理,如果是EPIPE,说明写通道已经关闭了。
int n=write(fd,buf,sz);
if(n==-1)
{
if(errno==EINTR || errno==EWOULDBLOCK)
return;
close(fd);
}
常见网络io模型
阻塞io和非阻塞io指的是内核数据准备阶段要不要阻塞等待,如果内核数据准备好了,将数据从内核拷贝至用户空间还是阻塞的,所以它们都为同步io
阻塞io和非阻塞io:
(1)阻塞在网络线程
(2)连接的fd阻塞属性决定了io函数是否阻塞
(3)具体差异在:io函数在数据未到达时是否立刻返回;
//默认情况下,fd是阻塞的,设置非阻塞的方法如下:
int flag=fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_NONBLOCK);
相关视频推荐
6种epoll的做法,从redis,memcached到nginx的网络模型实现
linux多线程之epoll原理剖析与reactor原理及应用
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
reactor的应用
目前大部分高性能网络中间件都是采用io多路复用加事件处理的机制,也就是reactor模型
一、redis(单reactor)
redis是单reactor模型,只有一个epoll对象,主线程就是一个循环,不断的处理epoll事件, 首先处理accept事件,将接入的连接绑定读事件处理函数后加入epoll中,等epoll检测到连接有读事件到来时,触发读事件处理函数,但这个函数并没有真正的去读数据,而是将该有读事件到来的连接放入clients_pending_read任务队列中,主线程循环到下一次epoll_wait前,再将这些任务队列中的连接分配给各个io线程本地的任务队列io_threads_list处理,在io线程里,不断对io_threads_pending[i]原子变量进行判断,看有没有值,有就代表主线程给派了任务,然后根据io_threads_op任务类型对任务进行读或写处理,先说读处理部分,主要读取客户端数据并进行解析命令处理,将命令读到该连接对应的querybuf中,主线程忙轮询,等待所有io线程解析命令完成,再主线程开始执行命令,因为这些都是内存操作,所以单线程就可以,如果用多线程的话还有锁的问题,执行完命令后将相应结果写入连接对应的buf数组中,如果放不下就放入reply链表中,然后再将各个连接的响应客户端的任务放入clients_pending_write任务队列中,主线程再分配给各个io线程进行写处理,将数据响应给客户端,主线程此时也是忙轮询,等待所有io线程完成,如果最后发现还有数据没发送完,就注册epollout写事件sendReplyToClient,等客户端可写时再把数据发送完。可以看出网络io相关的操作使用了多线程处理,但是命令执行等纯内存操作都是单线程完成的,全程只有一个eventLoop即相当于epollfd,这种就是单reactor模型,每个io线程都有自己的任务队列io_threads_list,所以也没有多线程竞争的问题。
二、 nginx网络模型(多进程)
nginx采用的是单reactor多进程模型,因为每个连接都是处理的无状态数据,故可以通过多进程实现,多进程之间共享epollfd,在内核2.6以前,accept还存在惊群问题,即如果有连接到来,多个进程的epoll_wait都能监测到,这样多个进程都处理了该相同的连接,这是有问题的,所以nginx采用了文件锁的方式,在ngx_process_events_and_timers函数中可以看出,哪个进程先获得了这把锁ngx-accept_mutex,就开始监听EPOLLIN事件,并进行epoll_wait接收对应的事件,接收到的事件先不处理,先放入一个队列中,如果是accept事件,就放入accept对应的ngx_posted_accept_events队列中,其它事件放入另外一个ngx_posted_events队列,然后再处理accept队列的事件回调函数,到这里才能释放文件锁,再去处理非accept队列里面的事件回调函数handler。可以看出nginx其实也是只有一个epollfd,只是被多个进程共享了,这样多个进程可以并行处理事件。
三、 memcached网络模型(多线程)
memcached是基于libevent来实现网络模型的,它是多线程多reactor模型,相比前面两种,它是有多个reactot模型的,也就是说有多个epoll进行事件循环,它也是将网络接入和网络读写io单独分离的方式,主线程主要处理网络的接入,主要看server_sockets函数,有accept事件后会调用event_handler回调函数,该事件是通过调用conn_new函数注册在主线程的event_base类型变量main_base上,所以主线程陷入事件循环后会监听accept事件。该event_handler回调函数里面做的事情就是调用drive_machine,看这个名字就知道是个状态机处理,所以accept事件来时就处理conn_listening下的事情,主要是进行accept得到客户端的sfd,然后通过round_robin算法选择一个工作线程,将该fd信息打包成CQ_ITEM放在工作线程的连接事件队列ev_queue中,最后通过pipe通知那个工作队列有连接事件过来了,其实就是往pipe中发一个”c”字符,工作线程此时处理事件回调thread_libevent_process,该回调主要是从连接事件队列ev_queue中取出主线程传过来的那个item,再调用conn_new处理该item,conn_new之前主线程也是调用的这个,主要是用来绑定fd的读写事件到event_base上去,工作线程则绑定到该线程对应的那个event_base上,这个event_base对应一个epoll,所以memcached是有多个epoll,因为每个工作线程都有自己的event_base,绑定完后,后续的读写事件回调也是event_handler函数,里面再调用drive_machine函数,只是连接的状态变成了conn_read和conn_write,这就是状态机的好处,代码逻辑很清晰。总的来说,主线程处理accept,工作线程处理后续的通信read和write,思路很清晰。
相关推荐
- 看完这一篇数据仓库干货,终于搞懂什么是hive了
-
一、Hive定义Hive最早来源于FaceBook,因为FaceBook网站每天产生海量的结构化日志数据,为了对这些数据进行管理,并且因为机器学习的需求,产生了Hive这们技术,并继续发展成为一个成...
- 真正让你明白Hive参数调优系列1:控制map个数与性能调优参数
-
本系列几章系统地介绍了开发中Hive常见的用户配置属性(有时称为参数,变量或选项),并说明了哪些版本引入了哪些属性,常见有哪些属性的使用,哪些属性可以进行Hive调优,以及如何使用的问题。以及日常Hi...
- HIVE SQL基础语法(hive sql是什么)
-
引言与关系型数据库的SQL略有不同,但支持了绝大多数的语句如DDL、DML以及常见的聚合函数、连接查询、条件查询。HIVE不适合用于联机事务处理,也不提供实时查询功能。它最适合应用在基于大量不可变数据...
- [干货]Hive与Spark sql整合并测试效率
-
在目前的大数据架构中hive是用来做离线数据分析的,而在Spark1.4版本中spark加入了sparksql,我们知道spark的优势是速度快,那么到底sparksql会比hive...
- Hive 常用的函数(hive 数学函数)
-
一、Hive函数概述及分类标准概述Hive内建了不少函数,用于满足用户不同使用需求,提高SQL编写效率:...
- 数仓/数开面试题真题总结(二)(数仓面试时应该讲些什么)
-
二.Hive...
- Tomcat处理HTTP请求流程解析(tomcat 处理请求过程)
-
1、一个简单的HTTP服务器在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,那么接下来我们看下如何用Java来实现一个简单...
- Python 高级编程之网络编程 Socket(六)
-
一、概述Python网络编程是指使用Python语言编写的网络应用程序。这种编程涉及到网络通信、套接字编程、协议解析等多种方面的知识。...
- [904]ScalersTalk成长会Python小组第20周学习笔记
-
Scalers点评:在2015年,ScalersTalk成长会Python小组完成了《Python核心编程》第1轮的学习。到2016年,我们开始第二轮的学习,并且将重点放在章节的习题上。Python小...
- 「web开发」几款http请求测试工具
-
curl命令CURL(CommandLineUniformResourceLocator),是一个利用URL语法,在命令行终端下使用的网络请求工具,支持HTTP、HTTPS、FTP等协议...
- Mac 基于HTTP方式访问下载共享文件,配置共享服务器
-
方法一:使用Python的SimpleHTTPServer进行局域网文件共享Mac自带Python,所以不需要安装其他软件,一条命令即可...
- 使用curl进行http高并发访问(php curl 大量并发获得结果)
-
本文主要介绍curl异步接口的使用方式,以及获取高性能的一些思路和实践。同时假设读者已经熟悉并且使用过同步接口。1.curl接口基本介绍curl一共有三种接口:EasyInterface...
- Django 中的 HttpResponse理解和用法-基础篇1
-
思路是方向,代码是时间,知识需积累,经验需摸索。希望对大家有用,有错误还望指出。...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
MacOS + AList + 访达,让各种云盘挂载到本地(建议收藏)
-
- 最近发表
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)