对于linux socket与epoll配合相关的一些心得记录

对于linux socket与epoll配合相关的一些心得记录

没有多少高深的东西,全当记录,虽然简单,但是没有做过测试还是挺容易让人糊涂的
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
1、通过上面语句可以简单设置缓冲区大小,测试证明:跟epoll结合的时候只有当单次发送的数据全被从缓冲区读完毕之后才会再次被触发,多次发送数据如果没有读取完毕当缓冲区未满的时候数据不会丢失,会累加到后面。
2、   如果缓冲区未满,同一连接多次发送数据会多次收到EPOLLIN事件。
单次发送数据>socket缓冲区大小的数据数据会被阻塞分次发送,所以循环接收可以用ENLIGE错误判断。
3、如果缓冲区满,新发送的数据不会触发epoll事件(也无异常),每次recv都会为缓冲区腾出空间,只有当缓冲区空闲大小能够再次接收数据epollIN事件可以再次被触发
接收时接收大小为0表示客户端断开(不可能有0数据包触发EPOLLIN),-1表示异常,针对errorno进行判断可以确定是合理异常还是需要终止的异常,>0而不等于缓冲区大小表示单次发送结束。
4、 如果中途临时调整接收缓存区大小,并且在上一次中数据没有完全接收到用户空间,数据不会丢失,会累加在一起
所以总结起来,系统对于数据的完整性还是做了相当的保正,至于稳定性没有作更深一步的测试
新增加:
5、如果主accept监听的soctet
fd也设置为非阻塞,那么单纯靠epoll事件来驱动的服务器模型会存在问题,并发压力下发现,每次accept只从系统中取得第一个,所以如果恰冯多个
连接同时触发server
fd的EPOLLIN事件,在返回的event数组中体现不出来,会出现丢失事件的现象,所以当用ab等工具简单的压载就会发现每次都会有最后几条信息得
不到处理,原因就在于此,我现在的解决办法是将server fd的监听去掉,用一个线程阻塞监听,accept成功就处理检测client
fd,然后在主线程循环监听client事件,这样epoll在边缘模式下出错的概率就小,测试表明效果明显
6、对于SIG部分信号还是要做屏蔽处理,不然对方socket中断等正常事件都会引起整个服务的退出
7、sendfile(fd, f->SL->sendBuffer.inFd, (off_t
*)&f->SL->sendBuffer.offset,
size_need);注意sendfile函数的地三个变量是传送地址,偏移量会自动增加,不需要手动再次增加,否则就会出现文件传送丢失现象
8、单线程epoll驱动模型误解:以前我一直认为单线程是无法处理web服务器这样的有严重网络延迟的服务,但nginx等优秀服务器都是机遇事件驱动
模型,开始我在些的时候也是担心这些问题,后来测试发现,当client
socket设为非阻塞模式的时候,从读取数据到解析http协议,到发送数据均在epoll的驱动下速度非常快,没有必要采用多线程,我的单核cpu
(奔三)就可以达到10000page/second,这在公网上是远远无法达到的一个数字(网络延迟更为严重),所以单线程的数据处理能力已经很高了,
就不需要多线程了,所不同的是你在架构服务器的时候需要将所有阻塞的部分拆分开来,当epoll通知你可以读取的时候,实际上部分数据已经到了
socket缓冲区,你所读取用的事件是将数据从内核空间拷贝到用户空间,同理,写也是一样的,所以epoll重要的地方就是将这两个延时的部分做了类似
的异步处理,如果不需要处理更为复杂的业务,那单线程足以满足1000M网卡的最高要求,这才是单线程的意义。

我以前构建的web服务器就没有理解epoll,采用epoll的边缘触发之后怕事件丢失,或者单线程处理阻塞,所以自己用多线程构建了一个任务调度器,
所有收到的事件统统压进任无调度器中,然后多任务处理,我还将read和write分别用两个调度器处理,并打算如果中间需要特殊的耗时的处理就增加一套
调度器,用少量线程+epoll的方法来题高性能,后来发现read和write部分调度器是多余的,epoll本来就是一个事件调度器,在后面再次缓存
事件分部处理还不如将epoll设为水平模式,所以多此一举,但是这个调度起还是有用处的

上面讲到如果中间有耗时的工作,比如数据库读写,外部资源请求(文件,socket)等这些操作就不能阻塞在主线程里面,所以我设计的这个任务调度器就有
用了,在epoll能处理的事件驱动部分就借用epoll的,中间部分采用模块化的设计,用函数指针达到面相对象语言中的“委托”的作用,就可以满足不同
的需要将任务(fd标识)加入调度器,让多线程循环执行,如果中间再次遇到阻塞就会再次加入自定义的阻塞器,检测完成就加入再次存入调度器,这样就可以将
多种复杂的任务划分开来,相当于在处理的中间环节在自己购置一个类似于epoll的事件驱动器

