TCP与UDP收发的时候TCP有缓冲区还是UDP有缓冲区,使用它们时该注意什么?

问题:TCP与UDP收发的时候TCP有缓冲区还是UDP有缓冲区,使用它们时该注意什么?

(一)基础

1、TCP为可靠链接,分三次握手四次释放。

2、UDP为不可靠链接

(二)TCPUDP的输出

个TCP套接口有一个发送缓冲区,可以用SO_SNDBUF套接口选项来改变这一缓冲区的大小。当应用进程调用write往套接口写数据时,内核从应用进
程缓冲区中拷贝所有数据到套接口的发送缓冲区,如果套接口发送缓冲区容不下应用程序的所有数据,或者是应用进程的缓冲区大于套接口的发送缓冲区,或者是套
接口的发送缓冲区中有别的数据,应用进程将被挂起。内核将不从write返回。直到应用进程缓冲区中的所有数据都拷贝到套接口发送缓冲区。所以,从写一个
TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程缓冲区,它并不是告诉我们对方收到数据。TCP发给对方的数据,对方在收到数据时
必须给矛确认,只有在收到对方的确认时,本方TCP才会把TCP发送缓冲区中的数据删除。
UDP因为是不可靠连接,不必保存应用进程的数
据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个
发送缓冲区。写UDP套接口的write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误
ENOBUFS.

(三)tcp socket的发送与接收缓冲区  

  应用程序可通过调用send(write, sendmsg等)利用tcp
socket向网络发送应用数据,而tcp/ip协议栈再通过网络设备接口把已经组织成struct
sk_buff的应用数据(tcp数据报)真正发送到网络上,由于应用程序调用send的速度跟网络介质发送数据的速度存在差异,所以,一部分应用数据被
组织成tcp数据报之后,会缓存在tcp
socket的发送缓存队列中,等待网络空闲时再发送出去。同时,tcp协议要求对端在收到tcp数据报后,要对其序号进行ACK,只有当收到一个tcp
数据报的ACK之后,才可以把这个tcp数据报(以一个struct sk_buff的形式存在)从socket的发送缓冲队列中清除。
  
tcp socket的发送缓冲区实际上是一个结构体struct sk_buff的队列,我们可以把它称为发送缓冲队列,由结构体struct
sock的成员sk_write_queue表示。sk_write_queue是一个结构体struct
sk_buff_head类型,这是一个struct sk_buff的双向链表,其定义如下:

struct sk_buff_head {
        struct sk_buff  *next;      //后指针
        struct sk_buff  *prev;      //前指针
        __u32             qlen;       //队列长度(即含有几个struct sk_buff)
        spinlock_t        lock;       //链表锁
    };

(1)

  内核代码中,先在这个队列中创建足够存放数据的struct sk_buff,然后向队列存入应用数据。
 
 结构体struct sock的成员sk_wmem_queued表示发送缓冲队列中已分配的字节数,一般来说,分配一个struct
sk_buff是用于存放一个tcp数据报,其分配字节数应该是MSS+协议首部长度。在我的实验环境中,MSS值是1448,协议首部取最大长度
MAX_TCP_HEADER,在我的实验环境中为224。经数据对齐处理后,最后struct
sk_buff的truesize为1956。也就是队列中每分配一个struct
sk_buff,成员sk_wmem_queue的值就增加1956。
    struct sock的成员sk_forward_alloc是表示预分配长度。当我们第一次要为发送缓冲队列分配一个struct sk_buff时,我们并不是直接分配需要的内存大小,而是会以内存页为单位进行的预分配。
   
tcp协议分配struct
sk_buff的函数是sk_stream_alloc_pskb。它首先根据传入的参数指定的大小在内存中分配一个struct
sk_buff,如果成功,sk_forward_alloc取该大小值,并向上取整到页(4096字节)的整数倍。并累加到struct
sock的成员sk_prot,也即表示tcp协议的结构体mytcp_prot的成员memory_allocated中,该成员是一个指针,指向变量
tcp_memory_allocated,它表示的是当前整个TCP协议当前为缓冲区所分配的内存(包括读缓冲队列)
   
当把这个新分配成功的struct
sk_buff放入到缓冲队列sk_write_queue后,从sk_forward_alloc中减去该sk_buff的truesize值。第二次
分配struct
sk_buff时,只要再从sk_forward_alloc中减去新的sk_buff的truesize即可,如果sk_forward_alloc已
经小于当前的truesize,则将其再加上一个页的整数倍值,并累加入tcp_memory_allocated。
    也就是说,通过sk_forward_alloc使全局变量tcp_memory_allocated保存当前tcp协议总的缓冲区分配内存的大小,并且该大小是页边界对齐的。
(2)

  前面讲到struct
