三次握手
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。 
- 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
- 客户端会随机初始化序号( client_isn ),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不 包含应用层数据,之后客户端处于 SYN-SENT 状态。
- 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号( server_isn ),将此序号填入TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1 , 接着把 SYN和 ACK 标志位置为 1 。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
- 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位 置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客 户到服务器的数据,之后客户端处于 ESTABLISHED 状态。
- 服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。
第三次握手是可以携带数据的,前两次握手是不可以携带数据的
为什么是三次握手?不是两次、四次?
简单来说,因为三次握手才能保证双方具有接收和发送的能力。
深入一点来讲的话分为三点:
原因一:避免历史连接
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
- 比如一个旧的过期的请求发到了服务端,服务端回复ack+syn报文给客户端。
- 客户端可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时) 。
- 那么就可以在第三握手时回复RTS报文给服务端,表示中止这一次连接。
- 如果是2次,可能就直接建立了连接。
原因二:同步双方初始序列号
客户端发送携带「初始序列号」的 SYN 报文的时 候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序 列号」给客户端的时候,依然也要得到客户端的应答回应,这样才能确保双方的初始序列号能被可靠的 同步。
原因三:避免资源浪费
如假设服务端接收到客户端的连接请求后,回复syn+ack,但是由于网络问题,这个报文丢失了。由于没有第三次握手,服务端也不知道具体情况,于是就**建立了多个冗余的无效链 接,造成不必要的资源浪费。
- 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号
- 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
为什么客户端和服务端的初始序列号 ISN 是不相同的?/序列号为什么随机
- 每次建立连接前新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。
- 另一方面是为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。
初始序列号 ISN 是如何随机产生的?
ISN = M + F
M 是一个计时器,这个计时器每隔 4 毫秒加 1。
F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。
MTU 和 MSS

- MTU :一个网络包的最大⻓度,以太网中一般为 1500 字节;
- MSS :除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大⻓度;
IP 层会分片,为什么 TCP 层还需要 MSS 呢?
IP 层本身没有超时传机制,它由传输层的 TCP 来负责超时和重传。
当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时 后,就会发「整个 TCP 报文(头部 + 数据)」。
那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。
为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的⻓度也就不会大于 MTU ,自然也就不用 IP 分片了。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用传所有的分片,大大 增加了传的效率。
什么是 SYN(ddos)攻击?如何避免SYN 攻击?
当服务端收到连接请求后,会发送syn+ack回复,自己进入send-rcvd状态,同时会把请求放入半连接队列。
假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到 一个 SYN 报文,就进入 SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。
如何避免SYN 攻击?
避免 SYN 攻击方式一
其中一种解决方式是通过修改 Linux 内核参数,控制队列大小和当队列满时应做什么处理。
避免 SYN 攻击方式二
受到 SYN 攻击:如果不断受到 SYN 攻击,就会导致「 SYN 队列」被占满。
tcp_syncookies 的方式可以应对 SYN 攻击的方法: net.ipv4.tcp_syncookies = 1 
- 当 「 SYN 队列」满之后,后续服务器收到 SYN 包,不进入「 SYN 队列」;
- 计算出一个 cookie 值,再以 SYN + ACK 中的「序列号」返回客户端,
- 服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到「 Accept 队列」。
- 最后应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。
