网络编程杂谈之TCP协议
TCP协议属于网络分层中的传输层,传输层作用的就是建立端口与端口的通信,而其下一层网络层的主要作用是建立"主机到主机"的通信,所以在我们日常进行网络编程时只要确定主机和端口,就能实现程序之间的数据交流,在Unix系统中就把主机+端口,叫做"套接字"(socket),所以一般网络编程都是基于对于socket的操作来做的。
TCP协议其实是一个非常复杂的协议,做过网络编程开发的都听过一句话‘’TCP本身是一种可靠的协议”,但正是为了保证可靠性,TCP 内部使用了如各种重传与控制算法,所以 TCP 是一个内部原理复杂,但是使用起来比较简单的这么一个协议。
下面我们对TCP协议进行一个基本的介绍,本文只是站在应用的角度上阐述,相比与真正的深入其中还是比较浅显的。
一、TCP协议格式
首先主要看下TCP协议的头格式
其中各字段的意义如下:
3、包序号(Sequence Number):32位的SN序列号标识了TCP报文中第一个byte在对应方向的传输中对应的字节序号,用来记录网络包顺序,解决传输中的乱序、重复问题,比如发送端发送的一个TCP包净荷(不包含TCP头)为10byte,SN为5,则发送端接着发送的下一个数据包的时候,SN应该设置为5+10=15。通过序列号,TCP接收端可以识别出重复接收到的TCP包,从而丢弃重复包,同时对于乱序数据包也可以依靠系列号进行重排序,进而对高层提供有序的数据流。另外如果接收的包中包含SYN或FIN标志位,逻辑上也占用1个byte,应答号需加1。
4、确认号(Acknowledgement Number):32位的ACK标识了报文发送端期望接收的字节序列,如果设置了ACK控制位,这个值表示一个准备接收的包的序列码,注意是准备接收的包,比如当前接收端接收到一个净荷为10byte的数据包,SN为5,则会回复一个确认收到的数据包,如果这个数据包之前的数据也都已经收到了,这个数据包中的ACK Number则设置为10+5=15,表示之前的数据都已经收到了,准备接受SN=15的数据包。
5、窗口(Advertised-Window):著名的滑动窗口(Sliding Window),用于TCP的流量控制。
6、状态位(TCP-FLAG):包的类型,用于操作TCP的状态机,其8位状态分别表示如下含义
CWR(Congestion Window Reduce) 0x80:拥塞窗口减少标志set by sender,用来表明它接收到了设置ECE标志的TCP包。并且sender 在收到消息之后已经通过降低发送窗口的大小来降低发送速率。
ECE(ECN Echo) 0x40:ECN响应标志被用来在TCP3次握手时表明一个TCP端是具备ECN功能的。在数据传输过程中也用来表明接收到的TCP包的IP头部的ECN被设置为11。
URG(Urgent) 0x20:该标志位表示紧急(The urgent pointer) 标志有效,目前已经很少使用参考后面流量控制和窗口管理部分的介绍。
ACK 0x10:取值1代表Acknowledgment Number字段有效,这是一个确认的TCP包,取值0则不是确认包。后续文章介绍中当ACK标志位有效的时候我们称呼这个包为ACK包,使用大写的ACK称呼。
PSH(Push) 0x08:该标志置位时,一般是表示发送端缓存中已经没有待发送的数据,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。
RST(Reset) 0x04:用于reset相应的TCP连接。通常在发生异常或者错误的时候会触发复位TCP连接。
SYN 0x02:同步序列编号(Synchronize Sequence Numbers)有效。该标志仅在三次握手建立TCP连接时有效。
FIN(Finish) 0x01:No more data from sender。当FIN标志有效的时候我们称呼这个包为FIN包。
7、校验位(Checksum):16位TCP头。发送端基于数据内容计算一个数值,接收端要与发送端数值结果完全一样,才能证明数据的有效性。接收端checksum校验失败的时候会直接丢掉这个数据包。CheckSum是根据伪头+TCP头+TCP数据三部分进行计算的。
8、紧急指针(Urgent Pointer):16位,指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。
9、选项(Option):长度不定,但长度必须以是32bits的整数倍。常见的选项包括MSS、SACK、Timestamp等等。
二、TCP状态机
关于TCP的状态机理解我们从几张经典的示意图开始
TCP状态转换图
TCP三次握手、四次挥手时序图
基于TCP的网络编程中链接的建立断开、数据发送都是依赖TCP状态转换实现的,例如所谓的建立链接并不是真正的链接,而是一种状态的维持,表面上的链接其实是通讯双方共同维护了一个“链接状态”,而建立链接--数据传输--断开链接的TCP通信过程,也是这些状态转换的过程,这其中状态的转换一部分是收到或发送的某个控制位字段的变化而引起的,如SYN、FIN、ACK等,还有一些是由于应用程序的动作或计时器超时引发的。
了解了以上的内容,下面我们就结合实际报文数据,对TCP链接三次握手,数据传输,断开四次挥手,进行一个简单的跟踪验证;
三、TCP通讯
1、三次握手
建立链接的三次握手的作用主要是初始化Sequence Number 的初始值,同时把这个值通过Synchronize Sequence Numbers(SYN包)告知对端。
通过Wireshark可以捕获到三次握手的报文
握手流程:
1、client首先初始化该值,发送一个SYN包给server端,告诉server端一个初始化的SN值
2、server收到client发送的数据,回复一包数据,包括ACK确认与SYN, 既要告诉client端收到了数据,同时告知对方SYN值;
3、client回复ACK确认包,告知server端收到了数据;
2、数据传输
通过Wireshark,我们可以看下TCP传输中一包数据的组成,对照前面的协议组成,可以看到这里我们发送的是0x11,0x11两个字节的数据
可以看到接收一段回复的确认包里ACK从1变成了3,为保证数据的顺序性与可靠性,TCP是有一整套的机制来控制的,如大家熟悉的滑动窗口、超时重传等;
这里有一个需要注意的细节,这里ACK确认号的真实值其实是从0xffeb49ed 变为 0xffed49ef的,这是由于当某个主机开启一个TCP会话时,他的初始序列号与确认号是随机的,可能是0和4,294,967,295之间的任意值,在Wireshark里显示的都是相对序列号/确认号,而不是实际序列号/确认号,相对序列号/确认号是和TCP会话的初始序列号相关联的。这里Wireshark为方便大家跟踪查看显示的是相对值,因为比起真实序列号/确认号,跟踪更小的相对序列号/确认号会相对容易一些。
3、四次挥手
断开链接的四次挥手的作用主要是回收资源,停止数据传输。由于TCP是全双工的,需要client与server两端分别断开各自的通向对方的通道。
通过Wireshark可以捕获到四次挥手的报文
挥手流程:
1、client端发送一个FIN包告诉server服务端已经没有数据要传输了,准备断开链接;
2、server端回复一个ACK确认包,也就是告诉cient端,好,我知道你要断开了;
3、server端这时要看自己还有没有数据要发送给client,如果没有了,也要发送一个FIN包告诉client端,我也没有数据要传输了,准备断开链接;
4、client端回复一个ACK确认包,告诉server端,好的,我知道你要断开了;
四次挥手的流程中,sever端在接收到client端的断开要求后,ACK确认包与FIN包是否可以合并为一个包来发送,也就是四次挥手是否可能变成三次挥手,答案是可能的,但由于TCP是全双工的,server端与client端数据传输的终止在时序上是独立且可能相隔较长时间,那么一般情况下一个完整的断开链接操作都是需要四次挥手来完成的。
到这里针对TCP协议,以及链接->传输->断开链接流程的基本介绍与说明就结束了,后续针对网络编程这一块我会接着写几篇文章,一是对自己工作中涉及到一些网络编程的内容进行梳理与总结,另一方面希望能从下至上的加强自己对网络编程这块认知的深度,也希望对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。