TCP/IP模型
- 链路层
- 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
- 数据链路层:在不可靠的物理介质上保证可靠的数据帧传输,这一层数据叫做数据帧。
- 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。实现点到点的传输。这一层的数据叫做数据报。
- 传输层:实现一个端口到另一个端口的数据段传输。实现端到端的传输。这一层的数据叫做数据段。
- 应用层:是最靠近用户的层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。
MTU和MSS
- MTU最大传输单元。不同的层的MTU也是不同的,IP协议的数据报最大为65535个字节,所以当IP数据报大于MTU的时候就需要对数据进行分片,这也是IP协议的主要功能之一。
- TCP为了避免被发送方分片,它主动把数据分成小段再交给网络层。最大的分段大小称为MSS(Maximum Segment Size),它相当于把MTU刨去IP头和TCP头之后的大小,所以一个MSS恰好能装进一个MTU中。在三次握手的时候接收方和发送方都会声明自己MSS,因为接收方和发送方的MTU可能是不同的,最终会以较小的MSS为准。
- UDP协议没有MSS的概念,传输层的数据可能一股脑的交给网络层,所以数据可能会被分片而影响性能。
TCP
TCP的结构图
- 序号:Seq(Sequence Number)序号占32位,用来标识从计算机A发送到计算机B的数据包的序号,计算机发送数据时对此进行标记。
- 确认号:Ack(Acknowledge Number)确认号占32位,客户端和服务器端都可以发送,Ack = Seq + 1。
- 首部长度:占4位,表示4字节*首部长度,最大值为15,表示首部长60字节。
- 标志位:每个标志位占用1Bit,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下:
-
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:建立一个新连接。
- FIN:断开一个连接。
TCP三次握手和四次挥手
三次握手
- SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。
- SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。
- ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。
四次挥手
- ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。
- FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。
- CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状态。
- FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器的ACK包,但并没有立即接收到服务端的FIN包,进入FIN_WAIT_2状态。
- LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。
- TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的确认,在此之后的2MSL时间称为TIME_WAIT状态。
关于四次挥手中TIME_WAIT状态等待2MSL的原因
客户端发送最后的ACK报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
- 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
- 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。
设置端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
为什么是三次握手,不是两次?不是四次?
三次握手的原因(相对两次和四次而言)
- 三次握手才可以避免建立多个连接(相对两次)
- 三次握手才可以同步双方的初始序列号,MSS,窗口大小等信息,两次握手最多建立一个单向的连接。
- 三次握手才可以避免资源浪费
- 相对于四次握手而言,减少了连接次数;相对于两次,避免了冗余连接
为什么不是两次?
- 无法防止历史连接的建立,会造成双方资源的浪费
- 无法可靠的同步双方序列号
为什么不是四次?
- 三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数(四次握手讲三次握手的第二步拆成了两步,没必要这样做)
TCP的流量控制
接收方为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收的一种流量控制方法。流量控制根本目的是防止分组丢失,它是构成TCP可靠性的一方面,由滑动窗口协议(连续ARQ协议)实现。滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。
滑动窗口详解
TCP的拥塞控制
拥塞控制是发送方为了避免因为网络拥塞而导致分组丢失所采用的一种发送速率的控制方法。发送方需要根据网络拥塞情况维护一个叫做拥塞窗口(cwnd)的状态变量,控制当前的发送速率。
TCP 主要通过四个算法来进行拥塞控制:
- 慢开始
- 拥塞避免
- 快重传
- 快恢复
慢开始与拥塞避免
- 发送的最初执行慢开始,令 cwnd = 1,当收到确认后,将 cwnd 加倍,2、4、8 ...
- 设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
- 如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。
快重传与快恢复
- 在发送方,如果收到三个重复确认,此时执行快重传,立即重传下一个报文段。
- 执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
流量控制和拥塞控制的区别
- 流量控制属于通信双方协商;拥塞控制涉及通信链路全局。
- 流量控制需要通信双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身 决定,发送窗大小由接收方响应的TCP报文段中窗口值确定
- 拥塞控制的拥塞窗口大小变 化由试探性发送一定数据量数据探查网络状况后而自适应调整。
- 实际最终发送窗口 = min{流控发送窗口,拥塞窗口}
TCP粘包、拆包以及解决办法
粘包/拆包
接收端只收到一个数据包,但是这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。(TCP包中数据的粘合!不是TCP包的粘合,TCP包通过首部能区分开)这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。
接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。
为什么会发生 TCP 粘包、拆包?
- 发送的数据大于 TCP 发送缓冲区剩余空间大小,拆包。
- 发送数据大于 MSS(最大报文长度),拆包。
- 发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,粘包。
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,粘包。
解决办法
由于 TCP 本身是面向字节流的,无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,归纳如下:
- 消息定长:发送端将每个数据包封装为固定长度(不够的可以通过补 0 填充),这样接收端每次接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
- 设置消息边界:服务端从网络流中按消息边界分离出消息内容。在包尾增加回车换行符进行分割,例如 FTP 协议。
- 将消息分为消息头和消息体:消息头中包含表示消息总长度(或者消息体长度)的字段。
TCP心跳机制
tcp维护长连接,一般是在应用层引入心跳机制。就是每隔一段时间发一个探测包,看连接是否顺畅,来确定连接的可用性。
TCP如何实现可靠传输的?
- 序列号:保证按序交付
- 检验和:保证数据不出错
- 流量控制、拥塞控制:调控双方的发送速率,防止因为网络阻塞,缓冲区满等原因丢包
- ACK确认机制、超时重传:保证每一个字节段的交付。
UDP
UDP结构图
TCP和UDP对比
- 是否面向连接 :UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接。
- 是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
- 传输效率 :由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
- 首部开销 :TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
- 是否提供广播或多播服务 :TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
- 是否有状态 :这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情。
- 传输形式 : TCP 是面向字节流的,UDP 是面向报文的。