sock的成员sk_forward_alloc表示预分配内存大小,用于向全局变量mytcp_memory_allocated累加当前已分配的整个
TCP协议的缓冲区大小。之所以要累加这个值,是为了对tcp协议总的可用缓冲区大小作限制。表示TCP协议的结构体mytcp_prot还有几个成员与
缓冲区相关。
  mysysctl_tcp_mem是一个数组,由mytcp_prot的成员sysctl_mem指向,数组共有三个元
素,mysysctl_tcp_mem[0]表示对缓冲区总的可用大小的最低限制,当前总共分配的缓冲区大小低于这个值,则没有问题,分配成功。
mysysctl_tcp_mem[2]表示对缓冲区可用大小的最高硬性限制,一旦总分配的缓冲区大小超出这个值,我们只好把tcp socket
的发送缓冲区的预设大小sk_sndbuf减小为已分配缓冲队列大小的一半,但不能小于SOCK_MIN_SNDBUF(2K),但保证这一次的分配成
功。mysysctl_tcp_mem[1]介于前面两个值的中间,这是一个警告值,一旦超出这个值,进入警告状态,这个状态下,根据调用参数来决定此次
分配是否成功。
   
这三个值的大小是根据所在系统的内存大小,在初始化时决定的,在我的实验环境中,内存大小为256M,这三个值分配是:96K,128K,192K。它们
可以通过/proc文件系统,在/proc/sys/net/ipv4/tcp_mem中进行修改。当然,除非特别需要,一般无需改动这些缺省值。

mysysctl_tcp_wmem也是一个同样结构的数组,表示发送缓冲区的大小限制,由mytcp_prot的成员sysctl_wmem指向,其缺
省值分别是4K,16K,128K。可以通过/proc文件系统,在/proc/sys/net/ipv4/tcp_wmem中进行修改。struct
sock的成员sk_sndbuf的值是真正的发送缓冲队列的预设大小,其初始值取中间一个16K。在tcp数据报的发送过程中,一旦
sk_wmem_queued超过sk_sndbuf的值,则发送停止,等待发送缓冲区可用。因为有可能一批已发送出去的数据还没有收到ACK,同时,缓
冲队列中的数据也可全部发出去,已达到清空缓冲队列的目的,所以,只要在网络不是很差的情况下(差到没有办法收到ACK),这个等待在一段时间后会成功
的。
    全局变量mytcp_memory_pressure是一个标志,在tcp缓冲大小进入警告状态时,它置1,否则置0。

(3)

  mytcp_sockets_allocated是到目前为止,整个tcp协议中创建的socket的个数,由
mytcp_prot的成员
sockets_allocated指向。可以在/proc/net/sockstat文件中查看,这只是一个供统计查看用的数据,没有任何实际的限制作
用。
  mytcp_orphan_count表示整个tcp协议中待销毁的socket的个数(已无用的socket),由mytcp_prot的成员orphan_count指向,也可以在/proc/net/sockstat文件中查看。
 
 mysysctl_tcp_rmem是跟mysysctl_tcp_wmem相同结构的数组,表示接收缓冲区的大小限制,由mytcp_prot的成员
sysctl_rmem指向,其缺省值分别是4096bytes,87380bytes,174760bytes。它们可以通过/proc文件系统,在
/proc/sys/net/ipv4/tcp_rmem中进行修改。struct
sock的成员sk_rcvbuf表示接收缓冲队列的大小,其初始值取mysysctl_tcp_rmem[1],成员sk_receive_queue
是接收缓冲队列,结构跟sk_write_queue相同。
  tcp
socket的发送缓冲队列跟接收缓冲队列的大小既可以通过/proc文件系统进行修改,也可以通过TCP选项操作进行修改。套接字级别上的选项
SO_RCVBUF可用于获取和修改接收缓冲队列的大小(即strcut
sock->sk_rcvbuf的值),比如下列的代码可用于获取当前系统的接收缓冲队列大小:

int rcvbuf_len;
    int len = sizeof(rcvbuf_len);
    if( getsockopt( fd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf_len, &len ) < 0 ){
        perror("getsockopt: ");
        return -1;
    }
    printf("the recevice buf len: %d\n", rcvbuf_len );

而套接字级别上的选项SO_SNDBUF则用于获取和修改发送缓冲队列的大小(即struct sock->sk_sndbuf的值),代码同上,只需改SO_RCVBUF为SO_SNDBUF即可。

获取发送和接收缓冲区的大小相对简单一些,而设置的操作在内核中动作会稍微复杂一些,另外,在接口上也会有所差异,即由setsockopt传入的表示缓
冲区大小的参数是实际大小的1/2,即,如果想要设发送缓冲区的大小为20K,则需要这样调用setsockopt:

int rcvbuf_len = 10 * 1024;  //实际缓冲区大小的一半。
     int len = sizeof(rcvbuf_len);
     if( setsockopt( fd, SOL_SOCKET, SO_SNDBUF, (void *)&rcvbuf_len, len ) < 0 ){
        perror("getsockopt: ");
        return -1;
     }

在内核中,首先内核要判断新设置的值是否超过上限,若超过,则取上限为新值,发送和接收缓冲区大小的上限值分别为sysctl_wmem_max和
sysctl_rmem_max的2倍。这两个全局变量的值是相等的,都为(sizeof(struct sk_buff) + 256) *
256,大概为64K负载数据,由于struct
sk_buff的影响,实际发送和接收缓冲区的大小最大都可设到210K左右。它们的下限是2K,即缓冲区大小不能低于2K。
    另外,SO_SNDBUF和SO_RCVBUF有一个特殊的版本:SO_SNDBUFFORCE和SO_RCVBUFFORCE,它们不受发送和接收缓冲区大小上限的限制,可设置不小于2K的任意缓冲区大小

此外还可以通过图例解释:

概念:

MTU:链路层上数据帧中数据的最大值,即IP数据报的整个值。详见TCP/IP第7页。数据进入协议栈的封装过程。

MSS:TCP报文段中数据的最大值---MSS选项只能出现在SYN报文中。

TCP输出:

每个TCP套接口都有一个发送缓冲区,我们可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用程序调用write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如 果
套接口发送缓冲区容不下应用程序所有的数据(或者应用进程的缓冲区大于套接口发送缓冲区,或者是套接口发送缓冲区还有其他数据),应用进程将被挂起,这里
假设write是阻塞的。内核将不从write系统调用返回,直到将应用进程缓冲区的所有数据都拷贝到套接口发送缓冲区。
因此从写一个TCP套接口的write调用成功返回仅仅代表我们重新使用应用进程的缓冲区。他并不告诉我们对端TCP或者应用进程已经接收到数据。

UDP输出:

这一次我们展示的套接口发送缓冲区用虚框表示,因为它并不存在。UDP套接口有发送缓冲区大小(SO_SNDBUF修改),不过它仅仅是写到套接口的UDP数据报的大小上限。 如果应用程序写一个大于套接口发送缓冲区大小的数据报,内核将返回一个EMSGSIZE错误。
既然UDP不可靠,他不必保存应用进程的数据拷贝,因此无需真正的发送缓冲区(应用进程的数据在沿协议栈往下传递,以某种形式拷贝到内核缓冲区,然而数据链路层在送出数据之后将丢弃该拷贝)。

根据上图发现,UDP没有MSS的概念,如果某个UDP应用程序发送大数据,那么他比TCP应用程序更容易分片。从UDP套接口
write成功返回仅仅表示用户写入的数据报或者所有片段已经加入到数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或者他的某个片段,内核
通常返回给应用进程一个ENOBUFS错误(也有的系统不会返回错误)。

TCP和UDP都拥有套接口接收缓冲区。TCP套接口接收缓冲区不可能溢出,因为TCP具有流量控制(窗口).然而对于TCP来说, 当接收到的数据报装不进套接口接收缓冲区时,该数据报就丢弃
。UDP是没有流量控制的:较快的发送端可以很容易淹没较慢的接收端,导致接收端的UDP丢弃数据报。

我们可以用程序来验证这一点:

#define NDG 2000
#define DGLEN 1400
client()
{
  for(int i=0;i<NDG;i++)
    sendto(sockfd,sendline,DGLEN,0,pservadd,servlen);
}

客户端快速发送大数据报,我们在一个慢速的主机(FreeBSD)上的接收端就发现很多丢包现象。UDP套接口接收缓冲区在 FreeBSD下面缺省是42080字节,也就是30*1400个字节的容纳空间。如果我们增大接收缓冲区,服务器就期望接收更多的数据报。 setsockopt(sockfd,SOL_SOCKET,SO_RECVBUF,&n,sizeof(n)),其中n=220*1024,这 个时候如果再次运行就会发现丢包有所改善(但并没实质解决)。

SO_RCVBU和SO_SNDBUF分别设置接收缓冲区和发送缓冲区大小。

时间: 2024-10-30 21:03:31

TCP与UDP收发的时候TCP有缓冲区还是UDP有缓冲区,使用它们时该注意什么?的相关文章

Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select poll epoll udp组播 线程池

