十五. TCP 三次握手
客户端与服务端建立一个 TCP 连接共计需要发送 3 个包才能完成,这个过程称为三次握手(Three-way Handshake)。如上面所述,数据段的序号、确认序号以及滑动窗口大小都在这个过程中完成。socket 编程中,客户端执行 connect() 时,将触发三次握手。
所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。
- 第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。序列号的实现目前会随着时间的变化而变化,所以每次建立连接时的序列号都不同
发送完毕后,客户端进入 SYN_SEND 状态。
- 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。
- 第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1
发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。
三次握手的过程的示意图如下:
![](https://img.haomeiwen.com/i21588374/66b4637822f7d589.png)
![](https://img.haomeiwen.com/i21588374/9dbf579e94584ef5.png)
为什么是三次握手
根据一般的思路,我们可能会觉得只要两次握手就可以了,第三步确认看似是多余的。那么 TCP 协议为什么还要费力不讨好的加上这一次握手呢?
这是因为在网络请求中,我们应该时刻记住:“网络是不可靠的,数据包是可能丢失的”。假设没有第三次确认,客户端向服务端发送了 SYN,请求建立连接。由于延迟,服务端没有及时收到这个包。于是客户端重新发送一个 SYN 包。回忆一下介绍 TCP 首部时提到的序列号,这两个包的序列号显然是相同的。
假设服务端接收到了第二个 SYN 包,建立了通信,一段时间后通信结束,连接被关闭。这时候最初被发送的 SYN 包刚刚抵达服务端,服务端又会发送一次 ACK 确认。由于两次握手就建立了连接,此时的服务端就会建立一个新的连接,然而客户端觉得自己并没有请求建立连接,所以就不会向服务端发送数据。从而导致服务端建立了一个空的连接,白白浪费资源。
在三次握手的情况下,服务端直到收到客户端的应答后才会建立连接。因此在上述情况下,客户端会接受到一个相同的 ACK 包,这时候它会抛弃这个数据包,不会和服务端进行第三次握手,因此避免了服务端建立空的连接。
SYN攻击:
1. 什么是 SYN 攻击(SYN Flood)?
在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.
SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。
SYN 攻击是一种典型的 DoS/DDoS 攻击。
2. 如何检测 SYN 攻击?
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。
HTTP
# netstat -na TCP | grep SYN_RECV | more,
3. 如何防御 SYN 攻击?
SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies技术
十六. TCP 四次挥手
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
- 第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
- 第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK。
- 第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。
四次挥手的示意图如下:
![](https://img.haomeiwen.com/i21588374/7853f59a2f228f2e.png)
![](https://img.haomeiwen.com/i21588374/c69be6ba43f1082d.png)
TCP 为什么要进行四次挥手? / 为什么 TCP 建立连接需要三次,而释放连接则需要四次?
因为 TCP 是全双工模式,客户端请求关闭连接后,客户端向服务端的连接关闭(一二次挥手),服务端继续传输之前没传完的数据给客户端(数据传输),服务端向客户端的连接关闭(三四次挥手)。所以 TCP 释放连接时服务器的 ACK 和 FIN 是分开发送的(中间隔着数据传输),而 TCP 建立连接时服务器的 ACK 和 SYN 是一起发送的(第二次握手),所以 TCP 建立连接需要三次,而释放连接则需要四次。
为什么 TCP 连接时可以 ACK 和 SYN 一起发送,而释放时则 ACK 和 FIN 分开发送呢?(ACK 和 FIN 分开是指第二次和第三次挥手)
因为客户端请求释放时,服务器可能还有数据需要传输给客户端,因此服务端要先响应客户端 FIN 请求(服务端发送 ACK),然后数据传输,传输完成后,服务端再提出 FIN 请求(服务端发送 FIN);而连接时则没有中间的数据传输,因此连接时可以 ACK 和 SYN 一起发送。
为什么客户端释放最后需要 TIME-WAIT 等待 2MSL 呢?
- 为了保证客户端发送的最后一个 ACK 报文能够到达服务端。若未成功到达,则服务端超时重传 FIN+ACK 报文段,客户端再重传 ACK,并重新计时。
- 防止已失效的连接请求报文段出现在本连接中。TIME-WAIT 持续 2MSL 可使本连接持续的时间内所产生的所有报文段都从网络中消失,这样可使下次连接中不会出现旧的连接报文段。
如果在处于 TIME_WAIT 状态的客户端返回 ACK 丢失,会发生什么呢?服务端由于没有接收到 ACK 号,可能会重新发送一次 FIN 。这个时候如果客户端不等待一段时间,直接关闭连接,那么通信中对应的端口号也会释放出来了,这时候如果正好有应用程序创建套接字,又恰好分配了这同一个端口号,接着服务器的 FIN 包又刚好发到。本来是要关闭之前的连接,但是由于端口号相同,这个 FIN 就开始断开这个刚刚建立的新连接。这所以需要等待一段时间,就是为了防止这种误操作。
![](https://img.haomeiwen.com/i21588374/d658614651e2ea77.png)
常用端口
知名端口号一般由 0 - 1023 的数字分配而成。
![](https://img.haomeiwen.com/i21588374/255ed8e88dae357c.png)
TCP 代表的知名端口:
![](https://img.haomeiwen.com/i21588374/9104fbc2d975a398.png)
UDP 代表的知名端口:
![](https://img.haomeiwen.com/i21588374/15fe53babb4a38a7.png)
十七. Socket Interfaces
从 Linux 内核的角度来看,一个套接字就是通信的一个端点。 从 Linux 程序的角度来看,套接字是一个有相应描述符的文件。 普通文件的打开操作返回一个文件描述字,而 socket() 用于创建一个 socket 描述符,唯一标识一个 socket。 这个 socket 描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些操作。
常用的函数有:
socket()
bind()
listen()
connect()
accept()
write()
read()
close()
Socket 的交互流程
![](https://img.haomeiwen.com/i21588374/bb0ccc72e4930150.png)
![](https://img.haomeiwen.com/i21588374/12f1fc2bcb875dec.png)
图中展示了 TCP 协议的 socket 交互流程,描述如下:
- 服务器根据地址类型、socket 类型、以及协议来创建 socket。
- 服务器为 socket 绑定 IP 地址和端口号。
- 服务器 socket 监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的 socket 并没有全部打开。
- 客户端创建 socket。
- 客户端打开 socket,根据服务器 IP 地址和端口号试图连接服务器 socket。
- 服务器 socket 接收到客户端 socket 请求,被动打开,开始接收客户端请求,知道客户端返回连接信息。这时候 socket 进入阻塞状态,阻塞是由于 accept() 方法会一直等到客户端返回连接信息后才返回,然后开始连接下一个客户端的连接请求。
- 客户端连接成功,向服务器发送连接状态信息。
- 服务器 accept() 方法返回,连接成功。
- 服务器和客户端通过网络 I/O 函数进行数据的传输。
- 客户端关闭 socket。
- 服务器关闭 socket。
这个过程中,服务器和客户端建立连接的部分,就体现了 TCP 三次握手的原理。
Reference:
《图解 TCP/IP》
《TCP/IP 详解 卷1:协议》
《网络是怎样连接的》
网友评论