socket缓冲区

每个socket被创建后,无论使用的是TCP协议还是UDP协议,都会创建自己的接收缓冲区和发送缓冲区。当我们调用write()/send() 向网络发送数据时,系统并不会 马上向网络传输数据,而是首先将数据拷贝到发送缓冲区,由系统负责择时发送数据。根据我们选用的网络协议以及阻塞模式,系统会有不同的处理。

这些socket缓冲区特性可整理如下:

  1. socket缓冲区在每个套接字中单独存在;
  2. socket缓冲区在创建套接字时自动生成;
  3. 即使关闭套接字也会继续传送发送缓冲区中遗留的数据;
  4. 关闭套接字将丢失接收缓冲区中的数据。

 

 

TCP阻塞和非阻塞模式下的数据发送

  1. 阻塞模式下,调用write()/send()后程序将阻塞,如果发送缓冲区的可用长度大于待发送的数据,则数据将全部被拷贝到发送缓冲区,等待系统将发送缓冲区内的数据发送出去,当数据全部被拷贝到发送缓冲区后阻塞状态将消失;如果发送缓冲区的长度小于待发送的数据长度,则数据能拷贝多少就先拷贝多少(分批拷贝),一直等待直到数据可以全部被拷贝到发送缓冲区为止才可调用返回。write()/send()调用返回后并不能保证数据已经发送到对方缓冲区了,只能保证数据成功拷贝到发送缓冲区了,至于传输可靠性方面那就是由系统的协议栈来保证。
  2. 非阻塞模式下,调用write()/send()后,如果发送缓冲区剩余大小大于待发送的数据大小,那数据将完整拷贝到发送缓冲区,如果发送缓冲区剩余大小小于待发送的数据大小,那本次write()/send()则为尽可能拷贝,有多少空间就拷贝多少数据,返回值为均为成功拷贝到发送缓冲区的数据长度。
  3. 当接收端不接收数据,或者处理速率比发送方的发送速率低导致其接收缓冲区已满(接收窗口win=0),进而导致数据发送方的发送缓冲区的数据不断堆积进而缓冲区满,此时我们再调用write()/send()都将阻塞等待。
  4. 系统将发送缓冲区的数据通过网卡发到网络了,系统也不会立即将刚发送的数据从缓冲区中移除,只有当接收方回复了ack,我们才能认为对方收到了我们发送的信息,否则刚发送的数据必须还保留在发送缓冲区等待重传。当系统收到接收方对刚发送数据的ack后,才会移除发送缓冲区内对应的数据,腾出空间。
  5. 当启用了Nagle算法后,数据会倾向于堆积到一定大小或超时后才真正往网络发送数据,因此启用Nagle算法后的发送缓冲区更容易发生数据堆积。
  6. 因为发送缓冲区满导致write()/send()一直无法返回,这个可以通过setsockopt的参数 SO_SNDTIMEO来做超时处理,如果有数据成功拷贝到发送缓冲区,那超时后的返回值是成功拷贝到发送缓冲区的数据长度,如果没有数据拷贝成功,此时的超时后返回值为-1,errno为EAGAIN 或 EWOULDBLOCK,表现就是跟非阻塞模式的write()/send()是一样的。如果不设置默认就是永不超时。
  7. socket关闭时,但发送缓冲区中的数据仍未完全成功发送出去,那么这些数据将由系统负责把数据可靠地发送给对方。

TCP阻塞和非阻塞模式下的数据接收

  1. 调用read()/recv()时,如果模式选择的是阻塞模式,那么当接收缓冲区没数据时,程序就会一直拥塞等待,直到有数据可读为止,每次读的数据大小由进程控制,一般都需要分批读取,read()/recv()成功返回时的返回值是成功读取到的数据的长度;如果模式选择的是非阻塞模式,那么程序调用read()/recv()调用返回的返回值是成功读取的字节数,如果没数据可读时同样是马上返回,此时的返回值为0。
  2. 当程序并没有及时读取接收缓冲区中的数据,那缓冲区可利用的空间逐渐变小,直到缓冲区满,但TCP套接口接收缓冲区不可能溢出。这是因为TCP有流量控制策略,根据TCP的流量控制中滑动窗口机制,接收方会捎到窗口大小给发送方,如果缓冲区空间为0发送方也能及时知道停止发送。
  3. 当socket关闭时,如果接收缓冲区还有数据没读取,那么这部分数据将被丢弃。
  4. 跟TCP阻塞写相同,我们可以通过setsockopt的参数 SO_RCVTIMEO来对阻塞模式的读做超时处理,如果一段时间内没有数据读取成功,此时的超时后返回值为-1,errno为EAGAIN 或 EWOULDBLOCK。
  5. recv的第四个参数若为MSG_WAITALL,则在阻塞模式下不等到指定数目的数据不会返回,除非超时时间到。当然如果对方关闭了,即使超时时间未到,recv 也返回0。