9、多系统兼容:我现在倒是觉得与其构建一个多操作系统都支持的服务器不如构建特定系统的,如果想迁移再次改动,因为一旦兼顾到多个系统的化会大大增加系
统的复杂度,并且不能最优性能,每个系统都有自己的独有的优化选项,所以我觉得迁移的工作量远远小于兼顾的工作量
10模块化编程,虽然用c还是要讲求一些模块化的设计的,我现在才发现几乎面相对想的语言所能实现的所有高级特性在c里面几乎都有对应的解决办法(暂时发现除了操作符重载),所有学过高级面相对象的语言的朋友不放把模式用c来实现,也是一种乐趣,便于维护和自己阅读
11、养成注释的好习惯
SO_RCVBUF SO_SNDBUF
先明确一个概念:每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态。接收缓冲区把数据缓存入内核,应用进程一直没有调用read进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。再啰嗦一点,不管进程是否读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。read所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,仅此而已。进程调用send发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。换句话说,send返回之时,数据不一定会发送到对端去(和write写文件有点类似),send仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中。后续我会专门用一篇文章介绍read和send所关联的内核动作。每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。
接收缓冲区被TCP和UDP用来缓存网络上来的数据,一直保存到应用进程读走为止。对于TCP,如果应用进程一直没有读取,buffer满了之后,发生的动作是:通知对端TCP协议中的窗口关闭。这个便是滑动窗口的实现。保证TCP套接口接收缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。 UDP:当套接口接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
以上便是TCP可靠,UDP不可靠的实现。
TCP_CORK TCP_NODELAY
这两个选项是互斥的,打开或者关闭TCP的nagle算法,下面用场景来解释
典型的webserver向客户端的应答,应用层代码实现流程粗略来说,一般如下所示:
if(条件1){
向buffer_last_modified填充协议内容“Last-Modified: Sat, 04 May 2012 05:28:58 GMT”;
send(buffer_last_modified);
}
if(条件2){
向buffer_expires填充协议内容“Expires: Mon, 14 Aug 2023 05:17:29 GMT”;
send(buffer_expires);
}
。。。
if(条件N){
向buffer_N填充协议内容“。。。”;
send(buffer_N);
}
对于这样的实现,当前的http应答在执行这段代码时,假设有M(M<=N)个条件都满足,那么会有连续的M个send调用,那是不是下层会依次向客户端发出M个TCP包呢?答案是否定的,包的数目在应用层是无法控制的,并且应用层也是不需要控制的。
我用下列四个假设场景来解释一下这个答案
由于TCP是流式的,对于TCP而言,每个TCP连接只有syn开始和fin结尾,中间发送的数据是没有边界的,多个连续的send所干的事情仅仅是:
假如socket的文件描述符被设置为阻塞方式,而且发送缓冲区还有足够空间容纳这个send所指示的应用层buffer的全部数据,那么把这些数据从应用层的buffer,拷贝到内核的发送缓冲区,然后返回。
假如socket的文件描述符被设置为阻塞方式,但是发送缓冲区没有足够空间容纳这个send所指示的应用层buffer的全部数据,那么能拷贝多少就拷贝多少,然后进程挂起,等到TCP对端的接收缓冲区有空余空间时,通过滑动窗口协议(ACK包的又一个作用----打开窗口)通知TCP本端:“亲,我已经做好准备,您现在可以继续向我发送X个字节的数据了”,然后本端的内核唤醒进程,继续向发送缓冲区拷贝剩余数据,并且内核向TCP对端发送TCP数据,如果send所指示的应用层buffer中的数据在本次仍然无法全部拷贝完,那么过程重复。。。直到所有数据全部拷贝完,返回。
请注意,对于send的行为,我用了“拷贝一次”,send和下层是否发送数据包,没有任何关系。
假如socket的文件描述符被设置为非阻塞方式,而且发送缓冲区还有足够空间容纳这个send所指示的应用层buffer的全部数据,那么把这些数据从应用层的buffer,拷贝到内核的发送缓冲区,然后返回。
假如socket的文件描述符被设置为非阻塞方式,但是发送缓冲区没有足够空间容纳这个send所指示的应用层buffer的全部数据,那么能拷贝多少就拷贝多少,然后返回拷贝的字节数。多涉及一点,返回之后有两种处理方式:
1.死循环,一直调用send,持续测试,一直到结束(基本上不会这么搞)。
2.非阻塞搭配epoll或者select,用这两种东西来测试socket是否达到可发送的活跃状态,然后调用send(高性能服务器必需的处理方式)。
综上,以及请参考本文前述的SO_RCVBUF和SO_SNDBUF,你会发现,在实际场景中,你能发出多少TCP包以及每个包承载多少数据,除了受到自身服务器配置和环境带宽影响,对端的接收状态也能影响你的发送状况。
至于为什么说“应用层也是不需要控制发送行为的”,这个说法的原因是:
软件系统分层处理、分模块处理各种软件行为,目的就是为了各司其职,分工。应用层只关心业务实现,控制业务。数据传输由专门的层面去处理,这样应用层开发的规模和复杂程度会大为降低,开发和维护成本也会相应降低。
再回到发送的话题上来:)之前说应用层无法精确控制和完全控制发送行为,那是不是就是不控制了?非也!虽然无法控制,但也要尽量控制!
如何尽量控制?现在引入本节主题----TCP_CORK和TCP_NODELAY。
cork:塞子,塞住
nodelay:不要延迟
TCP_CORK:尽量向发送缓冲区中攒数据,攒到多了再发送,这样网络的有效负载会升高。简单粗暴地解释一下这个有效负载的问题。假如每个包中只有一个字节的数据,为了发送这一个字节的数据,再给这一个字节外面包装一层厚厚的TCP包头,那网络上跑的几乎全是包头了,有效的数据只占其中很小的部分,很多访问量大的服务器,带宽可以很轻松的被这么耗尽。那么,为了让有效负载升高,我们可以通过这个选项指示TCP层,在发送的时候尽量多攒一些数据,把他们填充到一个TCP包中再发送出去。这个和提升发送效率是相互矛盾的,空间和时间总是一堆冤家!!
TCP_NODELAY:尽量不要等待,只要发送缓冲区中有数据,并且发送窗口是打开的,就尽量把数据发送到网络上去。
很明显,两个选项是互斥的。实际场景中该怎么选择这两个选项呢?再次举例说明
webserver,,下载服务器(ftp的发送文件服务器),需要带宽量比较大的服务器,用TCP_CORK。
涉及到交互的服务器,比如ftp的接收命令的服务器,必须使用TCP_NODELAY。默认是TCP_CORK。设想一下,用户每次敲几个字节的命令,而下层在攒这些数据,想等到数据量多了再发送,这样用户会等到发疯。这个糟糕的场景有个专门的词汇来形容-----粘(nian拼音二声)包。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/444899.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode144 二叉树的前序遍历

