一、应用层
1)发送接收流程
1. 发送文件
write
函数发送数据到 TCP 套接字时,内容不一定会立即通过网络发送出去。这是因为网络通信涉及多个层次的缓冲和处理,TCP 是一个面向连接的协议,它需要进行一定的排队、确认和重传等处理。具体来说,write
到 TCP 套接字并不意味着数据立刻发送到远程主机,它可能会经历以下几个步骤:
-
用户空间缓冲区:当调用
write()
或send()
发送数据时,数据首先会被放入用户空间的缓冲区(即应用程序的发送缓冲区)。这时,数据并没有立即从 TCP 套接字发送到网络,只是暂时存放在内核中的缓冲区里,等待进一步的处理。 -
TCP 内核缓冲区:接着,内核会将这些数据从用户空间缓冲区复制到 TCP 内核缓冲区。此时,数据依然没有发送到网络,只是存储在内核的发送缓冲区,准备通过 TCP 连接发送。
-
等待确认:TCP 协议本身是可靠的,意味着它需要确保数据的可靠发送。TCP 会对每一部分数据进行分段(packetization)并按顺序发送。发送的数据会首先加入 TCP 内核缓冲区,然后等待网络层处理、分段、传输。
-
网络传输:经过以上处理后,数据会通过网络层发送到目标主机。但这并不意味着数据在发送后就立即到达目标主机。由于 TCP 是面向连接的协议,它还会确保数据正确传输,可能涉及数据的重传、确认(ACK)等机制。
-
操作系统缓冲区与阻塞:如果 TCP 套接字的发送缓冲区已满,或者网络暂时无法处理数据,
write
调用可能会被阻塞,直到缓冲区有空间或者网络恢复。如果是非阻塞模式下,write
会立即返回,表示数据已经进入内核缓冲区,但并不一定已经完全通过网络发送。 -
完成通知:虽然
write
函数调用可能很快返回,但它返回的并不代表数据已经完全通过网络发送完成。实际上,发送的数据可能会在内核缓冲区中保持一段时间,等待网络状况、流量控制、拥塞控制等因素。
2. 读取文件
read
函数读取 TCP 套接字时,内容并不一定是立即从网络接收到的。TCP 协议涉及多个缓冲和处理步骤,数据在内核缓冲区中可能排队,直到满足某些条件后才会传递给应用程序。以下是 read
函数处理过程的步骤:
- 内核接收缓冲区:当远程主机通过网络发送数据到本地计算机时,这些数据首先会通过网络层传输并到达操作系统的 接收缓冲区。这时,数据并没有直接交给应用层,而是暂时存储在内核的缓冲区中,等待进一步的处理。
- 数据排队与处理:接收到的数据可能会排队在缓冲区内。虽然
read
或recv
函数已经被调用,但这些数据可能并未立即交给应用程序。操作系统内核会根据缓冲区的状态、网络环境以及流量控制等因素,决定什么时候把数据交给应用层。 - 等待数据可用:如果接收缓冲区中已有数据,
read
会立即返回相应的数据。如果缓冲区为空,read
会进入阻塞状态,直到数据到达并准备好读取。这时,操作系统会等待远程主机的进一步数据传输或本地网络环境的变化。 - 流量控制与延迟:TCP 协议有流量控制和拥塞控制机制,确保数据传输的可靠性与稳定性。但这些机制也可能导致读取数据的延迟,即使数据已经通过网络到达,可能因为缓冲区或流量控制的限制,数据还未被传递给应用程序。
- 读取过程中的分段与组合:由于 TCP 是流协议,传输的数据可能是分段的。即使应用程序调用
read
或recv
,也有可能只接收到部分数据包或数据块。这是因为 TCP 会将大块数据分为多个小段传输,应用层需要处理这些分段数据并将其拼接。 - 阻塞与非阻塞模式:
- 阻塞模式:如果套接字处于阻塞模式,
read
会等待直到有数据可以读取。如果接收缓冲区中没有数据,read
会阻塞,直到接收到来自远程主机的数据并可供读取。 - 非阻塞模式:如果套接字处于非阻塞模式,
read
会立即返回。如果没有数据可以读取,它会返回-1
并设置errno
为EAGAIN
或EWOULDBLOCK
,表示暂时没有数据可读。
- 阻塞模式:如果套接字处于阻塞模式,
- 数据交付与应用层读取:
- 如果内核缓冲区中有数据,
read
会从缓冲区中将数据交付给应用层。此时,应用层可以开始处理接收到的数据。 - 如果接收缓冲区中没有数据,
read
将等待更多数据传输到缓冲区,直到数据可用。
- 如果内核缓冲区中有数据,
- 内核的
read
行为:当应用程序调用read
时,操作系统内核会检查接收缓冲区中是否有数据。如果缓冲区中有数据,内核会将其交给应用程序;如果缓冲区为空,read
会阻塞,直到新的数据到达。整个过程受网络状况、流量控制、操作系统调度等多方面因素的影响。
2)HTTP协议
1. 序列化与反序列化
HTTP协议中的序列化与反序列化主要指的是将请求和响应的内容从程序数据结构转化为字符串格式(序列化),以及将字符串格式的HTTP消息转换回程序数据结构(反序列化)的过程。
-
序列化是将数据结构转换成可以存储或传输的格式
-
反序列化是序列化的逆操作,即将存储或传输的格式转化为相应的数据结构。
- 在HTTP请求和响应的交互中,传输的消息通常是字符串格式,因此序列化和反序列化非常重要。例如,在客户端向服务器发送请求时,HTTP请求会被序列化为一条字符串并通过网络发送;在服务器接收到该请求时,它会反序列化请求并进行处理,最终返回HTTP响应并再次进行序列化。
2. URL
URL(Uniform Resource Locator,统一资源定位符)是用于定位网络资源的地址。URL通常由多个部分组成:
- 协议(Protocol):如
http
、https
等,用于指定如何访问资源。 - 主机名(Host):指定访问的服务器的域名或IP地址,如
www.example.com
。 - 端口号(Port):在HTTP中通常是80(HTTP)和443(HTTPS),如果没有指定,则默认使用这些端口。
- 路径(Path):指向特定资源的路径,如
/index.html
。 - 查询字符串(Query):可选的,通常包含键值对,用于指定参数,如
?name=value
。 - 片段标识符(Fragment):可选的,用于指定文档内的某个位置,如
#section1
。
3. HTTP请求
-
请求行(Request Line):请求行是HTTP请求的第一行。
<请求方法> <请求URI> <HTTP版本>
GET /index.html HTTP/1.1
- 请求方法:指定要执行的操作,如
GET
、POST
、PUT
、DELETE
等。 - 请求URI:指定资源的路径,通常是请求的地址或服务器上的资源路径。它可以包含查询参数(Query String)。
- HTTP版本:指定HTTP协议的版本,一般是
HTTP/1.1
或HTTP/2
。
- 请求方法:指定要执行的操作,如
-
请求报头(Request Headers):紧随请求行之后的部分,包含多个键值对,每一行描述了客户端的元数据。请求头提供了额外的控制信息,比如客户端能力、认证信息、请求的内容类型等。
<头部字段名>: <字段值>
Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
- Host:指定服务器的域名和端口。
- User-Agent:标识发起请求的客户端软件(如浏览器)。
- Accept:指定客户端能够接受的内容类型(如
text/html
,application/json
等)。 - Authorization:包含认证信息(如基本认证的用户名和密码)。
- Content-Type:指定请求正文的类型(通常用于POST或PUT请求)。
- Accept-Encoding:指定客户端支持的内容编码(如gzip)。
- Connection:指定连接的管理方式(如
keep-alive
)。
-
请求正文(Request Body):请求正文是请求中的可选部分,用于发送数据给服务器,尤其在POST、PUT、PATCH等请求方法中,通常用于提交表单数据、上传文件等。请求正文的内容是数据的实际载体,可以是
HTML
表单数据、JSON
、XML
等格式。{"username": "john_doe","password": "password123" }
请求正文的内容和格式通常由
Content-Type
请求头来指定。常见的格式有:application/x-www-form-urlencoded
:用于提交表单数据(通常是简单的键值对)。multipart/form-data
:用于上传文件时使用的表单格式。application/json
:用于传输JSON数据。text/plain
:纯文本数据。
请求报文示例:
GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 Accept: text/html
4. HTTP响应
-
状态行:状态行是HTTP响应的第一行,包含了以下三部分信息
<HTTP版本> <状态码> <状态描述>
HTTP/1.1 200 OK
- HTTP版本:指示HTTP协议的版本(如
HTTP/1.1
)。 - 状态码:三位数字代码,用来表示响应的结果。
- 状态描述:对状态码的简单说明。
- HTTP版本:指示HTTP协议的版本(如
-
响应报头(Response Headers):响应头部分包含多个字段,提供了有关响应的额外信息,如响应的类型、服务器信息、内容长度等。常见的响应头包括。
<头部字段名>: <字段值>
Content-Type: text/html; charset=UTF-8 Content-Length: 1234 Server: Apache/2.4.41 (Ubuntu) Date: Sat, 27 Nov 2021 12:00:00 GMT
- Content-Type:表示响应主体的媒体类型(如
text/html
,application/json
)。 - Content-Length:表示响应正文的长度。
- Server:服务器的名称和版本。
- Date:响应生成的日期和时间。
- Location:用于重定向,表示资源的新位置(如在301或302状态码时)。
- Set-Cookie:服务器返回给客户端的Cookie信息。
- Cache-Control:指定缓存策略(如是否缓存、缓存时间等)。
- Connection:指定是否保持连接(如
keep-alive
)。
- Content-Type:表示响应主体的媒体类型(如
-
响应正文(Response Body):响应正文部分包含实际的数据内容,是响应的主体。它通常是客户端请求的资源,可能是HTML页面、图片、JSON数据、音频、视频等。响应正文的格式通常由
Content-Type
响应头指定,常见的格式有:// HTML响应正文 <html><head><title>Example</title></head><body><h1>Welcome to the Example Page</h1></body> </html>
text/html
:HTML页面。application/json
:JSON数据。image/jpeg
:JPEG格式的图片。audio/mpeg
:MP3格式的音频。
响应报文示例:
HTTP/1.1 200 OK Server: Apache Content-Type: text/html Content-Length: 1234<html>...</html>
4. HTTP方法
HTTP协议定义了几种常见的请求方法,用于表示客户端对服务器的不同操作请求:
-
GET:请求资源,不携带请求体,通常用于查询数据。
GET
请求:GET /search?query=example HTTP/1.1
HTML
表单的GET
请求:<form action="search" method="GET"> <!-- 定义表单,使用 GET 方法 --><input type="text" name="query" placeholder="Search"> <!-- 搜索框,用户输入搜索内容 --><input type="submit" value="Search"> <!-- 提交按钮 --> </form>
GET
特点:-
数据放在 URL 中:
GET
请求会将数据附加在 URL 后面,通常是以查询字符串的形式。例如:https://www.example.com/login?username=admin&password=1234
-
无副作用:
GET
请求一般用于获取资源,不应对服务器上的数据进行修改或产生副作用。因此,GET
请求应该是安全的和幂等的(即多次相同的请求会产生相同的结果)。 -
请求数据量有限:因为数据附加在 URL 后面,所以 URL 长度受到限制(通常是 2048 个字符),因此不适合传输大量数据。
-
缓存:
GET
请求可以被缓存(如果服务器设置了缓存策略),并且浏览器通常会缓存GET
请求的响应结果。 -
浏览器历史记录:由于数据在 URL 中,因此它们会被记录在浏览器的历史记录中,用户可以查看、修改 URL。
-
-
POST:提交数据,当用户提交表单时,数据不会出现在 URL 中,而是通过请求体发送。
POST
请求:POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 25username=admin&password=1234
HTML
表单的POST
请求:<form action="login" method="POST"><input type="text" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" value="Login"> </form>
POST
特点-
数据放在请求体中:与
GET
不同,POST
请求的数据不会出现在 URL 中,而是放在请求的主体部分。因此,POST
请求可以发送大量的数据(不受 URL 长度限制)。 -
有副作用:
POST
请求通常用于向服务器提交数据并创建或修改资源。例如,提交表单数据、上传文件等。由于POST
请求会导致数据修改,它应该是非幂等的(即每次请求都可能导致不同的结果)。 -
不被缓存:
POST
请求通常不会被缓存,因为它可能会引起数据的变化。 -
没有长度限制:由于数据在请求体中,所以
POST
请求可以传输大量数据,适合上传文件或提交大量表单数据。 -
浏览器历史记录:
POST
请求不会将数据存储在浏览器的地址栏和历史记录中,因此它比GET
更适合处理敏感信息。
-
-
PUT:更新资源,通常用于更新数据。
-
DELETE:删除资源。
-
HEAD:类似于GET,但服务器只返回响应头,不返回响应体。
-
OPTIONS:查询支持的方法或服务器的能力。
-
PATCH:部分更新资源。
HTTP方法95%使用
GET
和POST
特性 | GET | POST |
---|---|---|
数据位置 | URL 查询字符串 | 请求体(body) |
数据长度限制 | 受 URL 长度限制(通常为 2048 字符) | 没有限制 |
安全性 | 数据暴露在 URL 中,安全性差 | 数据不暴露在 URL 中,安全性好 |
用途 | 获取数据,查询信息 | 提交数据,修改或创建资源 |
是否可以缓存 | 可以缓存 | 不可以缓存 |
是否能通过书签保存 | 可以(因为数据在 URL 中) | 不可以(数据不在 URL 中) |
5. HTTP状态码
HTTP状态码用于表示服务器处理请求的结果,它们分为五个类别:
-
1xx:信息性状态码,表示请求已被接收,正在处理。
-
2xx
:成功状态码,表示请求已成功处理并返回结果。
- 200 OK:请求成功。
- 201 Created:资源已成功创建。
- 204 No Content:请求成功,但没有返回数据。
-
3xx
:重定向状态码,表示需要客户端进一步操作以完成请求。
- 301 Moved Permanently:资源已永久移动到新的位置。
- 302 Found:资源临时移到新的位置。
-
4xx
:客户端错误状态码,表示请求有语法错误或无法完成请求。
- 400 Bad Request:请求格式错误。
- 401 Unauthorized:未授权,缺少认证信息。
- 404 Not Found:请求的资源不存在。
-
5xx
:服务器错误状态码,表示服务器未能处理有效请求。
- 500 Internal Server Error:服务器内部错误。
- 503 Service Unavailable:服务不可用,通常是服务器过载或维护。
6. HTTP常见Header
HTTP请求和响应头用于传递额外的信息,常见的有:
-
Host
:指定服务器的主机名和端口号。Host: www.example.com
- 说明:该头部字段在HTTP/1.1中变得强制,因为它支持虚拟主机功能。在一个IP地址下,可能托管多个网站,
Host
字段帮助服务器确定目标网站。
- 说明:该头部字段在HTTP/1.1中变得强制,因为它支持虚拟主机功能。在一个IP地址下,可能托管多个网站,
-
Connection
:控制连接的持续方式。Connection: keep-alive(保持连接) Connection: close`(关闭连接)
- 说明:控制客户端和服务器在完成请求-响应周期后是关闭连接,还是保持连接用于后续请求。
keep-alive
常用于 HTTP/1.1 协议中,表示连接可以复用。
- 说明:控制客户端和服务器在完成请求-响应周期后是关闭连接,还是保持连接用于后续请求。
-
User-Agent
:标识发出请求的客户端软件。User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
- 说明:通常用于识别浏览器和操作系统信息。它告诉服务器请求是来自于什么平台和软件,帮助服务器做适当的响应。
-
Location
:指示客户端应访问的新 URL(通常用于重定向)。Location: https://www.newdomain.com
- 说明:如果服务器需要客户端访问另一个资源,通常会返回该字段,指示客户端应发起新的请求。
-
Content-Type
:指示响应体中的数据类型。Content-Type: application/json Content-Type: text/html
- 说明:该字段告诉客户端响应体的类型,从而使客户端能够正确解析和显示内容。
-
Content-Length
:指示响应体的大小。Content-Length: 1234
-
Set-Cookie
:指示服务器发送给客户端的 cookie。Set-Cookie: sessionId=123456; expires=Thu, 01 Jan 2023 00:00:00 GMT; path=/
- 说明:该字段用来设置客户端的 cookie,服务器通过设置 cookie 来在后续请求中识别客户端。
Cookie
:Cookie
是服务器发送到浏览器的一小段数据,浏览器会将其存储并在每次后续请求时将其发送回服务器。Cookie 可以存储一些用户的偏好设置、认证信息、会话标识符等。Session ID
:Session
是服务器端的一个机制,它用于存储关于用户会话的状态信息。Session ID 是一个唯一的标识符,用来标识一个用户的会话。这个 Session ID 通常会存储在 Cookie 中或通过 URL 传递。- 为了提高安全性,现在大多数 Web 应用会采用 Session ID 来记录用户的登录状态,而不是直接将账户密码等敏感信息存储在 Cookie 中。Cookie 只是用来存储标识符(如 Session ID),而实际的用户数据则由服务器管理。
3)HTTPS
HTTPS
(HyperText Transfer Protocol Secure)是HTTP
的安全版本,它通过在HTTP的基础上增加SSL/TLS
加密层来保证通信的安全性。
1. HTTPS
与HTTP
的区别
- 加密:
HTTPS
在HTTP
的基础上加入了SSL/TLS
加密协议,用于确保数据在传输过程中不会被窃取或篡改。HTTP
是明文传输,而HTTPS
是加密传输。 - 认证:
HTTPS
使用数字证书来验证通信的双方,确保你与正确的服务器进行通信,而HTTP
不做任何认证,可能存在中间人攻击(Man-in-the-Middle)。 - 数据完整性:
HTTPS
保证数据在传输过程中不被篡改,使用散列(Hashing)技术确保数据的完整性。HTTP
在这方面没有任何机制。
2. CA认证
CA(Certificate Authority,证书颁发机构)是一个受信任的第三方机构,负责发放、管理和验证数字证书。数字证书用于保证信息传输的安全性,尤其是通过 HTTPS、SSL/TLS 协议进行的加密通信。CA认证的主要目的是确认网站或服务器的身份,确保客户端可以信任该服务器。在互联网上,当访问一个安全的网站(例如:https://www.example.com),浏览器会向网站服务器验证其数字证书是否由一个被信任的CA颁发。这个过程就是“CA认证”的应用。
CA认证基于 公钥基础设施(PKI)体系结构,PKI 是由密钥对、数字证书、认证机构等组成的一套框架,主要包括以下几个步骤:
-
申请证书(申请公钥与私钥对):在整个认证流程中,首先需要生成一个公钥和私钥对。这个过程由 服务器 负责,具体步骤如下:
-
生成公钥和私钥对:服务器使用加密算法(如RSA、ECDSA等)生成一对密钥,其中公钥是用来加密信息或验证签名的,私钥则用于解密信息或创建数字签名。
-
生成证书签名请求(CSR):服务器会将生成的公钥和其他信息(如域名、申请者信息等)打包成一个证书签名请求(CSR)。需要注意的是,这个请求中不能包含私钥信息。
-
提交CSR:服务器将CSR提交给CA机构进行验证和签发证书。CSR中包含了申请者的身份信息以及公钥。
-
-
CA审核信息
-
CA审核过程:CA机构接收到服务器发来的CSR后,会对申请者的身份进行验证,通常会验证申请者提供的信息是否真实有效,包括域名的所有权、申请者身份等。
-
证书签发准备:在验证完成后,CA会准备签发证书,证书中将包括服务器的公钥、有效期、CA的签名等信息。
-
-
签发证书:CA使用其私钥对证书进行签名。数字证书中包含了以下信息:
-
服务器的公钥(来自CSR)
-
CA的签名,证明该证书是由可信的CA签发的
-
证书的有效期
-
证书持有者的身份信息
-
域名
-
证书序列号
-
-
证书返回:CA将签发的证书返回给申请者,服务器会将其部署在自己的网站上,这时服务器可以使用这个证书来实现安全通信。客户端收到证书后,会验证证书的有效性。
-
客户端验证证书:客户端(例如浏览器)收到服务器的证书后,会执行以下验证步骤:
-
验证证书链:客户端会验证证书是否由受信任的 CA 颁发,通常通过证书链的方式进行验证。客户端会检查证书的签名,确认它是否由已知的根证书(根CA)签发。
-
验证证书有效期:客户端还会检查证书的有效期,确保其在当前时间内有效。
-
验证域名匹配:客户端会检查证书中的域名与当前访问的服务器的域名是否匹配。
-
验证其他信息:客户端还会进行其他的验证,如是否存在证书撤销等问题。
-
-
加密通信(密钥交换):在客户端成功验证证书后,双方可以使用公钥和私钥进行加密通信:
-
密钥交换:通过加密的方式,客户端和服务器将会建立一个安全的通信通道,确保双方的数据传输是加密的。
-
加密通信:所有后续的通信都将使用加密通道进行保护,防止中间人攻击和数据篡改。
-
二、传输层
1)UDP(用户数据报协议)
UDP 是一种无连接的传输层协议,属于 Internet协议族。与 TCP(传输控制协议)相比,UDP没有保证数据传输的可靠性,也不进行流量控制和拥塞控制。它更适合用于那些对速度要求高,但对数据丢失或者顺序不太敏感的应用。
1. UDP特点
-
无连接性:UDP是一个无连接协议。在通信之前,客户端和服务器不需要建立连接。每个数据包(称为“数据报”)都作为独立的单元进行发送,并且发送之后不会等待确认。
-
不可靠性:UDP不保证数据报的传输顺序,也不确认数据是否成功到达。数据可能会丢失、重复,或者顺序错乱。应用层需要自行处理这些问题(如果有需要)。
-
低延迟:由于不进行复杂的错误检查和重传,UDP协议的开销较小,传输速度相对较快。它适用于对实时性要求较高的应用,如视频流、在线游戏、VoIP等。
-
无流量控制和拥塞控制:UDP没有内建的流量控制和拥塞控制机制,这意味着UDP不会自动调整发送速率,也不处理网络拥塞的情况。
-
适用于小数据包的传输:UDP通常用于传输小数据包,适合实时应用,如DNS查询、实时语音和视频等。
-
面向数据报:应用层交给UDP多长的报文、UDP原样发送,既不会拆分,也不会合并;
用udp传输100个字节的数据。如果发送端调用一次Sendto
发送100个字节,那么接收端也必须调用对应的一次Recvfrom
、接收100个字节;而不能循环调用10次Recvfrom
,每次接收10个字节。
2. UDP报头的本质
-
UDP报头:UDP的数据包格式非常简单,只有4个字段(总计8个字节)。
字段名称 长度 说明 源端口号 16位 发送方的端口号 目标端口号 16位 接收方的端口号 长度 16位 数据部分的总长度(包括UDP头和数据) 校验和 16位 用于错误检查的字段,确保数据在传输过程中未被篡改或损坏 -
struct_udp_header
:// UDP头部结构体 struct udp_header {uint16_t source_port; // 源端口号uint16_t dest_port; // 目标端口号uint16_t length; // UDP头部和数据的总长度uint16_t checksum; // 校验和 };
3. 基于UDP的应用层协议
DNS
(域名系统):DNS是互联网中最重要的协议之一,它将人类易记的域名(如www.example.com
)解析为IP地址。DNS协议通常使用UDP进行通信,尤其是进行查询时。DHCP
(动态主机配置协议):DHCP用于为局域网中的计算机自动分配IP地址、子网掩码、网关等配置信息。DHCP使用UDP协议进行客户端与服务器之间的通信。RTP
(实时传输协议):RTP用于音频和视频流的传输,它广泛应用于VoIP(语音通信)和视频会议等实时应用中。RTP通常在UDP协议上实现,因为它需要快速传输和低延迟。SNMP
(简单网络管理协议):SNMP用于网络设备的监控和管理。网络设备(如路由器、交换机)使用SNMP协议向网络管理系统报告其状态、性能等信息。TFTP
(简单文件传输协议):TFTP是一个非常简单的文件传输协议,通常用于小型文件的传输,例如操作系统引导、设备配置等。TFTP使用UDP作为其传输协议。
2)TCP(传输控制协议)
TCP 是一种面向连接、可靠的传输层协议。它是 IP协议(Internet Protocol)中最重要的协议之一,广泛用于互联网中的数据传输,尤其是需要保证数据完整性和顺序的场景,如网页浏览、文件传输、电子邮件等。
1. TCP的特点
- 面向连接:TCP在数据传输之前需要先建立连接,确保双方可以进行数据交换。这个过程叫做“三次握手”。
- 可靠性:TCP通过数据确认、重传机制、序列号等方式确保数据按顺序无误地到达接收方。
- 有序性:TCP保证数据的顺序,接收方收到的数据会按照发送方发送的顺序进行排序。
- 流量控制:TCP使用流量控制来避免发送方发送数据过快,导致接收方的缓冲区溢出。
- 拥塞控制:TCP通过拥塞控制算法来避免网络拥塞,确保网络的稳定性。
- 全双工通信:TCP支持双向数据流的同时传输,即可以在同一个连接中同时发送和接收数据。
2. TCP协议段
TCP协议段(TCP Segment)是TCP协议的数据单位。TCP协议段在网络中用于传输数据,确保数据的可靠传输,并提供流量控制和拥塞控制等功能。一个TCP协议段的结构包括多个字段,用于控制数据的传输顺序、确认数据的接收、错误检测等。
TCP协议段格式通常包括以下主要部分:
// 4 Bytes0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +++++++
| Source Port(16bits) | Destination Port(16bits) | .......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .......
| Sequence Number(32bits) | .......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .......
| Acknowledgment Number(32bits) | .......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20Bysts
| Data | |C|E|U|A|P|R|S|F| | .......
| Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window(16bits) | .......
|(4bits)|(4bits)|R|E|G|K|H|T|N|N| | .......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .......
| Checksum(16bits) | Urgent Pointer(16bits) | .......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +++++++
| [Options] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. Data .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
源端口号(Source Port)
- 16位字段,用于标识发送方应用程序的端口。
-
目标端口号(Destination Port)
- 16位字段,用于标识接收方应用程序的端口。
-
序列号(Sequence Number)
- 用于标识数据流中的字节序号。对于每一个数据段,TCP会分配一个唯一的序列号,用于数据的排序和确认。
- 初始序列号(ISN)是由双方在连接建立时通过三次握手来确定的。
-
确认号(Acknowledgment Number):
- 如果ACK标志位为1,表示接收方期望下一个接收到的数据字节的序号。
- 该字段指示接收方期望从发送方接收的数据的下一个字节位置,确保数据按序到达。
- 确认序号的意义:确认序号之前的数据已经全部收到,下一次发送从确认序号指定的序号开始发送。
- 确认号与序列号不复用:
- 可以同时发送和回复已经收到,两个号会同时存在。
-
数据偏移(Data Offset):
- 4位字段,表示TCP头部的长度,以4字节为单位。它告知接收方TCP数据段头部的结束位置,紧接着是数据部分。
-
保留位(Reserved) :4位,为将来使用而保留的一组控制位。在生成段中必须为零。
-
标志位:TCP标志位包含8个字段,用于控制连接的建立、数据的确认、连接的关闭等。
-
CWR (Congestion Window Reduced):减少拥塞窗口标志。该标志位用于TCP流量控制,表示发送方已经接收到拥塞通知并减少了其拥塞窗口。它通常用于拥塞控制机制中,特别是在TCP的拥塞窗口减少过程中。
-
ECE (ECN Echo):显式拥塞通知回显标志。这个标志表示网络中发生了拥塞事件。如果ECN(Explicit Congestion Notification)被启用,并且发送方收到了来自接收方的拥塞通知,则该标志位会被设置。
-
CWR 和 ECE 标志位用于TCP协议的 显式拥塞通知(ECN)功能,通常应用于支持 ECN 的网络中。
-
在许多 普通应用 中,这些标志位不被使用,因此它们在基础教程和常见实现中不被详细讨论。
-
它们的功能更多的是面向 高性能网络 和 网络调优,而普通的应用开发、网络调试中不常需要关注这两个标志位。
-
-
URG (Urgent):紧急指针有效标志。如果该标志位为1,则表示该数据包中紧急指针字段有效,并且接收方应优先处理该数据。通常,紧急数据会通过TCP的紧急指针机制进行标识。
- 在 Unix/Linux 系统中,应用程序可以通过
send()
系统调用来发送带外数据,并指定带外标志。接收端可以使用recv()
或recvfrom()
等接口来接收带外数据。
- 在 Unix/Linux 系统中,应用程序可以通过
-
ACK (Acknowledgment):确认号有效标志。如果该标志位为1,则表示确认号字段有效,接收方确认接收到数据的正确性。ACK用于TCP连接的可靠数据传输过程中。
-
PSH (Push):推送功能标志。当该标志位为1时,接收方应立即将收到的数据交给上层应用,而不应将其缓存。这通常用于需要快速响应的实时数据传输。
-
RST (Reset):重置连接标志。当该标志位为1时,表示需要重置连接。TCP会使用RST来表示非正常连接终止或拒绝一个新的连接请求。
-
SYN (Synchronize):同步标志。该标志用于连接的建立阶段。当一方想要建立连接时,它会发送一个SYN包。SYN用于同步双方的序列号。
-
FIN (Finish):结束标志。该标志用于连接的关闭。当一方完成数据的发送并希望关闭连接时,它会发送一个带有FIN标志的数据包。收到FIN标志的接收方会进行确认并结束连接。
-
-
窗口大小(Window):
- 16位字段,用于流量控制,表示接收方的缓冲区剩余容量,即接收方最多可以接收的字节数。
-
校验和(Checksum)
-
16位字段,主要用于检测TCP头部和数据部分的传输错误。校验和计算时包括TCP头部和数据部分的所有位,以及伪头部(源IP、目标IP等)。
-
校验和的计算过程涉及16位的加法运算,如果没有错误,接收方计算出来的校验和应该为0。
-
-
紧急指针(Urgent Pointer)
- 16位字段,如果URG标志为1,则表示数据中有紧急数据,紧急指针指示紧急数据的结束位置。
-
选项(Options)
- 可选字段,用于扩展TCP协议的功能。例如,最大报文段大小(MSS)、时间戳(Timestamp)等。
3. TCP的连接机制
TCP的核心任务是确保数据的可靠传输,它通过以下几个关键机制实现这一目标:
-
三次握手(TCP连接的建立):在建立TCP连接时,客户端和服务器之间通过一个三次握手过程来确保双方都准备好通信。
+----------+ +---------+ | Client | | Server | +----------+ +---------+CLOSED | | CLOSED | | LISTENSYN_SENT |-----[SYN] seq=100---------------->| |<----[SYN+ACK] seq=300, ack=101----| SYN_RCVD ESTABLISHED|-----[ACK] ack=301---------------->|| | ESTABLISHED| |
-
第一次握手:客户端发送一个带SYN标志的数据包(SYN=1),告诉服务器“我想建立连接”。
-
第二次握手:服务器收到客户端的SYN包后,回复一个带SYN和ACK标志的数据包(SYN=1, ACK=1),表示同意连接并确认客户端的SYN。
-
第三次握手:客户端收到服务器的确认后,再次发送一个带ACK标志的数据包(ACK=1),表示连接建立成功。
-
- 三次握手必要性:
- 验证全双工
- 同步双方的序列号:三次握手的过程确保了双方的序列号被同步。这是因为TCP是一个 面向字节流 的协议,在连接建立时,双方必须知道彼此的初始序列号,以便数据能够按顺序传输。
- 防止旧连接的干扰:如果没有三次握手的过程,可能会发生 延迟的旧数据包 在网络中混淆新连接的情况。三次握手通过同步序列号和状态,确保每个连接都是新的,避免了之前的连接遗留数据影响当前连接。
- 确保双方都准备好建立连接:通过三次握手,双方都可以确认对方处于能够进行数据传输的状态,避免不必要的连接失败。
- 防止资源浪费:三次握手确保双方都确认对方的接收能力和发送能力,避免因为没有资源准备好而浪费系统资源。
- 即便连接失败了,连接成本也是由客户端承担,如果由服务端承担,容易导致服务器出问题。
- 连接建立的成功和上层有没有accept没有关系
-
四次挥手(TCP连接的断开):在通信结束时,双方需要通过四次挥手来断开连接。
+----------+ +---------+ | Client | | Server | +----------+ +---------+ ESTABLISHED| | ESTABLISHED| | FIN_WAIT_1 |-----[FIN] seq=500---------------->| |<----[ACK] ack=501-----------------| CLOSE_WAIT FIN_WAIT_2 |<----[FIN] seq=700, ack=501--------| LAST_ACK TIME_WAIT |-----[ACK] ack=701---------------->| CLOSEDCLOSED | || |
-
第一次挥手:客户端发送一个带FIN标志的数据包(FIN=1),请求断开连接。
-
第二次挥手:服务器收到FIN包后,确认断开,并向客户端发送一个ACK包(ACK=1)。
-
第三次挥手:服务器准备好关闭连接,发送一个带FIN标志的数据包(FIN=1)给客户端。
-
第四次挥手:客户端收到服务器的FIN包后,确认断开,并发送一个ACK包(ACK=1)给服务器。至此,连接断开。
-
四次挥手的必要性:
保证双方数据传输完成:四次挥手确保每一方都有机会完成它的数据传输,确保没有数据丢失。如果不进行完整的关闭过程,可能会出现数据没有完全传输的情况。
确保连接的有序关闭:TCP是一个全双工协议,即每个方向上都有独立的数据流。四次挥手使得连接能够分别关闭每一个方向的数据流,确保双方的数据能够顺利传输完毕,且不会干扰到对方的操作。
避免半开连接:没有四次挥手,可能会出现 半开连接(即一方认为连接已关闭,另一方仍在等待数据传输)。四次挥手确保每一方都确认连接完全关闭,避免了这种情况的发生。
TIME_WAIT状态:主动关闭方进入 TIME_WAIT 状态,确保最后一个ACK包能够到达对方,防止由于网络延迟或数据丢失导致的连接关闭失败,持续时间在30-60s不等。
- 数据传输
-
确认应答机制:在 TCP 中,数据的传输并不是盲目进行的。每当发送方传输一段数据,接收方都需要确认其已成功接收。确认机制的核心思想是:通过接收方发送的 确认包(ACK) 来通知发送方接收的数据已经被成功接收,或者接收方期望接收的下一个数据是什么。
- 当接收方收到一个数据包时,它会返回一个 ACK(Acknowledgment)包,表示它已经成功接收到的数据。
- 确认包中包含一个 确认号(Acknowledgment Number),指示接收方期望接收的下一个数据字节的序列号。
- 如果接收方没有收到某些数据包,它将继续等待,并在重传超时后请求发送方重传丢失的数据。
-
超时重传机制:当 TCP发送方 发送数据后,如果在指定的时间内没有收到接收方的确认(ACK包),则认为该数据包丢失或未被正确接收,发送方会重新发送该数据包,直到接收到确认包为止。
- RTO:发送方等待确认包的最大时间。如果超出了这个时间,发送方会认为数据包丢失,触发重传。RTO 是根据网络的 往返时延(RTT) 和 RTT的变化量 来动态计算的,具体的计算方式通常采用 加权移动平均 或 指数平滑 方法(在Linux中通常是500ms)。
- 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。
-
捎带应答:它允许在数据包中不仅发送数据,还能携带对接收数据的确认信息。简单来说,发送方在发送数据的同时,接收方也在相同的数据包中进行确认,从而避免了单独发送确认包的开销。
-
4. TCP状态转换
-
服务端状态转换流程
-
CLOSED -> LISTEN
-
触发条件:服务器调用
listen()
函数。 -
行为:启动监听指定端口,等待客户端连接请求(SYN报文)。
-
关键点:此时服务器准备好接受连接,但尚未建立任何连接。
-
-
LISTEN -> SYN_RCVD
-
触发条件:收到客户端的SYN报文(第一次握手)。
-
行为:将连接请求放入内核等待队列,并向客户端发送
SYN+ACK
报文(第二次握手)。 -
关键点:
SYN_RCVD
是半连接状态,服务端等待客户端确认(ACK)。
-
-
SYN_RCVD -> ESTABLISHED
-
触发条件:收到客户端的ACK报文(第三次握手)。
-
行为:完成三次握手,连接正式建立。
-
关键点:此时服务端可以正常收发数据。
-
-
ESTABLISHED -> CLOSE_WAIT
-
触发条件:收到客户端的FIN报文(第一次挥手)。
-
行为:发送ACK确认客户端的FIN(第二次挥手),并进入
CLOSE_WAIT
状态。 -
关键点:服务端需处理完剩余数据后再关闭连接。
-
-
CLOSE_WAIT -> LAST_ACK
-
触发条件:服务端调用
close()
主动关闭连接。 -
行为:向客户端发送FIN报文(第三次挥手),并进入
LAST_ACK
状态,等待客户端最后的ACK。 -
关键点:服务端需确保客户端收到FIN的确认。
-
-
LAST_ACK -> CLOSED
-
触发条件:收到客户端对FIN的ACK(第四次挥手)。
-
行为:连接彻底关闭。
-
关键点:服务端资源释放,终止连接。
-
-
-
客户端状态转换流程
-
CLOSED -> SYN_SENT
-
触发条件:客户端调用
connect()
发起连接。 -
行为:发送SYN报文(第一次握手)。
-
关键点:客户端等待服务端的
SYN+ACK
。
-
-
SYN_SENT -> ESTABLISHED
-
触发条件:收到服务端的
SYN+ACK
(第二次握手),并发送ACK(第三次握手)。 -
行为:连接建立完成。
-
关键点:客户端开始正常通信。
-
-
ESTABLISHED -> FIN_WAIT_1
-
触发条件:客户端调用
close()
主动关闭连接。 -
行为:发送FIN报文(第一次挥手)。
-
关键点:客户端等待服务端的ACK。
-
-
FIN_WAIT_1 -> FIN_WAIT_2
- 触发条件:收到服务端对FIN的ACK(第二次挥手)。
- 行为:进入
FIN_WAIT_2
状态,等待服务端的FIN。 - 关键点:此时客户端不再发送数据,但可能接收数据。
-
FIN_WAIT_2 -> TIME_WAIT
- 触发条件:收到服务端的FIN报文(第三次挥手)。
- 行为:发送ACK确认服务端的FIN(第四次挥手),并进入
TIME_WAIT
状态,等待2MSL(报文最大生存时间)。 - 关键点:2MSL确保网络中残留的旧报文失效,避免影响新连接。
-
TIME_WAIT -> CLOSED
- 触发条件:2MSL超时。
- 行为:客户端彻底关闭连接。
- 关键点:资源释放,连接终止。
-
5. TIME_WAIT状态
在 TCP协议 中,连接关闭时,主动关闭连接的一方需要进入 TIME_WAIT 状态。这一状态确保连接的可靠关闭,避免因网络延迟等因素导致的 数据丢失 或 连接混淆。
-
TIME_WAIT状态的必要性
- TIME_WAIT 状态的核心作用是确保所有的传输报文段(包括丢失的报文段)都已被接收并清除。具体来说,TIME_WAIT 状态持续的时间是 2 * MSL(Maximum Segment Lifetime,最大报文生存时间)。这一设计目的是保证在连接关闭后,TCP协议能够有效地处理所有的延迟报文。
- MSL 是指 TCP报文的最大生存时间,即一个数据包在网络中可以存在的最长时间。按照 RFC 1122 的规定,MSL一般为 2分钟,但不同操作系统可能会有不同的默认值。例如,在 CentOS 7 中,默认的 MSL值 为 60秒,可以通过
cat /proc/sys/net/ipv4/tcp_fin_timeout
命令查看。
-
TIME_WAIT持续2 * MSL的原因
-
确保延迟数据包被清除:在网络中,可能会有一些 延迟的报文段,即数据包的到达时间比预期的晚。这些报文段可能是由于网络中的路由延迟、交换机缓冲等原因造成的。将 TIME_WAIT 持续 2 * MSL 时间可以确保所有这些报文段被清除,避免它们对后续连接造成干扰。
-
确保最后的ACK确认包可靠到达:假设最后一个 ACK 包丢失了,服务器会在 TIME_WAIT 状态中等待。如果丢失的 ACK 包没有到达,服务器可以在 TIME_WAIT 状态下重发 FIN 包进行连接关闭,确保数据能够可靠传输。即使客户端进程已经退出,TCP连接仍然存在,服务器仍然可以重发 LAST_ACK 包。
-
-
TIME_WAIT状态的挑战
-
端口号资源占用:当一个服务器主动关闭连接时(如客户端不再活跃,服务器主动清理连接),会产生大量的 TIME_WAIT 状态连接。每个连接都会占用一个 五元组(源IP,源端口,目的IP,目的端口,协议),在大规模连接场景下,这可能导致端口号资源被大量占用,进而影响到新的连接请求。
-
TCP连接数激增:特别是在处理大量短时间内的连接请求时(例如,快速的HTTP请求、实时消息服务等),每个连接关闭后都会进入 TIME_WAIT 状态。如果每秒有大量连接请求,服务器会积累大量处于 TIME_WAIT 状态的连接,导致无法快速重新绑定相同的端口。
-
-
解决TIME_WAIT状态引起的bind失败问题
在服务器的TCP连接没有完全断开之前,不允许重新监听相同的端口。这对于一些高并发应用来说,可能不太合理。特别是当服务器需要处理大量的客户端连接,而每个连接的生存时间较短时,服务器在每次主动关闭连接后会进入 TIME_WAIT 状态,这时会导致 bind 失败或无法迅速重用端口。
为了解决这个问题,可以通过设置 SO_REUSEADDR 套接字选项来允许端口的重复使用:
- SO_REUSEADDR:允许套接字在 TIME_WAIT 状态时重用端口。通过将该选项设置为
1
,可以允许多个套接字绑定到相同的端口,但它们必须有不同的IP地址。这样,尽管端口处于 TIME_WAIT 状态,新的连接仍然能够绑定到该端口。
- SO_REUSEADDR:允许套接字在 TIME_WAIT 状态时重用端口。通过将该选项设置为
-
如何设置SO_REUSEADDR
在服务器端,使用
setsockopt()
系统调用来设置该选项。int sockfd = socket(AF_INET, SOCK_STREAM, 0); int optval = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
这行代码会允许 SO_REUSEADDR 套接字选项,允许重用端口。通过这种方式,即使在 TIME_WAIT 状态下,也可以快速重用端口号。
3)TCP机制详解
1. 流量控制机制
TCP使用 滑动窗口(Sliding Window)机制来实现流量控制。滑动窗口的大小由接收端通过 窗口大小字段 来告知发送端,发送端通过窗口大小来调节自己的发送速率。
-
窗口大小字段(Window Size)
-
窗口大小 字段存在于 TCP报文头部 中,表示接收端接收缓冲区的剩余可用空间大小,即接收端能够接收的最大数据量。
-
窗口大小 的值由接收端在 ACK包 中进行反馈,告诉发送端自己还有多少空间可以接收数据。发送端根据接收端的反馈来调整发送数据的速率。
-
-
窗口的动态调整
-
接收端缓冲区空间充足时,接收端会将较大的 窗口大小 值放入 窗口大小字段,告诉发送端可以继续高效地发送数据。
-
接收端缓冲区接近满时,接收端会将 窗口大小字段 设置为较小的值,告知发送端减缓发送速度。
-
接收端缓冲区满时,接收端会将 窗口大小字段 设置为 0,表示它已经没有缓冲区空间来接收更多数据。
-
-
零窗口(Zero Window)
-
当接收端的缓冲区满了,且无法接收更多数据时,它会将 窗口大小 设置为 0,并通过 ACK 包告诉发送端停止发送数据。
-
发送端在接收到窗口为0的反馈后,会暂停数据发送,避免数据丢失。
-
-
窗口探测(Window Probe)
-
即使接收端的窗口大小为0,发送端依然需要定期发送 窗口探测包,以检查接收端缓冲区是否有空闲空间。
-
窗口探测包是一种非常小的包,主要用于让接收端告知发送端当前的窗口大小。接收端一旦有足够的空间,便会通过 窗口大小字段 告诉发送端允许继续发送数据。
-
2. 滑动窗口机制
TCP滑动窗口机制是一个核心特性,它使得 TCP协议 在保证可靠性传输的同时,能够提高传输效率。通过使用滑动窗口,发送方可以在没有等待确认的情况下,连续发送一定数量的数据(取决于接收方窗口大小)。如果某些数据包丢失,TCP 协议会通过合适的重传机制,确保数据正确到达。
-
发送数据:发送方根据窗口大小,连续发送数据(如前四个段)。这些数据被存储在 发送缓冲区 中,直到接收到对应的确认应答(ACK)。
-
接收确认:当接收方收到数据后,它会返回 ACK包,确认已接收到的数据。接收到的 ACK包 会告知发送方哪些数据已经被接收,并通过 确认号 更新窗口。
-
滑动窗口:一旦接收到确认,发送方就可以将窗口向后滑动,继续发送未发送的数据。这个过程依赖于接收方的反馈和窗口大小的调整。
一个窗口内的数据为什么不一次性发送?
即使接收方的缓冲区可以接收大量数据,网络本身的带宽和延迟也可能成为瓶颈。如果发送方一次性发送过多数据,可能会导致网络中某些路由器或交换机过载,造成 网络拥塞 或数据包丢失。
分段发送 数据有助于控制每次传输的数据量,避免网络资源消耗过大,并确保在遇到丢包时能够更有效地进行重传。
滑动窗口不会向左移动。
滑动窗口大小是可以变换的。
流量控制就是通过控制滑动窗口大小实现的。
3. 快重传机制
快重传机制 是 TCP 为解决 丢包 问题而引入的优化方案。它能够在接收到 重复ACK 后,立即重发丢失的数据包,而不需要等待超时重传。
-
重复ACK:当接收方收到丢失的数据段时,它会发送重复的 ACK包,并告知发送方它期望收到的数据序列号。
-
三次重复ACK触发快重传:如果发送方连续收到相同的 ACK包 (通常是三个相同的ACK),它会判断某个数据包丢失,并立即重发丢失的数据,而不需要等待超时。
例如,接收方连续发送 ACK=1001 表示丢失了 1001-2000 这一段数据,发送方在收到三次相同的 ACK 后,就会立即重发丢失的数据。
特性 | 超时重传 | 快重传 |
---|---|---|
触发条件 | 基于 超时,如果ACK没有在规定时间内到达,重传数据包 | 基于 重复ACK,如果收到相同的ACK 3次,认为数据丢失 |
重传延迟 | 可能会有较大的延迟,直到超时发生 | 快速重传,几乎没有延迟 |
适用场景 | 适用于较严重的丢包或网络延迟情况 | 适用于轻度丢包或偶尔的网络问题 |
网络负载 | 无额外负载,只有超时机制的计算 | 会产生重复的ACK包,增加额外的网络开销 |
性能影响 | 因为依赖超时机制,延迟较大,可能导致吞吐量下降 | 快速恢复丢失数据,提高吞吐量,但在极端丢包情况下不太有效 |
4. 延迟应答
在 TCP协议 中,每当接收方收到一个数据包时,它通常会立即向发送方返回一个 ACK包,以确认数据的接收。然而,频繁地发送 ACK包 会导致网络中出现大量的小数据包,这些包虽然包含确认信息,但对数据传输并无实际贡献。
为了优化这个过程,TCP允许接收方 延迟ACK的发送,并在适当的时机发送一个 合并的ACK包。这种做法减少了单独 ACK包 的数量,提高了网络的效率。
- 延迟ACK:接收方在收到数据后,会延迟一段时间来决定是否发送 ACK包。
- 合并ACK包:如果接收方接收了多个数据包,它会在一个 ACK包 中确认多个数据包的接收,而不是对每个数据包都发送一个独立的 ACK包。
- 最大延迟时间:接收方通常会在收到数据包后的 200ms 内发送一个 ACK包。如果这段时间内没有收到新的数据,接收方会立即发送确认包。
- 特殊情况下的例外:如果接收方的窗口已经满了,或者接收方发送了数据包(例如, TCP的双向数据流),它会立即发送 ACK包,以确认收到的数据。
- 编程时调用
recv
read
尽快将缓冲区中的数据拿走,就能保证延迟应答返回的窗口能够更大。
5. 拥塞控制机制
拥塞控制(Congestion Control)是 TCP协议 中的一个关键机制,旨在避免网络过载,确保数据流的稳定和高效传输。TCP通过动态调整数据的发送速率,来避免网络的拥塞和资源浪费,从而保证高效的数据传输。
- 慢启动(Slow Start):慢启动是TCP连接开始时的一个阶段,用于避免过快的开始发送数据,导致网络拥塞。
- 在初始阶段,发送方的 拥塞窗口(cwnd)从1个MSS(最大报文段大小)开始。
- 每收到一个 ACK 包,发送方会将 拥塞窗口增加1MSS,表现为指数增长。这意味着随着每个往返时间(RTT),发送方可以发送的未确认数据量将以指数级增长。
- 慢启动持续直到拥塞窗口达到某个阈值(慢启动阈值 ssthresh),然后进入 拥塞避免 阶段。
- 拥塞避免(Congestion Avoidance):当拥塞窗口达到慢启动阈值后,TCP进入 拥塞避免 阶段,开始减少拥塞窗口的增长速度,避免网络拥塞。
- 在 拥塞避免 阶段, 拥塞窗口的增长不再是指数级的,而是以 线性 增长的方式增长。每个RTT周期,窗口大小增加1MSS。
- 这一方式通过减缓发送速率的增长,避免在网络负载较高时过快增加发送数据,减少了网络拥塞的发生。
- 快重传:见第三小节。
- 快恢复(Fast Recovery):快恢复机制和 快重传 一起工作,确保丢包发生后能够快速恢复数据传输,避免过度减少发送速率。
- 在 快重传 触发后,发送方不再像 慢启动 那样将拥塞窗口重置为1,而是将拥塞窗口减半,并开始继续发送数据。
- 快恢复旨在避免丢包后立即回到慢启动阶段,而是通过减少窗口大小进行较为平缓的恢复。
- 滑动窗口大小 = Min(接受窗口,拥塞窗口)
**类别 ** | 机制 | 说明 |
---|---|---|
可靠性 | 校验和 | 用于确保数据在传输过程中未被篡改或损坏。 |
序列号(按序到达) | 保证数据包按照发送顺序到达接收方。 | |
确认应答 | 接收方通过ACK包确认收到的数据,确保数据传输的可靠性。 | |
超时重发 | 如果数据包未在超时时间内得到确认,发送方会重传数据。 | |
连接管理 | 管理TCP连接的建立、维持和关闭,确保连接的可靠性。 | |
流量控制 | 控制发送速率,防止接收方缓冲区溢出。 | |
拥塞控制 | 防止网络过载,调整发送速率以避免网络拥塞。 | |
提高性能 | 滑动窗口 | 允许发送方在接收到部分确认时继续发送数据,提高传输效率。 |
快速重传 | 通过连续收到重复ACK包触发重传,减少丢包后的延迟。 | |
延迟应答 | 延迟发送ACK包,减少网络中小数据包的数量,提高带宽利用率。 | |
捎带应答 | 将多个数据包的ACK合并成一个ACK包,从而减少ACK包的数量,提高效率。 |
4)网络通信模型
1. 面向字节流
面向字节流 是 TCP 协议的传输方式,数据传输以 字节流 的形式进行,不保留数据的边界或结构。
-
特点:
-
数据流式传输:TCP将数据作为一个连续的字节流传输,发送方和接收方都不关心数据的分包和重组。无论数据包有多大,接收方都按字节顺序逐个接收。
-
可靠性:由于 TCP协议 是面向连接的协议,它提供 可靠传输,通过 确认应答、重传机制 和 顺序控制 来确保数据的完整性和顺序。
-
无边界:在字节流模型中,数据的边界(例如消息的结束)是由应用层来定义的。传输层协议(TCP)不会干预数据的划分,数据流的划分由应用层程序来处理。
-
顺序保证:TCP提供 顺序保证,即接收方会按照发送方发送的顺序接收数据,即使中途数据包丢失或顺序乱了,TCP会自动进行重传,并确保顺序正确。
-
-
优点:
-
顺序可靠:TCP会确保字节按顺序到达,且在传输过程中不会丢失。
-
流量控制:通过 滑动窗口 和 拥塞控制,TCP能够有效管理数据流的发送速率,防止网络过载。
-
-
缺点:
-
相对较慢:由于TCP需要进行连接管理、数据重传、流量控制等处理,导致其在实时性和效率方面不如某些无连接协议(如UDP)。
-
较高的开销:每个连接的建立和维护需要消耗一定的资源,适合需要高可靠性的数据传输,但不适合实时性要求很高的应用。
-
2. 面向数据报
面向数据报 是 UDP 协议的传输方式,数据传输是以 数据报 的形式进行,数据包是独立的,每个数据报都有自己的完整信息,接收方将按各自的数据报接收。
-
特点:
-
无连接性:UDP是一个无连接的协议,它不需要在通信之前建立连接。每个数据报(即UDP数据包)是独立的,发送方可以随时发送数据报,接收方也不需要确认收到的数据。
-
独立性:每个 UDP数据报 都包含完整的目的地址信息(包括源地址和目标地址),并且数据报的大小是有限的。接收方在接收时会将每个数据报独立处理。
-
没有保证:UDP并不保证数据的可靠性,它不进行数据确认,也不保证数据的顺序。数据报可能会丢失、重复,或者顺序错乱。
-
应用层处理:UDP的应用层需要自己处理丢包、数据重传和顺序问题。UDP提供一种简单而快速的数据传输方式,适用于对延迟要求高且能容忍丢包的应用,如实时视频流、在线游戏等。
-
-
优点:
-
低延迟:由于没有连接建立和确认机制,UDP适用于对实时性要求高的应用。
-
轻量级:UDP的协议开销比TCP小,不需要维护连接或执行复杂的拥塞控制、流量控制等操作。
-
-
缺点:
-
不可靠:UDP不提供数据的可靠性保障,数据可能丢失或乱序到达,应用层需要自行处理。
-
无顺序保证:UDP无法保证数据包的顺序,接收方收到的顺序可能与发送方发送的顺序不同。
-
5)粘包问题
1. 粘包问题
粘包问题 是 TCP协议 中的一种常见问题,特别是在 面向字节流 的通信中。由于 TCP 是一个 面向字节流 的协议,它不会在数据之间加上分隔符来标识消息的边界,导致接收方无法直接知道一个完整的数据包的边界。这种情况称为 粘包。
粘包 是指多个应用层的数据包被发送方合并成一个大的数据包进行传输,而接收方不能区分原本的多个数据包。接收方通常需要通过一定的规则(如包长、特定的分隔符)来从字节流中拆分数据。
-
粘包问题的原因:
-
TCP是字节流协议:在TCP中,数据是作为连续的字节流传输的,发送的数据并没有边界。发送方可以将多个小数据包拼接在一起,发送给接收方,而接收方无法直接知道数据包的结束位置。
-
数据合并:在发送过程中,多个小数据包可能被合并为一个数据包发送出去,这种情况被称为 粘包。尤其是在网络传输中,可能由于系统的缓冲区、数据传输过程中的合并等原因,多个消息会被打包成一个大的数据包一起发送。
-
数据分割:由于TCP协议的 流控制 和 拥塞控制 机制,发送的数据并不一定会按发送的顺序或固定大小来传输,接收方可能会一次接收到多个分段的数据包,导致接收方无法正确区分每个独立的数据包。
-
-
粘包问题的表现:
-
多个数据包合并:多个应用层的消息在传输时合并成一个数据包,接收方无法知道每个消息的边界。
-
数据错乱:由于消息的边界无法识别,接收方可能会误把两个消息的内容拼接到一起,导致应用层数据解析错误。
-
2. 粘包和拆包的关系
粘包 和 拆包 是两个常见的网络通信问题:
- 粘包:多个数据包合并成一个大数据包发送到接收方,接收方无法识别每个数据包的边界。
- 拆包:一个大的数据包被拆成多个小的数据包发送给接收方,接收方需要能够正确重组这些数据包。
3. 解决粘包问题的方法
为了防止或解决粘包问题,通常有以下几种方法:
-
固定长度协议
最简单的方法是使用 固定长度的消息。每次发送的数据包长度都是固定的,这样接收方就可以按照固定的字节数来分割数据包。
-
优点:简单明了,接收方按固定长度读取数据即可。
-
缺点:不灵活,数据包长度必须预先确定,浪费带宽,无法处理变长的数据。
-
-
消息分隔符
通过在每个数据包的结尾添加一个特定的分隔符(如 ‘\n’、‘\0’、‘\r\n’ 或者自定义的标识符)来标识数据包的边界。
-
优点:非常简单且直观。
-
缺点:分隔符可能会与数据本身重复,导致误解析或数据错误,需要额外的处理来避免分隔符与数据冲突。
-
-
数据包头部包含数据长度
这是最常见的解决粘包问题的方法。通过在每个数据包的 头部 包含一个 数据包的长度字段,接收方通过这个字段来确定数据的边界。
-
实现方法:
-
发送方在每个数据包的头部加上一个字段,记录消息的 长度。
-
接收方读取头部的长度字段,然后按照这个长度读取相应的数据包。
-
优点:
- 无论数据包的内容多大或多小,都可以通过长度字段来识别数据包的边界。
- 常见于 HTTP、FTP、MQTT、TCP流协议 等应用中。
-
缺点:增加了每个数据包的开销,因为每个数据包需要包含额外的长度字段。
-
-
举例:
- 发送数据时,在每个数据包头部添加一个 固定大小的字段,记录数据的长度。比如,发送一个数据包
{长度字段(4字节) + 数据内容}
,接收方先读取 长度字段(4字节),再读取指定长度的 数据内容。
- 发送数据时,在每个数据包头部添加一个 固定大小的字段,记录数据的长度。比如,发送一个数据包
-
-
分包和合包控制(基于应用协议)
这种方法通过协议中的规则来明确如何划分和合并数据包。例如,在 HTTP协议 中,头部字段(如
Content-Length
或Transfer-Encoding
)指示了数据体的大小,接收方可以根据这些字段来判断数据包的结束。-
优点:适用于变长数据的处理,能解决不同数据包大小的问题。
-
缺点:需要应用层协议的支持。
-
举例:在 HTTP/1.1 协议中,
Content-Length
头字段标明了消息体的长度,接收方根据该字段的值来读取数据。
-
7) TCP异常
1. 进程终止
当进程终止时,相关的 TCP连接 会被释放,但实际上,在 进程终止 的情况下,TCP连接的断开行为和正常的连接关闭并没有太大区别。
- 释放文件描述符:当一个进程结束时,操作系统会自动关闭进程打开的所有文件描述符,包括套接字描述符。套接字一旦关闭,TCP连接就会被终止。
- 发送FIN(正常关闭):如果进程是通过正常关闭连接的方式退出,TCP会发送 FIN 报文段来通知对方连接将被关闭。接收方收到 FIN 后,会回送一个 ACK,并且也会执行连接的关闭操作。
- 和正常关闭没有区别:当进程退出时,它仍然会按照 TCP协议 的标准流程(四次挥手)来优雅地关闭连接。系统会通过 FIN 关闭连接,并不会直接丢弃连接状态。
2. 机器重启
机器重启 会导致与 进程终止 类似的情况,因为在重启过程中,所有的 TCP连接 都会丢失。
- 释放连接状态:在计算机重启时,所有正在进行的连接都会被终止。TCP连接的状态会丢失,操作系统会关闭所有的套接字,释放网络资源。
- TCP连接断开:类似进程终止的情况,连接会被释放,发送 FIN 的过程会根据具体的连接状态进行处理。
- 不会丢失数据:如果有未确认的数据,TCP协议会确保在重启之前通过重传进行可靠的传输。
3. 机器掉电/网线断开
机器掉电或网线断开 的情况比 进程终止 和 机器重启 更复杂。因为它属于 物理层 或 链路层 的问题,导致一方与另一方的连接断开,而这种断开并不会立即被另一方感知。
- 接收端认为连接仍在:在机器掉电或网线断开后,接收方无法立即得知连接已经断开,因为 TCP 协议不会立即检测到物理连接的丢失。接收方可能会认为连接仍然是有效的。
- 写操作时检测连接丢失:当接收端尝试发送数据(写操作)时,它会发现连接已经中断,因为它无法向对方发送数据。此时,接收端会收到一个 RST(重置)报文,表示连接异常被强制关闭。
- TCP的保活机制:即使接收端没有主动写入操作,TCP协议本身有一个 保活机制。保活机制通过定期发送 探测包 来询问连接的另一方是否仍然存在。如果对方没有响应,这时TCP连接会被认为已丢失,连接会被释放,接收方会收到 RST 包。
- 探测包(keep-alive probes)通常是小的数据包,如果对方没有回应,连接会被认为不可用,并被强制关闭。
- TCP保活定时器:根据不同操作系统的实现,TCP保活定时器会在连接空闲一段时间后启动,并开始定期发送保活探测包。如果对方没有响应,连接会被释放。
6)网络工具
1.网络状态
-
netstat
:命令行工具,用于显示网络连接、路由表、接口统计信息、伪连接(即 UNIX 域套接字)等信息。它在系统管理和故障排查中非常有用,尤其是在需要查看网络活动和调试网络问题时。netstat [选项]
netstat -a
-a
:显示所有连接(包括监听状态和已连接的连接)。-t
:显示TCP连接。-u
:显示UDP连接。-n
:显示IP地址和端口号,而不是进行域名解析。用于快速查看数字地址。-l
:仅显示在监听状态的服务端口。-p
:显示与每个连接相关的进程信息(需要 root 权限)-r
:显示路由表。-i
:显示网络接口统计信息。-s
:显示网络统计数据(包括TCP、UDP、ICMP等协议的统计信息)。
-
pidof
:pidof
是一个在 Linux 系统中用来查找指定程序或进程的 PID(Process ID,进程ID) 的命令。它返回的是程序或进程的 ID,用于管理和监控正在运行的进程。pidof <process_name>
//查找 nginx 进程的 PID pidof nginx
<process_name>
:要查找的进程的名称或可执行文件的名称。