TCP协议是TCP/IP协议族中另一个重要的协议。和ip协议相比,tcp协议更靠近应用层,因此在应用程序中具有更强的可操作性。
从四个方面分析tcp协议:tcp头部信息、tcp状态转移过程、tcp数据流、tcp数据流的控制。
tcp服务的特点
传输层协议主要有两个:tcp协议和udp协议。tcp相对udp特点是:面向连接、字节流、可靠传输。
使用tcp协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。tcp连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。
tcp协议的连接是一对一的。
字节流服务和数据报服务的区别。这种区别对应到实际编程中,则体现为通信双方是否必须执行相同次数的读写操作。
tcp固定头部结构
16位端口号:告知主机该报文段是来自哪里(源端口)以及传给那个上层协议或应用程序(目的端口)的。
32位序号:一次tcp通信(从tcp连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
32位确认号:用作对另一方发送来的tcp报文段的响应。
4位头部长度:标识该tcp头部有多少个32bit(4字节),因为4位最大能表示15.所以tcp头部最长是60字节。
6位标志位:详细参考手册。
16位窗口大小:是tcp流量控制的一个手段。窗口指的是接收通告的窗口。他告诉对方本端的tcp接收缓存区还能容纳多少字节的数据这样对方就可以控制发送数据的速度。
16位校验和:由发送端填充,接收端对tcp报文段执行CRC算法以校验tcp报文段在传输过程中是否损坏(tcp头部和数据部分)。是tcp可靠传输的一个重要保障。
16位紧急指针:是一个正的偏移量。他和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。
使用tcpdump观察tcp连接的建立和关闭
sudo tcpdump -i ens33 -nt '(src 192.168.30.109 and dst 192.168.30.108) or (src 192.168.30.108 and dst 192.168.30.109)'
season@ubuntu:~$ telnet 192.168.30.109
Trying 192.168.30.109...
Connected to 192.168.30.109.
Escape character is '^]'.
Ubuntu 16.04.3 LTS
ubuntu login:
telnet> quit
Connection closed.
抓如下报:
IP 192.168.30.108.59344 > 192.168.30.109.80: Flags [S], seq 592835856, win 29200, options [mss 1460,sackOK,TS val 382619106 ecr 0,nop,wscale 7], length 0
IP 192.168.30.109.80 > 192.168.30.108.59344: Flags [R.], seq 0, ack 592835857, win 0, length 0
IP 192.168.30.108.59346 > 192.168.30.109.80: Flags [S], seq 4029748011, win 29200, options [mss 1460,sackOK,TS val 382622437 ecr 0,nop,wscale 7], length 0
IP 192.168.30.109.80 > 192.168.30.108.59346: Flags [R.], seq 0, ack 4029748012, win 0, length 0
IP 192.168.30.108.46412 > 192.168.30.109.7: Flags [S], seq 3127718471, win 29200, options [mss 1460,sackOK,TS val 382623239 ecr 0,nop,wscale 7], length 0
IP 192.168.30.109.7 > 192.168.30.108.46412: Flags [S.], seq 3459342002, ack 3127718472, win 28960, options [mss 1460,sackOK,TS val 2097790564 ecr 382623239,nop,wscale 7], length 0
IP 192.168.30.108.46412 > 192.168.30.109.7: Flags [.], ack 1, win 229, options [nop,nop,TS val 382623239 ecr 2097790564], length 0
IP 192.168.30.108.46412 > 192.168.30.109.7: Flags [F.], seq 1, ack 1, win 229, options [nop,nop,TS val 382624459 ecr 2097790564], length 0
IP 192.168.30.109.7 > 192.168.30.108.46412: Flags [F.], seq 1, ack 2, win 227, options [nop,nop,TS val 2097791784 ecr 382624459], length 0
因为整个过程中并没有发生应用层的数据的交换,所以tcp报文段的数据部分的长度总是0.
半关闭状态:tcp连接是双全工的,所以他允许两个方向的数据传输被独立关闭。意思就是通信的一端可以发送结束报文段给对方,告诉他本端已经完成了数据的发送,但允许继续接收来自对方的数据,知道对方也发送结束报文段以关闭连接。tcp连接这种状态被称为半关闭。
TIME_WAIT 状态
客户端连接在收到服务器的结束报文段之后,并没有直接进入CLOSED状态。而是转移到TIME_WAIT状态。在这个状态客户端连接要等待一般长为2MSL(报文段最大生存时间,标准文档建议值是2min)的时间,才能完全关闭。
TIME_WAIT 状态存在的原因有两点:可靠地终止tcp连接。保证让迟来的tcp报文段有足够的时间被识别位丢弃。
season@ubuntu:~$ nc -p 12345 192.168.30.108 23
^C
season@ubuntu:~$ nc -p 12345 192.168.30.108 23
nc:bin failed :address already in use'^C
season@ubuntu:~$ net
netcat netkit-ftp netstat networkctl
season@ubuntu:~$ netstat -nat
激活Internet连接 (服务器和已建立连接的)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN
tcp 0 0 192.168.30.108:12345 192.168.30.108:23 TIME_WAIT
season@ubuntu:~$ netstat -nat
激活Internet连接 (服务器和已建立连接的)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN
上述代码显示,客户端程序被中断后,连接进入TIME_WAIT状态,12345端口仍被占用,所以客户端重启失败。
复位报文段
产生复位报文段的3种情况
- 访问不存在的端口
在主机B上执行telnet命令登录A主机上一个不存在的端口54321,并用tcpdump抓取该过程中两台主机交换的tcp报文段。
season@ubuntu:~$ sudo tcpdump -nt -i ens33 port 54321
season@ubuntu:~$ telnet 192.168.30.108 54321
Trying 192.168.30.108...
telnet: Unable to connect to remote host: Connection refused
IP 192.168.30.109.35538 > 192.168.30.108.54321: Flags [S], seq 4136023844, win 29200, options [mss 1460,sackOK,TS val 2102882001 ecr 0,nop,wscale 7], length 0
IP 192.168.30.108.54321 > 192.168.30.109.35538: Flags [R.], seq 0, ack 4136023845, win 0, length 0
由此可见A主机针对B主机回应了一个复位报文段(tcpdump输出R标志)。复位报文段的接收通告窗口大小位0,所以可以预见:收到复位报文段的一端应该关闭连接或者重新连接,而不能回应这个复位报文段。
- 异常终止连接
正常终止的方式:数据交换完成之后,一方给另一方发送结束报文段。tcp提供了异常终止一个连接的方法,给对方发送一个复位报文段。一旦发送了复位报文段。发送端所有排队等待发送的数据都将被丢弃。 - 处理半打开连接
服务端(或客户端)关闭或者异常终止了连接,而对方没有接收到结束报文段(比如发生了网络故障),此时,客户端(或服务器)还维持着原来的连接。而服务器(或)客户端即使重启,也已经没有该连接的任何信息了。这种状态称为半打开状态。
tcp交互/成块数据流
tcp报文段所携带的应用程序数据按照长度分为两种:交互数据和成块数据。
交互数据仅包含很少的字节,使用交互数据的应用程序(或协议)对实时性要求高,比如telnet、ssh等。成块数据的长度则通常位tcp报文段允许的最大数据长度。使用成块数据的应用程序(或协议)对传输效率要求高,比如tcp。
成块数据流:当传输大量大块数据的时候,发送方会连续发送多个tcp报文段,接收方可以一次确认所有这些报文段。
附
- 一个虚拟连接的建立是通过三次握手来实现的
- (B) --> [SYN] --> (A)
假如服务器A和客户机B通讯. 当A要和B通信时,B首先向A发一个SYN (Synchronize) 标记的包,告诉A请求建立连接.
注意: 一个 SYN包就是仅SYN标记设为1的TCP包(参见TCP包头Resources). 认识到这点很重要,只有当A受到B发来的SYN包,才可建立连接,除此之外别无他法。因此,如果你
的防火墙丢弃所有的发往外网接口的SYN包,那么你将不 能让外部任何主机主动建立连接。 - (B) <-- [SYN/ACK] <--(A)
接着,A收到后会发一个对SYN包的确认包(SYN/ACK)回去,表示对第一个SYN包的确认,并继续握手操作.
注意: SYN/ACK包是仅SYN 和 ACK 标记为1的包. - (B) --> [ACK] --> (A)
B收到SYN/ACK 包,B发一个确认包(ACK),通知A连接已建立。至此,三次握手完成,一个TCP连接完成
Note: ACK包就是仅ACK 标记设为1的TCP包. 需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位
- 四次握手用来关闭已建立的TCP连接
- (B) --> ACK/FIN --> (A)
- (B) <-- ACK <-- (A)
- (B) <-- ACK/FIN <-- (A)
- (B) --> ACK --> (A)
注意: 由于TCP连接是双向连接, 因此关闭连接需要在两个方向上做。ACK/FIN 包(ACK 和FIN 标记设为1)通常被认为是FIN(终结)包.然而, 由于连接还没有关闭, FIN包总是打上
ACK标记. 没有ACK标记而仅有FIN标记的包不是合法的包,并且通常被认为是恶意的