tcp 服务端接收数据处理思路梳理,以及select: Invalid argument报错 笔记

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: tcp 服务端接收数据处理思路梳理,以及select: Invalid argument报错 笔记

每次在实现tcp服务器端时,总会思考:处理接收到的客户端的消息细节时,总会陷入一点点的误区,

加上分析公司前人各式各样的业务代码,总是会被略微带偏,这里做简单的tcp接收后相关流的处理思路。

0:总结

在处理tcp的接收时:

1:tcp是可靠的,内核为每个tcp 客户端连接都分配了一个发送缓冲区和接收缓冲区。

2:基于第一点:

====》针对每个连接,可以可靠,按顺序得接收到数据(即放入对应得接收缓冲区中)。

====》每个连接的缓冲区是独立的,不会有串包现象。

====》缓冲区中存放,是流的形式,无法识别多个包的边界,需要业务层适配。

3:基于第二点:

====》我们需要在业务层适配,识别到一个完整的包,一般有两种方案:(Length+Data), (特定的头部或者尾部标识),我的理解其他相关特定协议思路大概相同

====》作为服务器端,我们需要同时处理多个接收缓冲区(epoll/select管理),同时要考虑如何读取到一个完整的包(每次读多长的数据,肯定能读到一个完整的包吗?)

4:基于第三点:怎么保证一次的事件触发,能正确的读取到缓冲区的内容。

===》比如如果是epoll ET模式,应该循环进行读读完所有的数据(可能有粘包现象,需要处理)

===》如果是epoll LT或者select模式,代码比较简单,但是,如果tcp内核底层拆过包,分多次发过来,可能有半包现象,以及recv时这个长度如何定义?

===》针对以上:(我能思考到的最优的就是: 读固定的长度,while读放入应用层缓冲区(我们应该每个连接维持一个应用层缓冲区)中,然后缓冲区做拆包处理)

