一文彻底搞通TCP之send & recv原理

接触过网络开发的人,大抵都知道,上层应用使用send函数发送数据,使用recv来接收数据,而send和recv的实现原理又是怎样的呢?

在前面的几篇文章中,我们有提过,TCP是个可靠的、全双工协议。其流量控制或者拥塞控制依赖于滑动窗口和拥塞窗口的滑动来实现,而这两个窗口的滑动实现则是依赖于TCP中的两个buffer,这两个buffer则是TCP socket在内核中的发送缓冲区(send buffer)和接收缓冲区(recv buffer)。

在本文中,我们首先会简单介绍下TCP中发送缓冲区和接收缓冲区的作用(对于后面理解send和recv非常重要),然后讲解Linux系统下,TCP发送和接收数据是如何实现的。

缓冲区

一文彻底搞通TCP之send & recv原理

缓冲区,可以理解为是一个临时缓存。

对于发送端来说,socket将数据拷贝到发送临时缓冲区,就立即返回到应用层去做其他的事情,而剩下的将临时缓冲区的数据通过内核发送到对端,这就是tcp的事。

对于接收端来说,内核将网络中的数据拷贝到缓冲区,等待上层应用读取。

发送缓冲区

上面有讲,进程在调用send()发送的数据的时候,最简单情况(也是一般情况), 将数据拷贝进入socket的内核发送缓冲区之中,然后send便会立即返回。

换句话说,在应用层调用send()返回之时,数据不一定会发送到对端去(和write写文件有点类似),send()仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中。

TCP socket有两种模式,即阻塞模式和非阻塞模式。

在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送并得到确认后再返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send向网络发送缓存中不能容纳的那部分数据,并等待对端确认后再返回(接收端只要将数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv)

在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.

在Linux内核中,有两种方式可以查看tcp缓冲区buffer大小。

1、通过查看/etc/sysctl.ronf下的net.ipv4.tcp_wmem值

一文彻底搞通TCP之send & recv原理

2、 通过命令'cat /proc/sys/net/ipv4/tcp_wmem'

cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304

从上面可以看出,在笔者所在的服务器上,tcp send缓冲区buffer有3个值,分别是4096 16384 4194304。

第一个值是socket的发送缓存区分配的最少字节数,

第二个值是默认值(该值会被net.core.wmem_default覆盖),缓存区在系统负载不重的情况下可以增长到这个值

第三个值是发送缓存区空间的最大字节数(该值会被net.core.wmem_max覆盖)

我们可以通过程序,来修改当前tcp socket的发送缓冲区大小,需要注意的是,如下的代码修改,只会修改当前特定的socket。

int buffer_len = 10240;
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&buffer_len, buffer_len);
接收缓冲区

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

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

与查看发送缓冲区大小的方式一样,接收缓冲区也是通过如上的两种方式。 1、通过查看/etc/sysctl.ronf下的net.ipv4.tcp_rmem值

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfppx.html