# TCP/IP协议入门

## 1. 简介

Transmission Control Protocol/Internet Protocol的简写，即传输控制协议/因特网互联协议。它是网络通信的一套协议集合。

从名字上来看可能会被认为是TCP和IP协议，实际生活中有时也确实就是指这两种协议，然而在很多情况下，它只是利用IP进行通信时必须用到的协议群的统称。具体来说，IP或ICMP、TCP或UDP、TELNET或FTP，以及HTTP等都属于TCP/IP的协议。

先来看一下OSI和TCP/IP模型：

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2F0dfcdb364bb225dc7b769c6e40be21eb5d3cbcae.png?generation=1589894632404471\&alt=media)

#### 应用层

就是应用软件使用的协议，如邮箱使用的POP3（接收电子邮件），SMTP（发送电子邮件）、远程登录使用的Telnet、获取IP地址的DHCP、域名解析的DNS、网页浏览的HTTP协议、AMQP（高级消息队列协议）等；这部分协议主要是规定应用软件如何去进行通信的。

#### 表现层

将应用处理的信息转换为适合网络传输的格式，或将来自下一层的数据转换为上层能够处理的格式。因此它主要负责数据格式的转换。

#### 会话层

决定采用哪种连接方式，比如有5封邮件需要发送，是建立一次发一封还是建立5个连接同时发送5封。

#### 传输层

建立连接并将一个数据/文件斩件分成很多小段，标记顺序以被对端接收后可以按顺序重组数据，另外标记该应用程序使用的端口号及提供QOS。TCP（传输控制协议）和UDP（用户数据报协议）就是属于传输层协议。

#### 网络层

将数据传输到目标地址。目标地址可以是多个网络通过路由器连接而成的某一个地址。因此这一层主要负责寻址和路由选择。IP协议属于网络层。

#### 数据链路层

负责物理层面上互连的、节点之间的通信传输、例如与1个以太网相连的2个节点之间的通信，跨VLAN（虚拟局域网）间的访问，需要上升到网络层。

#### 物理层

将数据最终编码为用0、1标识的比特流，然后传输。（例如将题主头像的图片，变为一串01100111100这样的数字来表示），比如线路，无线电，光纤，信鸽等。

**一个完整的网络传输的过程：**&#x5E94;用层协议将各种协议数据组装好，然后交给表现层转成统一的适合网络传输的格式，接着会话层负责建立会话连接，连接建立完成，传输层负责传输数据，那么传输的方向呢，这个时候网络层就指明了传输的地址，因为互联网中间有很多路由节点，一般不会只主机A直接传输数据到主机B，因此需要网络层来决定走哪条路进行传输，然后数据链路层负责物理层面的连接，数据传输，最后交给物理层比如光纤将信号传输到对端。

**TCP，IP，HTTP联系**：把IP想像成一种高速公路（网络层），它允许其它协议在上面行驶并找到到其它电脑的出口。TCP和UDP是高速公路上的“卡车”（传输层），它们携带的货物就是像HTTP，文件传输协议FTP这样的协议（应用层）等。

下面从一个发邮件的例子看7层协议的作用：

**场景：**&#x41;给B发了一封电子邮件，内容为“早上好”。

* 应用层：将写好的电子邮件按照协议包装好，包含收件人邮箱，内容等；
* 表示层：应用程序对内容进行编码处理，例如日文邮件使用ISO-2022-JP或UTF-8编码；
* 会话层：编码后，实际邮件不一定立马发出去，有些邮件客户端有一次同时发送多个邮件的功能，也可能用户点击“收信”按钮后再一并发送邮件，会话层具有管理这种何时发送数据的功能；
* 传输层：TCP根据应用的指示，建立连接，发送数据以及断开连接；
* 网络层：IP将TCP包作为自己的数据并添加IP首部，生成IP包，IP包首部包含接收此包的地址以及发送端地址，然后参考路由控制表决定此IP包的路由或主机，随后IP包将被发送给连接这些路由器或主机网络接口的驱动程序，以实现真正发送数据；
* 数据链路层：以太网驱动（网络接口）接收到IP包后附加上以太网首部，包含接收端的MAC地址，发送端MAC地址以及标志以太网类型的数据；
* 物理层：根据以太网首部信息数据，通过电缆或者光纤等传输给接收端；

接收端接收到数据后是一个反向拆包解析的过程，这里就不描述了。

## 2. TCP的三次握手和四次挥手

先来看一张经典的图，这个图清晰简明的体现了TCP三次握手和四次挥手的流程。

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2F55b45ce41d6b008ab2764ab564b41fc54651e278.jpeg?generation=1589894631859036\&alt=media)

