TCP 的基本信息
- 序列号:在建立连接时,由计算机随机生成的随机数,作为其初始值,通过SYN 包传给接收端的主机,每发送一次数据,就累加一次该数据字节数大小,用来解决网络包乱序问题。
- 确认应答号:指下一次期望收到的数据的序列号,发送端收到这个确认应答以后,可以认为在这个序号之前的数据都被正常接收,用来解决不丢包问题。
- 控制位:ACK 该位为1时,确认应答的字段变为有效,TCP 规定除了最初建立连接的SYN包之外,该位必须设置为1。 RST为1,表示TCP连接中出现异常必须强制断开连接。 SYN:该位为1时,表示希望建立连接,在其序列号的字段进行序列号的初始值的设定,FIN 为1时,表示今后不会再有数据发送,希望断开连接。
IP 层是不可靠的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。如果要保证网络数据包的可靠性,那么就需要上层的TCP协议负责。他保证接收端接受的网络包时无损坏、无间隔、非冗余和按序的。TCP的链接是字节流形式,无论我们消息有多大都可以进行传输,并且消息是有序的,对前一个消息没有接受的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。
建立一个TCP 连接时需要客户端以及服务端达成上述的三个信息的共识:
- Socket: 由IP 地址和端口号组成
- 序列号:用来解决乱序问题
- 窗口大小: 用来做流量控制
建立一个TCP四元组包括如下:
- 源地址
- 源端口
- 目的地址
- 目的端口
一旦完成三次握手,双方都处于ESTABLISHED状态,此时连接就已经建立完成了,客户端和服务端就可以相互发数据了。
为什么是三次握手?不是两次、四次?
- 避免历史连接
三次握手的首要原因就是为了防止旧的重复连接初始化造成混乱。
- 同步双方初始序列号
序列号是可靠传输的一个关键因素,他的作用是:1、接受方可以去除重复数据,2、接受方可以根据数据包中的序列号按序接受 3、可以标识发送出去的数据包,那些是已经被对方收到的。
避免资源浪费
如果客户端的 SYN 阻塞了,重复发送多次SYN 报文,因为服务器不清楚客户端是否收到了自己发送的建立连接的ACK 确认信号,所以每收到一个SYN,就只能先主动建立一个连接。这样造成了资源的浪费。
为了达到最佳的传输效能,TCP 协议在建立连接的时候通常需要协商双方的MSS值,当TCP层发现数据超过了MSS时,则就会先进行分片,当然由它形成的IP包的长度也就不会大于MTU, 自然也就不会进行IP 分片了。经过一个TCP 分片之后,进行重发的时候是以MSS为单位,而不用重传所有的分片,大大增加了重传的效率。
SYN 攻击
TCP 建立连接时需要三次握手的,假设攻击者短时间内,伪造了不同的IP 地址的SYN 报文,服务端每接受到一个SYN 报文就进入SYN_RCVD 状态,但是服务端发送出去的ACK + SYN 报文,无法得到未知IP 主机的ACK应答,久而久之就会沾满服务端的SYN 接受队列,使得服务器不能为正常用户服务。
LINUX 内核SYN(未完成连接建立) 队列与Accpet(以完成连接建立)队列是如何工作的?
- 当如无端接收到客户端的SYN 报文时候,将其加入到内核的SYN 队列
- 接着发送SYN + ACK给客户端,等待客户端回应ACK 报文。
- 服务端接收到ACK 报文后,从SYN 队列移除到Accept 队列。
- 应用通过调用accpet() socket 接口,从 Accept 队列中取出连接。
TCP 连接的断开
只有主动关闭连接的,才会有TIME_WAIT状态
需要TIME_WAIT状态主要是两个原因:
- 防止旧的连接的数据包,经过2MSL这个时间,足以让两个方向上的数据包都被丢弃,使原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
- 等待足够的时间保证最后的ACK能让被动关闭方接受,从而帮助其正常关闭。
TIME_WAIT 状态主要的危害有两种: - 对内存资源占用
- 对端口资源的占用,一个TCP 连接至少消耗一个本地端口。
Socket 编程
- 服务端和客户端初始化socket,得到文件描述符
- 服务端调用 bind 将绑定在IP地址和端口
- 服务端调用 listen, 进行监听
- 服务端调用accept, 等待客户端连接
- 客户端调用connect, 向服务器端的地址和端口发起连接请求
- 服务器端调用accept 返回用于传输的 socket 文件描述符;
- 客户端调用 write 写入数据; 客户端调用read 读取数据;
- 客户端断开连接,会调用close, 那么服务端 read 读取数据的时候,就会读取到EOF, 待处理完数据之后,服务端会调用close, 表示连接关闭。
注意的是当服务端调用accept 时,连接成功了就会返回一个已完成的socket,后续用来传输数据,所以监听的socket 和真正用来传送数据的socket 时俩socket,一个作监听socket, 一个叫做已完成连接socket。
- LINUX 内核中会维护两个队列
未完成连接队列(SYN队列):接到一个SYN建立连接请求,处于SYN_RCVD 状态
已完成的连接队列(Accept队列): 已经完成了TCP的三次握手过程,处于ESTABLISHED 状态。
客户端的connect 成功返回时在第二次握手,服务端accept 成功返回时在第三次握手