给定一个二叉树&#xff0c;返回它的 前序 遍历。 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,2,3] 进阶: 递归算法很简单&#xff0c;你可以通过迭代算法完成吗&#xff1f; 思路&#xff1a;模仿递归的思路压栈即可。 /*** Definition for a bi…

AJAX大总结

1、AJAX概述 1.1 什么是AJAX AJAX&#xff08;Asynchronous Javascript And XML&#xff09;翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互&#xff0c;传输的数据为XML&#xff08;当然&#xff0c;传输的数据不只是XML&#xff09;。 …

我对STL的一些看法(一)初步认识STL

后面一段时间我将会给大家分享我自己学到STL以及应用的时候遇到的问题还有他的一些精髓,可能开始的逻辑会有些乱吧,不过后面还会不断的整理和优化,让自己看明白也让更多的读者看的清楚。 最近刚闲下来,先说说什么是STL: 不知道你是否有过这样的经历。在大学,你准备着手完…

PaperNotes(12)-Autoregressive Quantile networks for generative modeling

Autoregressive Quantile networks for generative modeling3 autoregressive implicit quantiles3 autoregressive implicit quantiles autoregressive&#xff1a;自身做回归变量&#xff0c;用之前若干时刻的随机变量 来建模 之后某些时刻 随机变量的模型。 N维随机变量的…

我对STL的一些看法(二)认识vector容器

先说vector吧。 C++ Vector(向量容器) 是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。 vector 的数据安排以及操作方式,与 array 非常像似。两者的唯㆒差别在于空间的…

git大总结

git init 在本地新建一个repo,进入一个项目目录,执行git init,会初始化一个repo,并在当前文件夹下创建一个.git文件夹. git clone 获取一个url对应的远程Git repo, 创建一个local copy. 一般的格式是git clone [url]. clone下来的repo会以url最后一个斜线后面的名称命名,创…

我对STL的一些看法(三)认识list容器

C++ List(双向链表) 是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。由于…

C++(4)--初识变量、数据类型