#### 来解释其中的名词：

**SYN：**&#x5373;Synchronization，表示同步序号，用来建立连接，当SYN=1而ACK=0时，表明这是一个连接请求报文。对方若同意建立连接，则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接收报文。

**Seq：**&#x5373;Sequence Number，用来标识从TCP发送端向TCP接收端发送的数据字节流，它表示在这个报文段中的第一个数据字节在数据流中的序号；主要用来解决网络报乱序的问题。

**ACK：**&#x6B64;标志表示应答域有效，就是说前面所说的TCP应答号将会包含在TCP数据包中；有两个取值：0和1，为1的时候表示应答域有效，反之为0；TCP协议规定，只有ACK=1时有效，也规定连接建立后所有发送的报文的ACK必须为1。**（注意：和下面的Acknowledgment Number不一样，可以理解ACK是Acknowledgment Number的标志位）**

**Acknowledgment Number：**&#x33;2位确认序列号包含发送确认的一端所期望收到的下一个序号，因此，确认序号应当是上次已成功收到数据字节序号加1。不过，只有当标志位中的ACK标志（下面介绍）为1时该确认序列号的字段才有效。主要用来解决不丢包的问题；

**FIN：**&#x5373;finis，终结的意思， 用来释放一个连接。当 FIN = 1 时，表明此报文段的发送方的数据已经发送完毕，并要求释放连接。

### 三次握手过程

#### 第一次握手：

建立连接。客户端A发送连接请求报文段，SYN为1，seq为x，然后，客户端A进入SYN\_SEND状态，等待服务器B的确认，服务端B由SYN=1知道，A要求建立联机；

#### 第二次握手：

服务端B收到请求后要确认联机信息，向A发送ack number=(A的seq+1)，SYN=1，ACK=1，随机产生seq的包，此时服务器进入SYN\_RECV状态；

#### 第三次握手：

客户端A收到后检查ack number是否正确，即第一次发送的seq+1,以及位码ACK是否为1，若正确，客户端A会再发送ack number=(服务端B的seq+1)，ack=1，主机B收到后确认seq值与ack=1则连接建立成功，客户端和服务器端都进入ESTABLISHED状态，完成TCP三次握手。

完成了三次握手，客户端和服务器端就可以开始传送数据。以上就是TCP三次握手的总体介绍。

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2Fe73126ec4f3c49f89b08cd9fabde68c78badcefc.jpeg?generation=1589894632097601\&alt=media)

#### 三次握手的一个实例：

**第一次握手：**&#x5BA2;户端A发送位码syn＝1,随机产生seq number=3626544836的数据包到服务端B，服务端B由SYN=1知道客户端A要求建立联机；

**第二次握手：**&#x670D;务端B收到请求后要确认联机信息，向客户端A发送ack number=3626544837，syn=1，ack=1，随机产生seq=1739326486的包；

**第三次握手：**&#x5BA2;户端A收到后检查ack number是否正确，即第一次发送的seq number+1,以及位码ack是否为1，若正确，客户端A会再发送ack number=1739326487，ack=1，服务端B收到后确认seq=seq+1,ack=1则连接建立成功。

### 四次挥手过程

#### 第一次挥手：

主机A（可以使客户端，也可以是服务器端），设置seq和ack number，向主机2发送一个FIN报文段，此时，主机A进入FIN\_WAIT\_1状态，这表示主机1没有数据要发送给主机2了；

#### 第二次挥手：

主机B收到了主机A发送的FIN报文段，向主机1回一个ACK报文段，ack number为A的seq加1，主机A进入FIN\_WAIT\_2状态，主机B告诉主机A，我“同意”你的关闭请求，主机B进入CLOSE\_WAIT状态；目前只是同意关闭请求，但是主机B可能还有数据发送给主机A，所有需要第三次挥手，主机B告诉主机A，我没有数据发送给你了。

#### 第三次挥手：

主机B向主机A发送FIN报文段，请求关闭连接，同时主机B进入LAST\_ACK状态；

#### 第四次挥手：

主机A收到主机B发送的FIN报文段，向主机B发送ACK报文段，然后主机A进入TIME\_WAIT状态，主机B收到主机A的ACK报文段以后，就关闭连接，此时，主机A等待2MSL（Maximum Segment Lifttime，报文段的最大生存时间）后依然没有收到回复，则证明主机B已正常关闭，主机A也可以关闭连接了。

至此，TCP的四次分手就这么愉快的完成了。

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2Ff59ccae39592692d758bc0f47aee89a51f07199e.jpeg?generation=1589894631388664\&alt=media)

## 3. 为什么需要三次握手