5:基于第四点,如果有的代码没有用应用层缓冲区暂存呢?(仅仅考虑的是Length+data的模式

看到有的代码,是特定的业务,就是每次io事件触发,先接收特定的长度,在读取实际数据,这能确定一个完整的包,去做业务处理。

我就会思考这中处理会不会有缺陷,最容易思考的就是

1:一次事件触发处理一个包,会不会有数据没有处理完,内核的接收缓冲区中仍然有数据,比如epoll的ET模式场景,但是貌似select和epoll的LT影响不大

2: 如果业务层没有做相关的处理,有可能的场景是tcp底层拆包,这样先读取特定的自己取长度,再读取实际长度数据时,可能是有问题的?下个包一直没到。

===》但是,最终思考,如果业务层做过处理,保证tcp底层不会拆包,我们如果不用应用层缓冲区应该也是可行的。

===》即,业务层已经保证每次接收是一个特定格式(length+data)的完整的包,每次先接收特定字节头部,解析后接收实际长度数据 后做完整包的处理。

===》考虑事件触发特性,保证处理完善,我们epoll ET处理时,应该循环一次性读完所有的包,而epoll LT以及select的模式下,貌似影响不大,都能正确取完。

1:tcp进行相关recv的处理时

在实际的业务中,我们通常配合select或者epoll对服务器端相关连接进行管理,在接收客户端的消息时,有一些关注点:

1.1:tcp是可靠的流式传输,并且实际上服务器端为每个客户端都维持了一个接收缓冲区和一个发送缓冲区。

第一:tcp底层的流的接收可以保证。(有自己的缓冲区,并且可靠,顺序)

对于每个客户端的连接,可以保证可靠按顺序接收到,放进自己对应的缓冲区中

===》我一直陷入一个误区,如果依赖tcp底层的拆包逻辑,可能在收到多个包的中间会收到其他的包,这其实是一个思想误区,不可能串包

===》服务端对tcp每个连接都设有一个发送缓冲区和接收缓冲区,针对一个连接加上tcp的可靠传输,tcp底层的接收是可以得到保障的。

第二:需要定制协议,对缓冲区流正确处理。

===》tcp虽然可靠,但是却是流的形式进行接收,无法知道多个包的边界。

===》为了能正确识别到每个完整的包,去正确处理(识别到一个完整的包(tcp recv时是从缓冲区中拿数据,第一:缓冲区可能多个包(粘包)第二:可能recv读了半个包,或者tcp底层拆包,第二个包还没来))

===》所以我们需要在业务层对流做特定的限制,保证能识别到一个包 : 比如length+data,比如 加特定标识的协议头部或者尾部

第三:在特定协议的基础上,如何保证正确读取多个缓冲区。

===》如果是epoll的ET边缘触发模式,就得用while循环进行多次读取

===》如果是epoll的LT水平触发模式,或者select,是能下次触发的,不过也可以循环读取完做处理提升效率。

第四:如何保证包的完整性,一个完整的包去做对应业务处理。

===》根据应用层协议,可以先进行数据读取,放入缓存中,然后根据协议解析缓存中的相关数据进行完整包的处理。 (相对应的,应用层缓冲区也应该是一个连接对应一个缓冲区)

??采用缓冲区的方式是没有问题的,但是针对特定的协议,比如length+data的方式,先读取特定字节,解析实际长度,再接收特定的实际数据可以吗?

===》个人理解是在一定的业务层保证上是可以的,

===》tcp如果发送一个过大的包,会进行拆包的,这种场景事件触发后,根据特定的自己字节读实际的长度,下个包迟迟不到就有问题。

===》但是如果tcp我们业务层保证了不回tcp拆包,我们控制了发包大小,我觉得其实也是可行的。

2:关注细节:网络字节序,结构体的对齐方式

如果代码中实现tcp发送与接收相关协议设计时,需要关注一些细节:

1:如果协议用的结构体的方式,要注意结构体字节对齐大小,会影响接收端的解析。

2:一般大于2byte的字节,最后按照特定的函数进行相关的主机字节序和网络字节序的转换

#include <arpa/inet.h> 
uint32_t htonl(uint32_t hostlong);        //32位主机字节序转为网络字节序   Host to Network Long   4字节
uint16_t htons(uint16_t hostlong);        //十六位主机字节序转为网络字节序  Host to Network Short  2字节
uint32_t ntohl(uint32_t hostlong)         //32位网络字节序转为主机字节序    Network to Host Long
uint16_t ntohs(uint16_t hostlong)         //16位网络字节序转为主机字节序    Network to Host Short

3:代码测试时 select: Invalid argument

栈内存定义结构变量最好清零

在简单demo进行测试时,环境运行启动不起来,报错select: Invalid argument

百度结合测试后,发现时select最后参数struct timeval tv;设置的问题

===》1:参考百度,有类似的问题是因为设置了 tv.tv_usec 值过大。

===》2:然而我代码中并没有对这个值设置过大,但是没有对初始化时清零。

struct timeval tv;
  memset(&tv, 0, sizeof(struct timeval));  //注意  这里清理初始化后的内存
  fd_set rset;
  int maxfd = m_listenfd + 1;
  while(m_running)
  {
    tv.tv_sec = 30;
    //tv.tv_usec = 0;  //是因为用的栈内存  如果这里的内存比较大的话就会导致select  Invalid argument
        rset = m_allset;
        ret = select(maxfd, &rset, (fd_set *)0,(fd_set *)0, (struct timeval *)&tv);
        ...
    }

===》另外,我听同事说tcp缓冲区溢出问题,个人理解是 tcp接收缓冲区不会有所谓的溢出问题,发送缓冲区因为发送频率应该会出现类似问题。

目录
相关文章
|
8天前
|
Python
8. 如何解决 Tornado 检测到了有事件(events)被发送到一个已经关闭的流(stream)。在 Tornado 中,一个流代表一个请求或响应的数据流。这个警告可能意味着在请求处理的过程中,
8. 如何解决 Tornado 检测到了有事件(events)被发送到一个已经关闭的流(stream)。在 Tornado 中,一个流代表一个请求或响应的数据流。这个警告可能意味着在请求处理的过程中,
|
3月前
|
JSON 数据格式
Navicate数据报错之Bad Request,发送端口测试的时候,使用JSON字符串,而不是Text
Navicate数据报错之Bad Request,发送端口测试的时候,使用JSON字符串,而不是Text
|
5月前
|
安全 网络安全 Python
使用 Python 代码实现 ICMP Timestamp 请求和回应
使用 Python 代码实现 ICMP Timestamp 请求和回应
|
12月前
|
存储 网络协议 Java
UDP,TCP特点对比,DatagramPacket,SocketAddress的使用 ,UDP的API,如何编写一个简单的回显服务器及客户端,详细解释(本文内容较难,建议多次阅读,自己敲一敲)
UDP,TCP特点对比,DatagramPacket,SocketAddress的使用 ,UDP的API,如何编写一个简单的回显服务器及客户端,详细解释(本文内容较难,建议多次阅读,自己敲一敲)
|
5月前
|
移动开发 小程序 JavaScript
mPaas 问题之参数收不到如何解决
mPaaS小程序是阿里巴巴移动平台服务(mPaaS)推出的一种轻量级应用解决方案,旨在帮助开发者快速构建跨平台的小程序应用;本合集将聚焦mPaaS小程序的开发流程、技术架构和最佳实践,以及如何解决开发中遇到的问题,从而助力开发者高效打造和维护小程序应用。
|
11月前
|
消息中间件
分享zframe_send使用过程中 的一个小问题
在zframe_send函数中,第一个参数是一个指向zframe_t指针的指针。这是因为在发送帧之后,该函数会销毁帧并将你的指针设置为NULL。这样做是为了防止你再次使用已经被销毁的帧。
62 0
|
12月前
|
前端开发 JavaScript
.net core 前端传递参数有值 后端接收到的数据却是null
1、问题分析 在做接口测试时,偶然出现了前端输出有值,但是后端断点调试时却出现接收参数总是为null的情况 2、解决办法 前端打印log,看前端的每一个传值的数据类型,与后端请求参数类进行认真的一一比对 小技巧: ① 直接打印调用接口的传参值的数据类型,例如 console.log(type of this.form.name) --string console.log(type of this.form.age) --number 打印的数据类型与后端接口的参数类比对,查出不对应的类型 ② 关于非必填的值,默认传值可能出现空字符串(' ')、NaN值(Not a Number
236 0
|
12月前
|
缓存 索引
SAP ABAP 系统传输请求(Transport Request)导入到其他系统背后执行的逻辑
SAP ABAP 系统传输请求(Transport Request)导入到其他系统背后执行的逻辑
|
网络协议
客户端与服务端--(基于TCP/IP的群聊系统的设计与实现)
接上期 *Swing组件组合使用--登录界面源码(仿QQ)*,这期分享客户端与服务端源码
SAP OData 服务在 gateway 层出错,应该如何处理?通过一个错误消息 Query XXX is invalid or contains errors 来举例说明
订阅专栏 在浏览器里访问如下url遇到错误: https://jerry.sap.corp:44300/sap/opu/odata/sap/C_SALESCONTRACTITMFLFMTQ_CDS/$metadata
SAP OData 服务在 gateway 层出错,应该如何处理?通过一个错误消息 Query XXX is invalid or contains errors 来举例说明