[本文谢绝转载原文来自http://990487026.blog.51cto.com] Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select  poll  epoll udp组播 线程池 TCP 11种状态理解: 1,客户端正常发起关闭请求 2,客户端与服务端同时发起关闭请求 3,FIN_WAIT1直接转变TIME_WAIT 4,客户端接收来自服务器的关闭连接请求 多路IO转接服务器: select模型 poll模型 epoll模型 udp组播模型 线

TCP/IP(三):传输层TCP与UDP

TCP协议 概述 TCP协议和UDP协议处于同一层:传输层,但是两者之间有很大的区别,TCP协议具有以下特点: TCP提供可靠的数据传输服务,TCP是面向连接的,即数据在通信之间要先建立连接,结束通信时要释放连接,这也是后面所说的3次握手,4次挥手: TCP是点对点的连接方式,即一条TCP连接两端只能是两个端点: TCP提供可靠的,无差错的,不丢失,不重复,按顺序的服务: TCP提供全双工通信,允许通信双方任何时候都能发送数据,TCP在连接的两端都设置有发送缓存和接收缓存: TCP是面向字节流的

对TCP/IP协议的一些看法(12):UDP协议

UDP协议相比于TCP来说,也是不可靠的传输协议.那么什么场合下采用UDP协议呢: 1.高效可靠的环境下 2.由于UDP开销小(1)不用三次握手2)传输过程中不用确认3)不用四次握手),故适合在轻权的环境下通信,例如TFTP.SNMP.DNS和DHCP协议 3.对实时性要求高,例如打电话,你可以听不清几个字,但想必你受不了重复听到很多字吧 4.多播信息或大多是为简短信息的情况下 5.应用场景中心重性能胜于重完整性和安全性 UDP数据包的格式封装包括首部和数据部分,其中首部的字段如下: 源端口号

应聘复习基础笔记1:网络编程之TCP与UDP的优缺点,TCP三次握手、四次挥手、传输窗口控制、存在问题

重要性:必考 一.TCP与UDP的优缺点 ①TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端的可靠传输.对可靠性要求较高的应用层协议,如FTP.Telnet.SMTP.HTTP.POP3 ②UDP---用户数据报协议,是一个简单的面向数据报的运输层协议.UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去

TCP/IP详解学习笔记--TCP的基本概念

1.TCP的服务 虽然TCP和UDP最后都会通过IP层传输,但是二者却为用户提供完全不同的服务,TCP提供的是面向连接的,可靠的字节流服务 面向连接意味着俩个使用TCP的应用在彼此交换数据之前必须先建立一个TCP连接.可以用打电话比喻TCP的连接,要想俩个人通话,首先一方要拨通另一方的电话,等待另一方接通电话之后才可以通话,TCP连接只有俩方能通话,这和UDP完全不同 TCP通过如下方式来提供可靠的服务 .应用数据被分成TCP认为最合适的数据块 .当TCP发出一个段时,它启动一个定时器,等待目的

TCP/IP网络编程之基于TCP的服务端/客户端(二)

回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服务端的I/O代码 echo_server.c --while ((str_len = read(clnt_sock, messag, 1024)) != 0) write(clnt_sock, messag, str_len);-- 接着,我们回顾客户端的代码 echo_client.c -- wr

TCP的三次握手以及TCP状态转换图详解

今天来讨论一下TCP的三次握手以及TCP的状态转换图.首先发一个三次握手的流程图如下: 圖 2.4-3.三向交握之封包连接模式A:封包发起当用戶端想要对服务器端发起连接时,就必須要送出一個要求连线的封包,此时用戶端必须随机取用一個大于1024 以上的端口來做为程序通信的通道.然后在 TCP 的表头当中,必须带有 SYN 的主动连线(SYN=1),並并且记下发送给服务器端的序列号(Sequence number = 10001) .B:封包接收与确认封包发送当服务器端收到这个包,并且确定要接受这个

&#39;IOKING&#39; TCP Transmission Server Engine (&#39;云猴&#39;&#169;TCP通讯服务器引擎)(预告版)

关键词: IOKING IOCP TCP  Transmission Server Engine Lock Free Interlocked 云猴完成端口TCP通讯服务器引擎 无锁 原子锁(函数) 'IOKING' TCP Transmission Server Engine ('云猴'?TCP通讯服务器引擎)(预告版) 下载连接: http://download.csdn.net/detail/guestcode/7474171 一.       技术要点 I O C P:基于Windows的

《TCP/IP详解卷2:实现》笔记--UDP:用户数据报协议

用户数据报协议,即UDP,是一个面向数据报的简单运输层协议:进程的每次输出操作只产生一个UDP数据报,从而发送 一个IP数据报. 进程通过创建一个Internet域内的SOCK_DGRAM类型的插口,来访问UDP.该类型插口默认地称为无连接的.每次进程发送 数据时,必须指定目的IP地址和端口号.每次从插口上接收数据报时,进程可以从数据报中收到源IP地址和端口号. UDP插口也可以被连接到一个特殊的IP地址和端口,这样,所有写到该插口的数据报都被发往该目的地,而且只有来自该IP 地址和端口号的数据