TCP/IP详解 第十二章 TCP传输控制协议
内容介绍:
一、TCP的服务
1、尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务,TCP提供一种面向连接的,可靠的字节流服务。
2、TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、***、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
二、TCP的首部与UDP首部对比
思考:tcp与udp最大最小的数据长度?https://blog.****.net/caofengtao1314/article/details/106495232
TCP头部8位字段
1、CWR—拥塞窗口减(发送方降低它的发送速率)
2、ECE—ECN回显(发送方收到了一个更早的拥塞通知)
3、URG—紧急(紧急指针字段有效—很少被使用)
4、ACK—确认(确认号字段有效—连接建立以后一般都是启用状态)
5、PSH—推送(接收方应尽快给应用程序传送这个数据---没被可靠地实现和应用)---基本应用层无法控制,网络层直接控制为PSH
6、RST—重置连接(连接取消,经常是因为错误)
7、SYN—用于初始化一个连接的同步序号
8、FIN—该报文段的发送方已经结束向对方发送数据
思考:URG与PSH的区别?https://blog.****.net/caofengtao1314/article/details/106495269
三、TCP与UDP套接字编程对比
TCP:
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
UDP:
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;
UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;
思考:可不可以从编程模型看到udp与tcp有无链接的区别?
四、TCP与UDP粘包
在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。
TCP的socket编程,收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。
举个栗子:
我们连续发送三个数据包,大小分别是500, 1200 ,5000 ,这三个数据包,都已经到达了接收端的网络堆栈中。
使用UDP协议,不管应用层使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完.
思考:不管应用层使用多大的缓冲区?
如果缓冲区为100字节,那么第一次读100字节,那基于链式传输的就会丢失剩下的400字节,第二次读取100字节,会丢失剩下的1100字节数据。第三次读取100字节会丢失剩下的4900字节数据。
思考:如果缓冲区为2000字节呢?如果为5000字节呢?
如果缓冲区为2000字节,那么第一次读500字节。第二次读取1200字节数据 。第三次读取2000字节会丢失剩下的3000字节数据。
如果缓冲区为5000字节,那么第一次读500字节。第二次读取1200字节数据 。第三次读取5000字节。
如果缓冲区为10000字节,那么第一次读500字节。第二次读取1200字节数据 。第三次读取5000字节。
使用TCP协议,应用层只要把接收的缓冲区大小设置在6700以上,我们就能够一次把所有的数据包接收下来只需要有一次接收动作。实际测试在server accept成功以后,休眠10秒然后才读取客户端的数据,结果一次读取完成。
应用层如果将缓冲区大小设置为100那么就需要读取67次才可以读取完,实际测试总共读取了67次,如果调整各种接收方式可能会出现大于读取67次的情况,但是读取的总得数量仍然是6700个字节,那是因为TCP的拥塞控制和滑动窗口在作怪,。
UDP不存在粘包问题,是由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并一次发送出去。另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)。
TCP由于采用Negal算法优化,并且采用字节流式传输,因此存在粘包的问题。
总结: TCP存在粘包的问题,UDP不存在粘包的问题。
五、TCP与UDP分片
全球互联网 mtu值统计
参考TCP/IP详解卷一,第一版 115页 做的全球互联网实验
作为一个实验,我们多次运行修改以后的traceroute程序,目的端为世界各地的主机,可以到达15个国家(包括南极洲),使用了多个跨大西洋和跨太平洋的链路。但是,在这样做之前,作者所在子网与路由器netb之间的拨号SLIP链路MTU增加到1500与以太网相同。
在18次运行当中,只有其中2次发现的路径MTU小于1500。其中一个跨大西洋的链路MTU值为572,而路由器返回的的是新格式的ICMP差错报文。另外一条链路,在日本的两个路由器之间,不能处理1500字节的数据帧,并且路由器没有返回新格式的ICMP差错报文。把MTU值设成1006则可以正常工作。
从这个实验可以读出结论,现在许多但不是所有的广域网都可以处理大于512自己的分组,利用路径MTU发现机制,应用程序就可以充分利用更大的MTU来发送报文。 对于MTU值,要求主机必须能够接收至少576字节的IP数据报。在许多UDP应用程序设计中,其应用程序数据被限制成512字节或更小,因此比这个限制值小。当然,我们可以通过分析很多路由协议(DNS TFTP BOOTP SNMP)总是发送每份小于512字节的数据。
最大的UDP数据报长度:
理论上,IP数据报的最大长度65535字节,这是由于IP首部16比特总长度字段所限制的。去除20字节IP首部和8字节的UDP首部,UDP数据报中用户数据的最大长度为65507,实际使用socket 编程调用sendto函数也是这样,超过这个值会出现sendto失败。
最大的TCP数据报长度:
使用send函数目前不清楚为send多大就可以失败,但是根据网络定义,这个值完全是根据不同的平台不同的内核实现的。
在抓包过程中影响TCP报文长度的一个值为MSS(最大报文长度)表示TCP传往另一端的最大数据块的长度。当一个连接监理师,连接的双方都要通告各自的MSS。最终的IP数据报通常是40个字节 ,即MSS值最大为1460字节。
对于TCP来说,它是尽量避免分片的,为什么?因为如果在IP层进行分片了话,如果其中的某片的数据丢失了,对于保证可靠性的TCP协议来说,会增大重传数据包的机率,而且只能重传整个TCP分组。
如何避免分片?
在这3次握手中,除了确认SYN分节外,通信的两端还进行协商了一个值,MSS,这个值用来告诉对方,能够发送的TCP分节的大小。这个值一般是取其链路
层的MTU大小减去TCP头部大小和IP头部的大小。MSS=MTU-TCP头部大小-IP头部大小. MTU的值可以通过询问链路层得知。
当两端确认好MSS后进行通信,当TCP层往IP层传输数据时,如果TCP层缓冲区的大小大于MSS,那么TCP层都会将其发送缓冲区中的数据切分成MSS大小的分组进行传输,由于MSS是通过MTU减去TCP头部大小和IP头部的大小计算得出的,MSS肯定比MTU小,那么到IP层的时候就可以避免IP层的分片。
文献【1992】提供了再一个繁忙的NFS服务器上所发生的不同校验和和差错的统计结果,时间持续了40天。
从统计结果可以看出,不要完全相信数据链路(如以太网,令牌环等)的CRC校验。应该始终打开端到端的校验和功能。而且,如果你的数据很有价值,也不要完全相信UDP或TCP的校验和,因为这些都只是简单的校验和,不能检测出所有可能发生的差错。
六、TCP与UDP的区别
。
七、TCP与UDP在实际项目中的应用
udp组播
1、对于网络层校验的问题加入了TAG+CRC32校验+data_len,保证数据头尾的完整性
2、基于UDP的数据最大长度,LXC项目转发效率考虑
①ts 长度188 字节
②lxc_dtv à lxc_server à lxc_proxy
经过了3次转发,200ms从demux读出的数据长度为48128也就是188字节的256倍,而基于目前加密的考虑和mtu的考虑将每次数据转发出去的长度定义为188*4那么经过3次转发需要,总共需要转发3*25=75次,再加上6个线程,基本200ms,需要转发75*6=450次。太浪费CPU了。
解决办法,从最大长度入手,lxc内部直接发送48128*5/4的长度,然后到proxy才按照一个合理的值将数据分成小段发送出去。
udp通信
1、对于网络层校验的问题加入了数据长度,对于互联网保证数据完整性很差
2、为了防止分片将数据长度设计不超过1472,但是基于互联的mtu值考虑,最好小于576-20-8=548
TCP通信
1、对于粘包的问题和网络层校验的问题加入了TAG+CRC32校验+data_len,保证数据头尾的完整性
2、为了防止分片将数据长度设计小于MSS
八、TCP连接的建立与终止
三次握手
四次挥手
九、TCP半关闭
shoutdown函数可以实现半关闭,
close函数实现的是两端都关闭
十、TCP同时打开与关闭
十一、TCP状态转换图
引导:防火墙如何固定TCP客户端与服务端
十二、TCP复位报文段
情况1:到不存在的端口的连接请求
情况2:异常终止一个连接
情况3:检测半打开连接
情况4:时间等待错误