网络编程基础
一、网络发展和网络协议
1.1、网络发展
- 独立模式:计算机之间相互独立
- 网络互联:多台计算机链接在一起,完成数据共享
- 局域网LAN:计算机数量更多,通过交换机和路由器连接在一起
- 广域网WAN:将远隔千里的计算机都连接在一起
- 所谓 "局域网" 和 "广域网" 只是一个相对的概念.
1.2、网络协议
1.2.1、OSI七层模型:
OSI七层模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范。
分层名称 | 功能 | 功能概览 | |
---|---|---|---|
7 | 应用层 | 针对特定应用的协议 | HTTP/HTTPS FTP(文件传输协议) SMTP(邮件传输协议) POP3(邮局协议) |
6 | 表示层 | 设备固有数据格式和 网络标准数据格式的转换 加密 | 接收不同表现形式的信息 如文字流,图像,声音等 SSL/TLS |
5 | 会话层 | 通信管理,负责简历和断开通信连接 管理传输层以下的分层 会话管理 | 何时建立连接 何时断开连接 以及保持多久的链接 RPC,NetBIOS |
4 | 传输层 | 管理两个节点之间的数据传输 负责可靠传输 确保谁被可靠的传送到目的地 端到端传输 | 是否有数据丢失 TCP,UDP |
3 | 网络层 | 地址管理和路由选择 路由 ,IP寻址 | 经过那个路由器传递到目标地址 IP,ICMP,Router |
2 | 数据链路层 | 互联设备之间传送和识别数据帧 帧传输,MAC寻址 | 数据帧和比特流之间的转换 如:以太网。PPP。Switch |
1 | 物理层 | 以“0”,“1”代表电压的高低, 灯光的闪灭 界定连接器和网线的规格 传输原始比特流 | 比特流和电子信号之间的切换, 连接器和网线的规格 如以太网,光纤,Hub |
1.2.2、TCP/IP五层模型
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求
物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆 (现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决 定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.
数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测 到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太 网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.
网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规 划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.
传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标 主机.
应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问 协议(Telnet)等. 我们的网络编程主要就是针对应用层.
1.3、数据包封装和分用
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报 (datagram),在链 路层叫做帧(frame). 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装 (Encapsulation). 首部信息中包含了一些类似于首部有多长, 载荷(payload)有多长, 上层协议是什么等信息. 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部, 根据首部中的 "上层协议 字段" 将数据交给对应的上层协议处理.
1.4、网络中的地址管理
1.4.1、IP地址
- IP协议有两个版本, IPv4和IPv6. 凡是提到IP协议, 没有特殊说明的, 默认都是指IPv4
- IP地址是在IP协议中, 用来标识网络中不同主机的地址;
- 对于IPv4来说, IP地址是一个4字节, 32位的整数;
- 我们通常也使用 "点分十进制" 的字符串表示IP地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个 字节, 范围是 0 - 255;
1.4.2、MAC地址
- MAC地址用来识别数据链路层中相连的节点;
- 长度为48位, 及6个字节. 一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
- 在网卡出厂时就确定了, 不能修改. mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址, 可 能会冲突; 也有些网卡支持用户配置mac地址)
二、理解源IP地址和目的IP地址
对于端口号的理解:
端口号(port)是传输层协议的内容。端口号是一个2字节16位的整数,用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。IP地址+端口号能够标识网络上的某一台主机的某一个进程。一个端口号只能被一个进程占用。
一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。
三、网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分。
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换。
#include<arpa/inet.h>
unit32_t htonl(unit32_t hostlong);
unit16_t htons(unit16_t hostshort);
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t netshort);
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
四、socket
4.1、socket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
//socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
//应用程序可以像读写文件一样用read/write在网络上收发数据;
//如果socket()调用出错则返回-1;
//对于IPv4, family参数指定为AF_INET;
//对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
//protocol参数的介绍从略,指定为0即可。
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
//服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后
//就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
//bind()成功返回0,失败返回-1。
//bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听
//myaddr所描述的地址和端口号;
//前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
//listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5), 具体细节同学们课后深入研究;
//listen()成功返回0,失败返回-1;
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
//三次握手完成后, 服务器调用accept()接受连接;
//如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
//addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
//如果给addr 参数传NULL,表示不关心客户端的地址;
//addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//客户端需要调用connect()连接服务器;
//connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
//connect()成功返回0,出错返回-1;
4.2、sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同。
- IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址.
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
- socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为 参数;
4.3、简单实现一个UDP和TCP网络程序、
代码链接:简易UDP
代码链接:简易TCP
五、HTTP协议
5.1、HTTP协议格式
一个HTTP请求由以下4部分组成:<请求行>、<请求头>、<空行>、<请求体>(可选)
5.1.1、请求行
-
格式:
<方法> <请求URI> <HTTP版本>
示例:
GET /index.html HTTP/1.1
-
关键字段:
-
方法:定义操作类型,如
GET
(获取资源)、POST
(提交数据)、PUT
(更新资源)、DELETE
(删除资源)等。 -
请求URI:目标资源的路径(如
/api/data
)或完整URL(代理场景)。 -
HTTP版本:如
HTTP/1.1
或HTTP/2
-
5.2.1、请求头
-
以键值对形式传递附加信息,每行一个头字段,格式为
Header-Name: value
。 -
常见请求头:
-
Host: www.example.com
(目标主机,HTTP/1.1必需) -
User-Agent: Mozilla/5.0
(客户端标识) -
Content-Type: application/json
(请求体的数据类型) -
Content-Length: 128
(请求体的字节长度) -
Authorization: Bearer token123
(身份验证凭证)
-
5.1.3、 空行
-
请求头结束后需一个空行(
\r\n
),表示头部结束。
5.1.4、 请求体(Request Body)
-
适用场景:
POST
、PUT
等方法需要传递数据时使用。 -
示例(JSON数据):
{"username": "admin", "password": "123456"}
完整HTTP请求示例
POST /login HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/json
Content-Length: 42
{"username": "admin", "password": "123456"}
5.2、HTTP响应格式
<状态行><响应头><空行><响应体>(可选)
5.2.1. 状态行(Status Line)
-
格式:
<HTTP版本> <状态码> <原因短语>
示例:HTTP/1.1 200 OK
-
关键字段:
-
状态码:3位数字,表示请求结果。如:
-
200 OK
:成功 -
404 Not Found
:资源未找到 -
500 Internal Server Error
:服务器内部错误
-
-
原因短语:状态码的文本描述(如
OK
)。
-
5.2.2. 响应头(Response Headers)
-
格式与请求头类似,传递服务器信息或资源元数据。
-
常见响应头:
-
Content-Type: text/html
(响应体类型) -
Content-Length: 1024
(响应体长度) -
Set-Cookie: sessionid=abc123
(设置Cookie) -
Cache-Control: max-age=3600
(缓存控制)
-
5.2.3. 空行
-
响应头结束后需一个空行(
\r\n
)。
5.2.4. 响应体(Response Body)
-
服务器返回的实际内容,如HTML页面、JSON数据等。
完整请求示例
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 127
<html>
<body>
<h1>Welcome to Example.com</h1>
</body>
</html>
5.3、请求格式和响应格式总结
组件 | 请求格式 | 响应格式 |
---|---|---|
起始行 | 方法 URI 版本 (如 GET / HTTP/1.1 ) | 版本 状态码 原因短语 (如 HTTP/1.1 200 OK ) |
头部 | 客户端信息(Host、User-Agent等) | 服务器信息(Content-Type、Set-Cookie等) |
空行 | 必须存在(\r\n ) | 必须存在(\r\n ) |
主体 | 可选(如POST数据) | 可选(如HTML页面) |
5.4、HTTP请求分的方法
方法类型 | 方法名称 | 核心用途 |
---|---|---|
安全方法 | GET、HEAD、OPTIONS | 不修改服务器资源 |
非安全方法 | POST、PUT、DELETE、PATCH | 可能修改服务器资源 |
幂等方法 | GET、PUT、DELETE、HEAD、OPTIONS、TRACE | 多次请求效果相同 |
非幂等方法 | POST、PATCH | 多次请求可能产生不同结果 |
缓存支持方法 | GET、HEAD | 响应可被缓存 |
场景 | 推荐方法 | 原因 |
---|---|---|
查询数据 | GET | 安全、幂等,支持缓存 |
创建新资源(ID由服务器生成) | POST | 非幂等,适合创建操作 |
全量更新资源(已知ID) | PUT | 幂等,需提供完整资源数据 |
删除资源 | DELETE | 幂等,明确资源标识 |
部分更新资源 | PATCH | 仅传递需修改的字段 |
跨域预检请求 | OPTIONS | 获取服务器支持的CORS方法 |
5.4.1、常见问题:post请求和get请求区别
特性 | GET | POST |
---|---|---|
语义 | 获取资源(查询) | 提交数据(创建或触发操作) |
数据位置 | URL的查询字符串(Query String) | 请求体(Body) |
数据可见性 | 明文暴露在URL中(易被缓存、日志记录) | 封装在请求体中(相对隐蔽) |
安全性 | 安全方法(不修改服务器数据) | 非安全方法(可能修改数据) |
幂等性 | 幂等(多次请求结果相同) | 非幂等(多次请求可能产生不同结果) |
缓存 | 可被浏览器或代理缓存 | 默认不缓存(需显式指定缓存头) |
数据长度限制 | 受URL长度限制(浏览器通常限制为2KB~8KB) | 无严格限制(由服务器配置决定) |
适用场景 | 搜索、分页、资源详情查询 | 登录、注册、文件上传、表单提交 |
浏览器行为 | 可收藏、可分享链接 | 无法直接通过URL重现操作 |
GET | POST | |
---|---|---|
核心目的 | 获取数据(安全、幂等) | 提交数据(非安全、非幂等) |
数据安全 | 低(URL暴露) | 较高(Body传输) |
适用操作 | 查询、搜索、分页 | 创建、更新、删除、触发业务逻辑 |
设计原则 | 遵循RESTful的“读”操作 | 遵循RESTful的“写”操作 |
5.5、HTTP状态码
状态码 | 类别 | 常见用途 |
---|---|---|
200 | 成功 | 标准成功响应 |
301/302 | 重定向 | 资源路径变更 |
400 | 客户端错误 | 请求参数错误 |
401/403 | 客户端错误 | 认证/权限问题 |
404 | 客户端错误 | 资源不存在 |
500 | 服务器错误 | 服务器内部异常 |
503 | 服务器错误 | 服务过载或维护 |
核心原则:
-
2xx → 成功处理,可继续后续逻辑。
-
3xx → 需遵循响应头重定向。
-
4xx → 客户端需修改请求。
-
5xx → 服务端问题,需排查修复。
5.6、HTTP端口号
端口号范围:0~65535(16位无符号整数),按用途分为三类:
端口类型 | 范围 | 典型用途 | 示例端口 |
---|---|---|---|
知名端口 | 0~1023 | 系统核心服务 | 22 (SSH), 80 (HTTP) |
注册端口 | 1024~49151 | 用户/企业应用 | 3306 (MySQL), 8080 |
动态端口 | 49152~65535 | 客户端临时通信 | 操作系统随机分配 |
5.6.1. 知名端口(0~1023)
特点:
-
绑定系统级服务(如HTTP、FTP等),普通用户程序默认无法使用。
-
需要管理员权限(如Linux的
root
)才能监听这些端口。
常见端口示例:
端口 | 协议/服务 | 用途 |
---|---|---|
20/21 | FTP | 文件传输(数据/控制连接) |
22 | SSH | 安全远程登录 |
25 | SMTP | 邮件发送 |
53 | DNS | 域名解析(UDP/TCP) |
80 | HTTP | 网页访问 |
443 | HTTPS | 加密网页访问 |
3306 | MySQL | 数据库服务(虽属注册端口,但广泛使用) |
5.6.2. 注册端口(1024~49151)
特点:
-
用于用户级应用程序或企业服务,需向IANA注册(非强制)。
-
普通用户程序可绑定,但建议避免冲突(如避免使用已约定俗成的端口)。
常见端口示例:
端口 | 协议/服务 | 用途 |
---|---|---|
1433 | MSSQL | Microsoft SQL Server |
1521 | Oracle DB | Oracle数据库默认端口 |
2049 | NFS | 网络文件系统 |
3306 | MySQL | 数据库服务 |
3389 | RDP | 远程桌面协议 |
5432 | PostgreSQL | PostgreSQL数据库 |
6379 | Redis | 内存数据库服务 |
8080 | HTTP-Alt | 替代HTTP端口(如Tomcat默认) |
5.6.3. 动态/私有端口(49152~65535)
特点:
-
客户端发起连接时由操作系统临时分配,通信结束后释放。
-
无固定用途,避免与服务端端口冲突。
示例场景:
浏览器访问网页时,本地生成动态端口(如52000
)与服务器的80端口通信。、
5.6.4.常见问题
为什么1024以下端口需要特权权限?
答:防止普通用户程序占用系统关键服务端口,确保系统稳定性。
Q2: 如何查看当前系统的端口占用?
Linux/Unix:
-
netstat -tuln # 查看监听中的TCP/UDP端口 ss -tunlp # 更高效的替代命令 lsof -i :80 # 查看占用80端口的进程
Q3: 端口号能否重复使用?
答:同一协议(TCP/UDP)的同一端口在同一时刻只能被一个进程绑定,但可通过SO_REUSEADDR
选项复用(需谨慎)。
6.TCP和UDP
6.1、UDP
6.1.1、UDP协议概述
定位:传输层协议(基于IP协议)。
核心特性:
-
无连接:无需预先建立连接,直接发送数据。每个数据报独立处理,无握手或状态维护。
-
不可靠传输:不保证数据到达、不重传、不维护顺序。无确认、重传或排序机制,依赖上层协议处理丢包和乱序。
-
轻量高效:头部开销小(仅8字节),适合低延迟场景。
6.1.2、UDP数据格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+---------------+---------------+
| Source Port (16 bits) | Destination Port (16 bits) |
+---------------+---------------+---------------+---------------+
| Length (16 bits) | Checksum (16 bits) |
+---------------+---------------+---------------+---------------+
| Payload (变长) |
+---------------------------------------------------------------+
字段详解
字段 | 长度 | 含义 |
---|---|---|
Source Port | 16 bits | 源端口号:发送方的应用程序端口号(可选,若为0表示不指定)。 |
Destination Port | 16 bits | 目标端口号:接收方的应用程序端口号(必需)。 |
Length | 16 bits | UDP数据报总长度:包括头部和数据部分,最小值为8(仅有头部)。 |
Checksum | 16 bits | 校验和:检测头部和数据的传输错误(可选,IPv4中可为0,IPv6中必须计算)。 |
Payload | 变长 | 数据内容:上层协议(如DNS、RTP)传递的实际数据。 |
6.1.3、UDP适用场景
场景 | 原因 |
---|---|
实时音视频传输 | 低延迟优先,容忍少量丢包(如Zoom、VoIP)。 |
DNS查询 | 请求-响应模式,快速且数据量小,重试成本低。 |
广播/组播 | UDP支持一对多通信(如IPTV、在线游戏状态同步)。 |
IoT设备通信 | 资源受限设备适合轻量协议(如传感器数据上报)。 |
6.2、TCP
6.2.1、TCP协议概述
定位:传输层协议,提供可靠、面向连接的字节流服务。
核心特性:
-
可靠性:通过确认应答、超时重传、流量控制等机制确保数据完整有序。
-
面向连接:需通过三次握手建立连接,四次挥手终止连接。
-
全双工通信:双方可同时发送和接收数据。
-
拥塞控制:动态调整发送速率以适应网络状况。
6.2.2、TCP报文格式
TCP报文由**头部(Header)和数据部分(Payload)**组成,头部最小20字节(最大60字节,含选项):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+---------------+---------------+
| Source Port (16 bits) | Destination Port (16 bits) |
+---------------+---------------+---------------+---------------+
| Sequence Number (32 bits) |
+---------------+---------------+---------------+---------------+
| Acknowledgment Number (32 bits) |
+---------------+---------------+---------------+---------------+
| Data Offset | Reserved |Control Flags| Window Size (16 bits) |
+---------------+---------------+---------------+---------------+
| Checksum (16 bits) | Urgent Pointer (16 bits) |
+---------------+---------------+---------------+---------------+
| Options (0~40 bytes, optional) |
+---------------+---------------+---------------+---------------+
| Payload (变长) |
+-----------------------------------------------------------------------+
字段详解
字段 | 长度 | 作用 |
---|---|---|
Source/Dest Port | 16 bits | 源/目标端口号(标识应用程序)。 |
Sequence Number | 32 bits | 序列号:当前报文段第一个字节的编号(保证数据有序)。 |
Acknowledgment Num | 32 bits | 确认号:期望收到的下一字节的序列号(用于ACK应答)。 |
Data Offset | 4 bits | 头部长度(以4字节为单位,最小值为5,即20字节)。 |
Control Flags | 9 bits | 控制标志位(如SYN、ACK、FIN等,见下表)。 |
Window Size | 16 bits | 接收窗口大小:告知发送方可发送的数据量(流量控制)。 |
Checksum | 16 bits | 校验和:覆盖头部、数据和伪头部(类似UDP)。 |
Urgent Pointer | 16 bits | 紧急指针:标识紧急数据的末尾位置(需设置URG标志)。 |
控制标志位
标志位 | 名称 | 含义 |
---|---|---|
SYN | 同步 | 建立连接时同步序列号(三次握手使用)。 |
ACK | 确认 | 确认号有效(多数报文ACK=1)。 |
FIN | 结束 | 请求终止连接(四次挥手使用)。 |
RST | 复位 | 强制断开连接(异常处理)。 |
PSH | 推送 | 接收方应尽快将数据交给应用层。 |
URG | 紧急 | 紧急数据需优先处理(配合紧急指针)。 |
6.2.3、TCP状态机
状态 | 描述 |
---|---|
CLOSED | 初始状态,表示无连接。 |
LISTEN | 服务器等待连接请求(SYN)。 |
SYN_SENT | 客户端已发送SYN,等待ACK。 |
SYN_RCVD | 服务器收到SYN并发送SYN-ACK,等待ACK。 |
ESTABLISHED | 连接已建立,可正常传输数据。 |
FIN_WAIT_1 | 主动关闭方发送FIN,等待ACK。 |
FIN_WAIT_2 | 收到对端ACK,等待对端FIN。 |
CLOSE_WAIT | 被动关闭方收到FIN并发送ACK,等待应用层关闭。 |
LAST_ACK | 被动关闭方发送FIN,等待ACK。 |
TIME_WAIT | 主动关闭方收到FIN并发送ACK,等待2MSL(确保报文消失)。 |
6.2.4、三次握手和四次挥手
三次握手(建立连接)
Client Server
| |
| SYN (seq=x) →| 客户端发送SYN,进入SYN_SENT状态
|← SYN-ACK (seq=y, ack=x+1) | 服务器响应SYN-ACK,进入SYN_RCVD状态
| ACK (ack=y+1) →| 客户端确认,双方进入ESTABLISHED状态
-
目的:同步初始序列号(ISN),协商窗口大小和MSS(最大报文段长度)。
四次挥手(终止连接)
Client Server
| |
| FIN (seq=u) →| 客户端发起关闭,进入FIN_WAIT_1
|← ACK (ack=u+1) | 服务器确认,进入CLOSE_WAIT
|← FIN (seq=v, ack=u+1) | 服务器发起关闭,进入LAST_ACK
| ACK (ack=v+1) →| 客户端确认,进入TIME_WAIT(等待2MSL)
-
TIME_WAIT作用:确保最后一个ACK到达,防止旧连接报文干扰新连接。
6.2.5、TCP应用场景
场景 | 原因 |
---|---|
文件传输 | 需确保数据完整(如FTP、HTTP文件下载)。 |
网页浏览 | HTTP/HTTPS依赖TCP可靠传输HTML、CSS、JS等资源。 |
电子邮件 | SMTP、POP3、IMAP协议需要可靠传递邮件内容。 |
数据库操作 | SQL查询结果需准确无误(如MySQL、PostgreSQL)。 |
6.3、TCP和UDP的区别
TCP:
面向连接,传输数据需要通过三次握手建立连接,通过四次挥手断开连接。
可靠传输,通过应答(ACK),超时重传,流量控制和拥塞控制确保数据完整,有序到达
字节流模式,数据无边界,可能合并和拆分,(需要应用层处理粘包、拆包问题)
效率低,头部较大(10字节+选项),且需要额外控制机制,如连接管理,重传
动态调整速率,通过滑动窗口,拥塞算法适应网络状况,避免拥塞。
一对一通信,每条连接仅支持两个端点。
UDP
无连接。直接发送数据,无需预先建立连接。
不可靠传输。不保证数据到达或顺序,可能丢包、乱序。
数据报模式。每个数据包独立且有明确边界,接收端按发送次数读取。
效率高。头部仅8字节,无额外控制开销,适合实时性要求高的场景。
无控制机制。可能持续高速发送数据,易导致网络拥塞。
支持一对一、一对多、多对多。适用于广播、组播(如IP电话)。
特性 | TCP | UDP |
---|---|---|
连接性 | 面向连接 | 无连接 |
可靠性 | 高(确保数据完整) | 低(可能丢包) |
传输效率 | 较低(控制开销大) | 高(开销小) |
数据边界 | 字节流(需处理粘包) | 数据报(保留边界) |
适用场景 | 文件传输(FTP)、网页(HTTP/HTTPS)、邮件(SMTP) | 实时通信、广播、视频会议 |
6.4、TCP常见问题
6.4.1、粘包问题
由于TCP是字节流协议,数据无明确边界,导致:
-
粘包:多个数据包被合并接收(发送方快速写入小包,接收方缓冲区未及时读取)。
-
拆包:单个数据包被拆分接收(数据包大于接收缓冲区大小或MSS)。
解决方案对比表
方法 | 实现方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
固定长度法 | 每个数据包固定长度,不足部分填充空字符。 | 实现简单,解析高效 | 浪费带宽,灵活性差 | 心跳检测、固定指令(如传感器上报) |
分隔符法 | 在数据包末尾添加特殊分隔符(如\n 、\r\n 或自定义字符)。 | 直观易调试,适合文本协议 | 需处理分隔符转义,大数据效率低 | HTTP头、Redis协议、日志传输 |
长度前缀法 | 在数据头部添加长度字段(如4字节整数),标明后续数据的实际长度。 | 灵活高效,通用性强 | 需解析头部,增加复杂度 | 二进制协议(gRPC、自定义协议) |
高级协议法 | 使用标准协议(如HTTP/2的帧结构)或自定义协议(TLV格式:类型+长度+值)。 | 扩展性强,支持复杂业务逻辑 | 学习成本高,实现复杂 | 高并发场景(金融交易、流媒体) |
避免粘包策略表
策略 | 具体操作 | 注意事项 |
---|---|---|
明确数据边界 | 设计应用层协议时强制定义数据边界(如长度前缀或分隔符)。 | 避免混合使用多种边界标识,确保协议一致性。 |
使用成熟协议 | 优先采用现成协议(如HTTP/2、MQTT),其内部已处理粘包问题。 | 避免重复造轮子,减少开发成本。 |
合理设计包大小 | 控制单个数据包不超过MTU(通常1500字节),减少拆包概率。 | 需结合业务场景平衡数据完整性与传输效率。 |
缓冲区管理 | 接收方维护缓冲区,按规则拼接和拆分数据包。 | 处理半包时需等待后续数据,避免逻辑错误。 |
超时与异常处理 | 设置读取超时,丢弃不完整数据包并重置连接。 | 防止恶意攻击或网络异常导致资源耗尽。 |
测试与验证 | 模拟网络延迟、拆包场景,验证协议健壮性。 | 使用工具(如Wireshark)抓包分析。 |