UDP阻塞和非阻塞下的数据发送接收

  1. UDP套接口有发送缓冲区大小(SO_SNDBUF修改),不过它仅仅是写到套接口的UDP数据报的大小上限,即UDP没有发送缓冲区。如果一个应用程序写一个大于套接口发送缓冲区大小的数据报,内核将返回EMSGSIZE错误。
  2. 从UDP套接口write成功返回仅仅表示用户写入的数据报或者所有片段已经加入到数据链路层的输出队列。如果该队列没有足够的空间存放该数据包或者它的某个片段,内核通常返回给应用进程一个ENOBUFS错误。
  3. UDP是没有流量控制的:较快的发送端可以很容易淹没较慢的接收端,当接收缓冲区满后,后面收到的数据报都将丢弃。
  4. UDP存在丢包的可能:调用recvfrom方法接收到数据后,处理数据花费时间太长,再次调用recvfrom,两次调用间隔里,发过来的包可能丢失。处理方法是调大接收缓冲区或者通过将接收到数据存入一个缓冲区,并迅速返回继续recvfrom。
UDP socket 设置为的非阻塞模式 
Len = recvfrom(SocketFD, szRecvBuf, sizeof(szRecvBuf), MSG_DONTWAIT, (struct sockaddr *)&SockAddr,&ScokAddrLen);


UDP socket 设置为的阻塞模式 
Len = recvfrom(SocketFD, szRecvBuf, sizeof(szRecvBuf), 0, (struct sockaddr *)&SockAddr,&ScokAddrLen);

 

阻塞、非阻塞的本质

阻塞:阻塞的本质是,进程因为资源等待而主动让出CPU,进程从运行队列删除,幷加入到等待队列,然后等待资源。等超时或数据资源到来则唤醒进程继续执行,若有数据可读那就把数据拷贝给进程,无数据可读但超时了则返回进程继续执行后面的逻辑。

非阻塞:本质是应用进程掌控读取数据的节奏,通过轮训的方式查询数据是否可读,进程始终占用着CPU,能比较好地满足高性能进程需求,执行效率高(数据没到位,进程可以继续处理其他业务,无需阻塞其他业务进行)。

 

#补充

1、TCP. SO_RCVBUF & TCP. SO_SNDBUF

每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的流量(拥塞)控制便是依赖于这两个独立的buffer以及buffer的填充状态。

1.1 接收端冲区

接收缓冲区把数据缓存入内核,应用进程一直没有调用recv()进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。不管进程是否调用recv()读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。

recv(),就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回;

1.2发送缓冲区

进程调用send()发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。换句话说,send()返回之时,数据不一定会发送到对端去(和write写文件有点类似)。

send(),仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中,发送是TCP的事情,和send其实没有太大关系。

1.3 实际应用

接收缓冲区被TCP和UDP用来缓存网络上来的数据,一直保存到应用进程读走为止。

对于TCP,如果应用进程一直没有读取,接收缓冲区满了之后,发生的动作是:接收端通知发发端,接收窗口关闭(win=0)。这个便是滑动窗口的实现。保证TCP套接口接收缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。

1.4 问题

能发出多少TCP包以及每个包承载多少数据,除了受到自身服务器配置和环境带宽影响,对端的接收状态也能影响你的发送状况。

查看测试机的socket发送缓冲区大小,如图1所示:

 

第一个值是一个限制值,socket发送缓存区的最少字节数;
第二个值是默认值;
第三个值是一个限制值,socket发送缓存区的最大字节数;
根据实际测试,发送缓冲区的尺寸在默认情况下的全局设置是16384字节,即16k。
proc文件系统下的值和sysctl中的值都是全局值,应用程序可根据需要在程序中使用setsockopt()对某个socket的发送缓冲区尺寸进行单独修改。

解决思路:通过合理的设置“TCP.SO_RCVBUF & TCP.SO_SNDBUF”来提高系统的吞吐量以及快速检测tcp链路的连通性; 这两个选项就是来设置TCP连接的两个buffer尺寸的。

2、UDP接收缓冲区

每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。

UDP:当套接口接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。

3、总结

这也是TCP可靠,UDP不可靠的表现。

原文链接:https://blog.csdn.net/Swallow_he/article/details/84392285

posted @   wangguigang7  阅读(1370)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示