已失效的连接请求报文段”的产生在这样一种情况下：client发出的第一个连接请求报文段并没有丢失，而是在某个网络结点长时间的滞留了，以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后，就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段，同意建立连接。假设不采用“三次握手”，**那么只要server发出确认，新的连接就建立了。由于现在client并没有发出建立连接的请求，因此不会理睬server的确认，也不会向server发送ack包。（此时因为client没有发起建立连接请求，所以client处于CLOSED状态，接受到任何包都会丢弃）但server却以为新的运输连接已经建立，并一直等待client发来数据。这样，server的很多资源就白白浪费掉了。**&#x91C7;用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况，client不会向server的确认发出确认。server由于收不到确认，就知道client并没有要求建立连接。

## 4. 为什么需要四次挥手

TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式，这就意味着，当主机1发出FIN报文段时，只是表示主机1已经没有数据要发送了，主机1告诉主机2，它的数据已经全部发送完毕了；**但是，这个时候主机1还是可以接受来自主机2的数据**；当主机2返回ACK报文段时，表示它已经知道主机1没有数据发送了，但是主机2还是可以发送数据到主机1的；当主机2也发送了FIN报文段时，这个时候就表示主机2也没有数据要发送了，就会告诉主机1，我也没有数据要发送了，之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理，就需要了解四次分手过程中的状态变化。

## 5. TCP和UDP的区别

UDP是User Datagram Protocod的简称，即用户数据报协议，是一种无连接的传输层协议，提供面向事务的简单不可靠信息传送服务。以下为与TCP的区别：

1. TCP是基于连接的，UDP是基于无连接的，也就是发送数据之前不需要建立连接；
2. UDP比TCP要求的系统资源要多，程序结构比较简单；
3. TCP提供可靠的服务。也就是说，通过TCP连接传送的数据，无差错，不丢失，不重复，且按序到达，UDP尽最大努力交付，即不保证可靠交付；
4. UDP具有较好的实时性，工作效率比TCP高，适用于对高速传输和实时性有较高的通信或广播通信；
5. 一条TCP连接只能是点到点的，UDP支持一对一，一对多，多对一和多对多的交互通信。

### 5.1 TCP报文结构

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2Fcb10b0d8f5f8bd40349b194abb3ab138e58c703a.png?generation=1589894631625658\&alt=media)

* 首部长度：也叫数据偏移，是指TCP所传输的数据应该从哪个位开始计算，可以把它看做TCP首部的长度，如果没有选项TCP首部长度为20字节，那么数据便宜可以设置为5，就说明前20个字节是TCP首部，剩下的就是TCP数据。
* 保留字段：为了以后拓展时使用，一般设置为0。
*   控制位：长度6位，含义如下：

    * URG：该位为1时表示包中有需要紧急处理的数据。
    * ACK：1表示应答有效，TCP规定除了最初建立连接时的SYN包之外该位必须设置为1。
    * PSH：1表示需要将收到的数据立刻传给上层应用协议。0表示不需要立刻上传而是先进行缓存。
    * RST：1表示TCP连接中出现异常必须强制断开连接。比如连接一个没有被使用的端口或者断电等情况。
    * SYN：1表示希望建立连接。
    * FIN：1表示希望断开连接，今后没有数据需要发送。

    （有些资料上说控制位有8位，保留位有4位，控制位首部增加了CWR（与ECE配合使用）和ECE（1通知对方网络有拥塞，缩小拥塞窗口））
* 窗口：窗口大小是指无需等待确认应答而可以继续发送数据的最大值（比如设置为4个段的大小），窗口的使用提高了网络吞吐量，使TCP发送报文不用等待上一个应答就可以立即发送。通过窗口实现高速重发控制比超时机制提供了更加快速的重发服务。窗口的大小是由接收端决定的，并且根据接收端缓冲区可用大小进行动态调整。
* 校验和：校验收到的数据是否正确。
* 紧急指针：只有在URG控制位为1时有效，该字段的数值表示本报文段中紧急数据的指针，正确来讲，从数据部分的首位到紧急指针所指示的位置为止为紧急数据。因此也可以说紧急指针指出了紧急数据的末尾在报文段中的位置，如何处理紧急数据属于应用的问题，一般在暂时中断通信或中断通信的情况下，例如web浏览器中点击停止按钮或在shell远程连接时输出Ctrl+C时都会有URG为1的包，此外，紧急指针也用作表示后数据流分段的标志。
* 选项：用于提高TCP传输性能，最大为40字节，具有代表性的选项比如
  * MSS选项（Maximum Segment Size），用来在TCP连接时确定最大消息长度。
  * 窗口扩大，用来改善TCP吞吐量的选项。
  * 时间戳字段，用于高速通信中对序列号的管理。
  * ......

