TCP定义及特点
定义
TCP是一种面向连接(连接导向)的、可靠的基于字节流的传输层通信协议。TCP将用户数据打包成报文段,发送后会启动一个定时器,然后另一端收到的数据进行确认、对失序的数据重新排序、丢弃重复数据
特点
- TCP是面向连接的传输控制层协议
- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的
- TCP提供可靠交付的服务
- TCP提供全双工通信。数据在两个方向上独立的进行传输,因此,连接的每一端必须保持每个方向上的传输数据序号。
- 面向字节流。面向字节流的含义:虽然应用程序和TCP交互是一次一个数据块,但应用程序交下来的数据仅仅是一连串的无结构的字节流
TCP报文
在开始三次握手之前,看下TCP报文的数据结构
- TCP首部
- TCP数据部分
着重看TCP头部结构,如下
了解一下什么是序列号和确认号
- Sequence number
表示的是我方(发送方)这边,这个packet的数据部分的第一位应该在整个data stream中所在的位置。(注意这里使用的是“应该”。因为对于没有数据的传输,如ACK,虽然它有一个seq,但是这次传输在整个data stream中是不占位置的。所以下一个实际有数据的传输,会依旧从上一次发送ACK的数据包的seq开始)
- Acknowledge number
表示的是期望的对方(接收方)的下一次sequence number是多少,一旦连接建立成功,ACK值一直为1。
三次握手
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
第一次握手:标志位SYN = 1,随机生成一个序列号seq1 = x
第二次握手:标志位SYN ,ACK = 1,确认号ack = x + 1,随机生成一个序列号seq2=y
第三次握手:标志位ACK = 1, 确认号ack = y + 1,seq2= x + 1
SYN/FIN的传输虽然没有data,但是会让下一次传输的packet seq增加一,但是,对于ACK的传输,不会让下一次的传输packet加一,也就是说,下一个实际有数据的传输,依旧从上一次发送ACK的数据包的seq开始计算
网络抓包分析
上面给了具体的连接过程,但是偏理论,下面实战一下,通过抓包进行分析
具体查看每一次握手都做了什么(主要看序列号,确认号)
有图有真相,不怕你不信
参考链接:https://www.jianshu.com/p/15754b4e9458
常见问题
问题一:为什么不是两次握手,而是三次握手
前两次握手客户端可以确认服务端的接收和发送是正常的(一个来回),但是服务端却不知道客户端的接收能力是否正常,那 TCP 的可靠性就无从谈起,所以需要第三次握手来确认双方的收发能力,以确保TCP连接的可靠性。
问题二:TCP的三次握手都可以携带数据吗?如果不是,那么哪一次握手可以携带数据,其他的为啥不能携带数据?
假设第一次握手客户端携带数据到服务器,服务器解析并存储此次连接的信息,如果数据量大,服务端就要分配足够的内存来进行存储,假如有黑客while(1000000000)来新建连接,结果可想而知,服务器将会被占用大量的内存。
第三次握手,此时对于客户端来说,连接已经建立,客户端携带数据完全没有问题
问题三:传输的过程中,报文丢失了怎么办
- 第一次握手报文丢失
客户端发送 SYN 报文,然后进入到 SYN_SENT 状态。
客户端迟迟收不到服务端的 SYN-ACK 报文,就会触发客户端的超时重传机制。
在 Linux 里,客户端的 SYN 报文最大重传次数由/proc/sys/net/ipv4/tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。每次超时的时间是上一次的 2 倍。当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
- 第二次握手报文丢失
第二次握手时,服务端会进入 SYN_RCVD 状态
客户端角度:客户端第一次握手发出去的报文没有得到回复,那么客户端就会觉得自己的 SYN 报文丢失,于是客户端就会触发超时重传机制,重传 SYN 报文。
服务端角度:发送SYN_ACK报文后将会开启一个定时器,如果报文没有得到回应,就会触发超时重传机制,重传 SYN-ACK 报文,重传的次数由/proc/sys/net/ipv4/tcp_synack_retries控制,默认是5次。
- 第三次握手报文丢失
服务端角度:发送SYN_ACK报文后将会开启一个定时器,如果超过了定时器设置的时间都没有收到客户端的ACK,将会重发SYN_ACK包。由/proc/sys/net/ipv4/tcp_synack_retries控制,默认是5次。
客户端角度:此时非彼时,现在我是 ACK 报文,拥有独特的权限,所以我是不会重传的
问题四:什么情况下报文失效或丢弃
- 服务端的半连接队列(syns quene)满了,客户端就一直在超时重传 SYN 报文,直到达到最大的重传次数
- 服务端的连接队列(accept quene)满了
TCP的全连接和半连接队列
当服务端调用listen()函数监听端口的时候,内核会为每个监听的socket创建两个队列
- 半连接队列(syn queue):客户端发送SYN包,服务端收到后回复SYN+ACK后,服务端进入SYN_RCVD状态,这个时候的socket会放到半连接队列。
- 全连接队列(accept queue):当服务端收到客户端的ACK后,socket会从半连接队列移出到全连接队列。当调用accpet函数的时候,会从全连接队列的头部返回可用socket给用户进程。
在4.3版本之前的内核,SYN队列的最大大小以前是用
net.ipv4.tcp_max_syn_backlog
来配置,但是现在已经不再使用了。
现在用net.core.somaxconn
来同时表示SYN队列和Accept队列的最大大小
查看某一个端口(也就是一个服务)的连接状况
ss 命令
ss是Socket Statistics的缩写。顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。
# ss
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp ESTAB 0 0 10.0.2.10:ssh 10.0.2.2:52316
Recv-Q:半连接队列
Send-Q:全连接队列