TCP-三次握手原创
TCP-三次握手
最近对计算机网络蛮感兴趣的,借着这股劲,来系统研究研究 TCP 协议。
先来个经典的三次握手问题
一问,为什么要三次握手?
首先 TCP 协议底层是 IP层,IP层 本身是不可靠的
TCP 协议的出现,就是要创建一个可靠的协议,区别于 UDP协议
TCP 为了提供可靠的服务,得先建立起连接,保证通讯双方至少是可达的
这个过程就像是打电话一样,如果对方都没有接听你的电话,你一直在电话这头说话,谁能听得到呢
图文详解
建立一条端对端的连接
主动发起连接的端称客户端
被动接受连接的端称服务端
先上一张图,看看客户端发起三次握手后两端的状态变更情况:
客户端状态变更
- 开始时是 关闭的状态「CLOSED」
- 当客户端向服务端发送同步请求的时候,状态变为 同步发送状态「SYN_SENT」
- 当接受服务端的响应时,状态变更为 已建立状态「ESTABLISHED」
服务端状态变更
- 开始时也是 关闭的状态 「CLOSED」
- 当服务端唤起系统调用 bind、listen 后状态变更为 监听状态「LISTEN」
- 当服务端接受到客户端的同步请求后,状态变更为 同步接收状态 「SYN_RCVD」
- 当服务端接受到客户端的ACK响应后,状态变更为 已建立状态「ESTABLISHED」
注: CLOSED 是一种假想的状态,所以在使用 netstat 命令查看state的时候并不能展示这种状态
触发三次握手
如何触发三次握手呢?有很多的方法
比如你可以通过 Java 网络编程编写一个服务端 ServerSocket,监听特定的端口
再编写一个客户端连接这个服务端
这里介绍一种更快捷的方法
- 打开命令行,输入下面的命令,创建一个服务端
nc -l 9999
- 再打开另一个命令行,连接服务端
telnet 127.0.0.1 9999
打开 Wireshark就可以抓到相应的TCP包了
在 filter 输入 tcp and tcp.port == 9999 ,只抓取 tcp协议并且tcp的端口号为 9999 的网络包
不熟悉 TCP 协议的同学看到上图这一堆的数值可能还是会有点迷糊
那就先介绍一下TCP头的结构
TCP 头部结构的大小为20到60个字节,按照顺序有
-
16位的源端口号 :表示该包从哪个端口发来的
-
16位的目的端口号 :表示该包要发往的目的端口号
-
32位的序列号 :因为TCP是面向字节流的,这个序列号表示这个包的第一个字节在字节流中的序号
-
32位的确认号 :发送确认(ACK)一端期望下次接收的序列号
-
4位首部长度 :指TCP头部长度为多少个32位
-
6位保留
-
6位标志位
- URG : urgent 紧急标志 ,标记该包优先级高
- ACK :ackownledegment 应答响应标志
- PSH :push 立即发送包,不缓存
- RST :reset 重置连接,表示该连接不再接受请求
- SYN : syn 发送同步包,建立同步连接
- FIN :fin 断开连接
-
16位窗口大小 :接受方的接受窗口大小,对发送方进行流量的控制
-
16位校验和
-
16位紧急指针 :和 URG 标记一起使用,URG标志位为1的时候,这个值才生效,这个值为偏移量,表示区间内的数据为紧急数据 [seq,seq+offset]
-
可选字段,如最大报文长度MSS
接着分析三次握手的报文
- 第一个报文,看上图 NO为 402 的报文
这是由客户端发送到服务端的报文
- 序列号为 3116214529
- 确认号为 0
- 来源端口号 55848
- 目的端口号 9999
- flags 标志只有 SYN 是置为1的
- 窗口大小为 65535
- options 有MSS,值为16344
- 第二个报文
这个报文是从服务端发送给客户端的
- 序列号为 940657738
- 确认号为 3116214530,等于第一个报文的 序列号+1
- 来源端口号 9999
- 目的端口号 55848
- flags 标志 SYN、ACK 是置为1的
- 窗口大小为 65535
- options 有MSS,值为16344
- 第三个报文
这个报文是从客户端发送给服务端的
- 序列号为 3116214530
- 确认号为 940657739,等于第二个报文的 序列号+1
- 来源端口号 55848
- 目的端口号 9999
- flags 标志 ACK 是置为1的
- 窗口大小为 6379
三次握手是 TCP 建立连接的过程,需要关注的点在于报文的flags变化,window,mss 等