### 5.2 UDP报文结构

![](https://3037548586-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnpynfpU0w3FBTJVlOK%2Fsync%2F4d7020d9e1c53ed855dc541bad3e45ffe0bcec6b.png?generation=1589894632345021\&alt=media)

## 6. TCP连接状态

* CLOSED：初始状态，没有任何连接状态。
* LISTEN：表示服务器端的某个SOCKET处于监听状态，可以接受连接。
* SYNC\_SENT：客户端SOCKET执行CONNECT连接时，客户端发送SYN报文，此时客户端就进入SYNC\_SENT状态，等待服务端的确认（第一次握手）。
* SYN\_RCVD：服务端接收到SYN报文，向客户端发送ACK报文后进入SYNC\_RCVD状态。
* ESTABLISHED：表示连接已经建立了，第三次握手客户端发送ACK报文到服务端，客户端进入ESTABLISHED状态，服务端收到ACK报文后也进入ESTABLISHED状态。
* FIN\_WAIT\_1：建立连接后其中一方终端A请求终止连接，向另一方终端B发送一个FIN报文后进入FIN\_WAIT\_1状态。
* CLOSE\_WAIT：表示等待关闭，终端B收到终端A发送的FIN报文后，向终端A发送一个ACK报文段，终端B进入CLOSE\_WAIT状态，等待终端B是否还有数据发送给终端A；
* FIN\_WAIT\_2：终端A收到终端B的ACK报文后进入FIN\_WAIT\_2状态；
* LAST\_ACK：当终端B向终端A发送FIN报文后，请求关闭，终端B进入LAST\_ACK状态；
* TIME\_WAIT：终端A收到终端B的FIN报文后，发送给终端BACK报文，进入TIME\_WAIT状态，又叫做2MSL状态；
* CLOSED：终端B接收到终端A发来的最后一个ACK报文后进入CLOSED状态，连接结束，进入初始状态。
* CLOSING：这种状态比较特殊，很少见，正常情况下当终端A发送FIN报文后，按理说应该先收到或者同时收到终端B的ACK报文，再收到FIN报文，但是如果终端A发送FIN报文后没有收到ACK报文，反而收到终端B的FIN报文，什么情况下会出现这样的场景呢？那就是如果双方几乎在同时close一个SOCKET的话，就会出现双方同时发送FIN报文的情况。也即会出现CLOSING状态，表示双方都正在关闭SOCKET连接。

## 7. 关于TCP的问题

1. TCP数据包的最大长度是多少？

TCP以段为单位发送数据，在建立TCP连接时，两端在发送SYN包的时候附带一个MSS值，这个值是根据本机接口能适应的MSS大小，然后从这两个里面选择一个较小的值作为最终的MSS值（最大消息长度）。如果某一方没有提供MSS值，可以选为IP包的长度不超过576字节的值（IP首部20字节，TCP首部20字节，MSS 536字节）。

1. TCP重发超时时间是多少?

根据数据包往返时间动态调整（往返时间+偏差），最初的超时时间一般设置为6秒左右，超时时间以0.5秒作为单位进行控制，偏差的最小值也是0.5秒，所以超时重发时间最少是1秒。

1. 第四次挥手为什么要等待2msl？

为了保证对端已经收到了第四次的ack报文并成功关闭了连接，如果B经过1MSL没有收到ack报文，就会重发FIN报文，重发的FIN报文正常情况下在A第四次挥手发完ACK后经过2MSL（第一个1MSL是A向B发送的ACK，第二个1MSL是B重发的FIN到A的时间）一定能收到，如果没有收到则表示B已经收到ACK报文并正确关闭了。

> 参考：
>
> OSI模型讲解：[https://www.zhihu.com/question/24002080/answer/31817536](https://www.zhihu.com/question/24002080/answer/31817536)
>
> 理解TCP的三次握手，四次挥手：[https://www.jianshu.com/p/ce6bd119a4eb?utm\_campaign=hugo\&utm\_medium=reader\_share\&utm\_content=note\&utm\_source=weixin-friends](https://www.jianshu.com/p/ce6bd119a4eb?utm_campaign=hugo\&utm_medium=reader_share\&utm_content=note\&utm_source=weixin-friends)
>
> 三次握手，四次挥手：[https://www.jianshu.com/p/092705233d37](https://www.jianshu.com/p/092705233d37)
>
> TCP连接是如何建立和终止的：[https://juejin.im/post/5b0119776fb9a07aaf357d77](https://juejin.im/post/5b0119776fb9a07aaf357d77)