C变量1.C 命名规则2.C 命名规范3.C 数据类型sizeof ()4.声明和使用变量4.1使用整型变量4.2使用单精度浮点型变量4.3使用双精度浮点型变量5.附送-cout 设置宽度&#xff0c;对齐方式6.算术运算符和表达式6.1除法、取余6.2自加、自减7.强制类型转换《老九学堂C课程》《C primer》…

我对STL的一些看法(四)认识deque容器

Deque(双向队列) 是一种优化了的、对序列两端元素进行添加和删除操作的基本序列容器。它允许较为快速地随机访问,但它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元…

我对STL的一些看法(五)初识关联容器

3关联容器 pair类型 这个是一个简单的标准库类型,该类型在utility头文件中定义,我们来看看他主要的操作: pair<T1 ,T2> p1; 创建一个空的pair对象 pair<T1,T2> p1(v1,v2);创建一个pair对象,他的两个元素分别为T1类型的v1,T2类型的v2 make_pair(v1,v2…

关系数据库——mysql数据类型大总结

整数类型&#xff1a; 实数类型&#xff1a; 定点数&#xff1a;DECIMAL和NUMERIC类型在MySQL中视为相同的类型。它们用于保存必须为确切精度的值。 DECIMAL(M,D)&#xff0c;其中M表示十进制数字总的个数&#xff0c;D表示小数点后面数字的位数。 如果存储时&#xff0c;整…

学点数学(5)--线性规划对偶形式的理解

线性规划对偶问题的理解1.弱对偶2.强对偶曾在上课的时候多次遇到这个求一个问题的对偶形式&#xff0c;大多是硬套公式。记一次&#xff0c;忘一次。后来在苏大佬的博客中看到了相关阐述&#xff0c;感觉豁然开朗&#xff0c;&#xff08;可以记得就一些了&#xff09;遂做笔记…

关系数据库——视图/存储过程/触发器

视图 视图是虚拟的表&#xff0c;与包含数据的表不同&#xff0c;视图只包含使用时动态检索数据的查询,主要是用于查询。 为什么使用视图 重用sql语句简化复杂的sql操作&#xff0c;在编写查询后&#xff0c;可以方便地重用它而不必知道他的基本查询细节。使用表的组成部分而…

C++(5)--运算符、表达式、条件结构(if, switch)

C运算符、表达式 条件结构1.表达式与运算符1.1赋值运算符1.2算术运算符1.3关系运算符1.4逻辑运算符1.5位运算符1.6 sizeof()1.7 三目运算符1.8 运算符的优先级2.条件结构2.1--if2.2 --switch结构2.3 switch 和 多重if 结构的对比条件结构)《老九学堂C课程》《C primer》学习笔记…

关系数据库——mysql常用函数总结

文本处理函数 Left(x,len) – 返回串左边的字符&#xff08;长度为len&#xff09; Right(x,len) Length(x) – 返回串的长度 Locate(x,sub_x) – 找出串的一个子串 SubString(x, from, to) – 返回字串的字符 Lower(x) Upper(x) LTrim(x) RTrim(x) Soundex(x) – 读…

setsockopt()用法(参数详细说明)

先来看看函数的原型: int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); 然后我们来看看参数: s(套接字): 指向一个打开的套接口描述字 level:(级别): 指定选项代码的类型。

再议libcurl编程

那是2年前用libcurl了,我肯定好久不用的知识,放置久了就会遗忘,现在我又重拾起这个知识点,重头再来,至于前面的基础知识,可以参考我的 http://blog.csdn.net/pbymw8iwm/article/details/6675754 假设你要获取URL所表示的远程主机上的资源。你需要写一段程序用来完…

关系数据库——并发控制

并发控制 多用户数据库&#xff1a;允许多个用户同时使用的数据库&#xff08;订票系统&#xff09; 不同的多事务执行方式&#xff1a; 1.串行执行&#xff1a;每个时刻只有一个事务运行&#xff0c;其他事务必须等到这个事务结束后方能运行。 2.交叉并发方式&#xff1a; …

C++(6)--初识循环while,do-while

初识循环1.使用while 循环结构2.使用do-while 循环3.python中的while循环《老九学堂C课程》《C primer》学习笔记。《老九学堂C课程》详情请到B站搜索《老九零基础学编程C入门》-------------简单的事情重复做&#xff0c;重复的事情用心做&#xff0c;用心的事情坚持做(老九君…

map类的erase方法的在Linux与Windows中的差异

这次的代码是跨平台的,尼玛在win32上通过,但是在linux上不通过了,查找了一下原来是平台linux不支持。 有人举了例子: std::map< int , float > i_f_map; i_f_map[1] = 1.2f; i_f_map[23] = 1.4f;