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

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

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

cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4194304

TCP接收缓冲区buffer有3个值,分别是4096 87380 4194304。

第一个值是socket的接收缓存区的最少字节数,

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

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

同样的,可以通过如下代码,修改接收缓冲区的大小。

int buffer_len = 10240;
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&buffer_len, buffer_len);
实现原理

为了便于我们理解TCP的整个传输过程,我们先了解下TCP的四层模型以及四册模型在数据传输中的流向。后面我们将从四层模型的角度来分析send和recv函数在每层中都做了什么。

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

send原理 NAME
       send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

DESCRIPTION
       The system calls send(), sendto(), and sendmsg() are used to transmit a message to another socket.

当调用该函数时,send函数: 1、先比较待发送数据的长度len和套接字sockfd的可用发送缓冲区的长度

如果数据长度len大于发送缓冲区的长度,则分多次发送

如果果len小于或者等于sockfd的缓冲区长度,那么send先检查协议是否正在发送sockfd的发送缓冲中的数据

如果是就等待协议把数据发送完

否则,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和len

如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完

如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里。 如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR; 如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。 需要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。 如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR.(每一个除send外的socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该socket函数就返回SOCKET_ERROR)。

如果对具体实现不是很感兴趣,可直接此部分

从四层模型的角度来分析send实现。

应用层

对于TCP,应用程序在创建socket之后,调用connect()函数,通过socket使客户端和服务端建立连接。然后就可以调用send函数发送数据。

传输层

数据在传输层进行处理,以TCP协议为例,其主要有以下功能:

1、构造TCP段

2、计算校验和

3、发送回复(ACK)包

4、滑动窗口(sliding windown)等操作保证可靠性。

不同的协议有不同的发送函数,TCP调用tcp_sendmsg函数,而UDP则调用的是sock_sendmsg函数。

tcp_sendmsg()的主要工作是传输用户层的数据,将数据放入skb中。然后调用tcp_push()发送,tcp_push函数调用tcp_write_xmit() 函数,依次调用发送函数tcp_transmit_skb将skb封装tcp头之后,回调ip_queue_xmit。

网络层

ip_queue_xmit(skb)主要有路由查找校验、封装ip头和ip选项,最后通过ip_local_out发送数据包。

数据链路层

数据链路层在不可靠的物理介质上提供可靠的传输。该层的功能包括:物理地址寻址、数据成帧、流量控制、数据错误检测、重发等。这一层的数据单位称为帧(frame)。

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

上图为send函数源码的调用逻辑图,对源码有兴趣的话,可以在net/tcp.c找到对应的实现。

recv原理 NAME
       recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

DESCRIPTION
       The recvfrom() and recvmsg() calls are used to receive messages from a socket, and may be used to receive data on a socket whether or not it is connection-oriented.

       If  src_addr  is not NULL, and the underlying protocol provides the source address, this source address is filled in.  When src_addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.  The argument
       addrlen is a value-result argument, which the caller should initialize before the call to the size of the buffer associated with src_addr, and modified on return to indicate the actual size of the source address.  The returned address is
       truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.

       The recv() call is normally used only on a connected socket (see connect(2)) and is identical to recvfrom() with a NULL src_addr argument.

当调用该函数时候:

先检查套接字sockfd的接收缓冲区

如果sockfd接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。

当协议把数据接收完毕,recv函数就把sockft的接收缓冲中的数据copy到buf中,recv函数返回其实际copy的字节数。

如果recv在copy时出错,那么它返回SOCKET_ERROR;

如果recv函数在等待协议接收数据时网络中断了,那么它返回0 。

对方优雅的关闭socket并不影响本地recv的正常接收数据;

如果协议缓冲区内没有数据,recv返回0,指示对方关闭;

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

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