前言
在 Linux C 开发中,网络编程是嵌入式、后台开发非常核心的一块知识点。本文基于 Linux 套接字 socket 编程,从 UDP 通信讲起,梳理 UDP、TCP 全套 API、通信模型、三次握手四次挥手,最后讲解 HTTP 协议报文格式,附带核心代码思路,适合零基础入门网络编程,全部知识点来自实际开发学习笔记。
阅读范围:UDP 通信 → TCP 通信 → HTTP 协议,适合 C 语言有基础,了解 Linux 系统 IO 的同学。
一、UDP 通信(用户数据报协议)
1. UDP 协议特点
- 无连接:通信双方不需要提前建立连接,发完数据包就完事。
- 不可靠、不安全:没有确认重传机制,数据包可能丢失、乱序。
- 资源开销小:头部简单,处理逻辑少,实时性高。
- 面向数据报:以数据包为最小单位收发,不能拆分。
适用场景:直播、音视频、DNS 查询等可以容忍少量丢包,追求速度的业务。
2. UDP 核心 API 函数
UDP 通信流程:
发送端:
socket()→sendto()/recvfrom()接收端:socket()→bind()→recvfrom()/sendto()
① socket 创建套接字
int socket(int domain, int type, int protocol);
domain:协议族,IPv4 填AF_INETtype:套接字类型,UDP 使用SOCK_DGRAM(数据报套接字)protocol:协议,填 0 自动匹配对应协议- 返回值:成功返回套接字文件描述符;失败返回
-1
② sendto 发送 UDP 数据包
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:socket 返回的文件描述符buf:待发送数据缓冲区首地址len:发送数据字节长度flags:标志位,一般写 0dest_addr:对端的 IP + 端口结构体addrlen:地址结构体大小,sizeof(struct sockaddr_in)- 返回:成功返回实际发送字节数;失败
-1
③ recvfrom 接收 UDP 数据包
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
src_addr:输出参数,保存发送方的 IP 和端口信息addrlen:传入地址结构体最大长度,函数返回实际读到的地址长度- 返回:读到的字节数;0 代表对端关闭;失败
-1
④ bind 绑定端口(接收端必须)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
将本机 IP 地址、端口号绑定到 socket 文件描述符上。
注意:只能绑定本机网卡 IP;端口被占用会绑定失败。
⑤ IPv4 地址结构体 sockaddr_in
struct sockaddr_in {
sa_family_t sin_family; //协议族 AF_INET
in_port_t sin_port; //端口号,**必须网络大端字节序**
struct in_addr sin_addr; //ip地址(网络字节序)
};
struct in_addr {
uint32_t s_addr;
};
字节序转换函数
网络统一使用大端字节序,主机字节序(小端)必须转换!
htons():host to network short,把本机 short (16 位端口) 转为网络大端inet_addr():字符串 IP ("192.168.1.10") → 网络字节序整数 IPinet_ntoa():网络字节序 IP → 可读字符串 IP
3.UDP 简易通信思路
- 服务端(接收方):socket 创建 udp 套接字 → 填充
sockaddr_in本机端口 IP → bind 绑定 → 循环recvfrom接收数据,拿到客户端地址,可用 sendto 回复消息。 - 客户端(发送方):socket 创建 udp 套接字,不需要 bind;直接填充服务端 IP 端口,调用 sendto 发送数据。
UDP 没有真正服务端客户端严格区分,bind 只是固定本地端口。
二、TCP 通信(传输控制协议)
1.TCP 协议特点
- 面向连接 :通信前必须通过三次握手 建立连接;结束通信执行四次挥手断开连接。
- 可靠传输:确认应答、重传机制、滑动窗口、拥塞控制,保证数据不丢、不乱序。
- 资源开销大:协议头复杂,需要维护连接状态。
- 字节流 :没有数据包边界,会出现粘包问题。
适用场景:文件传输、HTTP、数据库等要求数据完整正确的业务。
2.TCP 核心 API
TCP 服务端流程: socket() → bind() → listen() → accept() → send()/recv()
TCP 客户端流程: socket() → connect() → send()/recv()
① connect 客户端建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
向服务端发起三次握手连接请求;addr 填写服务端 IP 端口。成功返回 0。
② listen 设置监听
int listen(int sockfd, int backlog);
将 socket 设置为监听状态,等待客户端连接;backlog代表未处理连接队列最大数目。
③ accept 阻塞等待客户端接入
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
阻塞函数,有客户端连接到达就返回新的套接字描述符,专门用于和该客户端收发数据。
⚠注意:listen 的 fd 叫监听套接字;accept 返回 fd 叫通信套接字,二者不能混用。addr 输出客户端 IP 端口信息。
④ send /recv 收发 TCP 字节流
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
flags 一般传 0;recv 返回0代表对端已经关闭连接。
3.TCP 关键机制
(1)三次握手建立连接
- 客户端发送
SYN报文(请求建立连接) - 服务端回复
SYN+ACK(同意连接 + 确认收到客户端 SYN) - 客户端回复
ACK,连接正式建立,开始传输业务数据。
目的:确认双方发送、接收能力都正常;协商初始序列号。
(2)四次挥手断开连接
TCP 是全双工,两边可以独立关闭读写通道:
- 主动关闭方发送
FIN,告诉对方我不再发送数据 - 被动方回复
ACK确认 FIN - 被动方业务处理完成,发送自己的
FIN - 主动方回复
ACK,连接彻底释放。
特殊情况:被动方收到 FIN 之后没有剩余数据要发送,可以合并第二步第三步,变成三次挥手。
(3)粘包问题
TCP 是字节流协议,没有数据包边界,多次发送的数据内核缓冲区会合并,接收方一次读到多组业务数据,就是粘包。
解决粘包常见方案:
- 定长包:每次收发固定长度数据;
- 增加分隔符 :比如
\r\n作为数据结束标记; - 头部携带数据长度:包头存放后续数据字节数,先读头部,再按长度读取数据。
❌简单延时 sleep 不能作为工程解决方案。
(4)TCP 头部标志位
表格
| 标志 | 含义 |
|---|---|
| SYN | 请求建立连接 |
| FIN | 请求关闭连接 |
| ACK | 确认应答(绝大多数报文都置 1) |
| RST | 重置异常连接 |
| PSH | 推送数据,尽快交给应用层 |
| URG | 紧急数据有效 |
同时 TCP 还有序号、确认序号、滑动窗口、拥塞控制、掉线检测保证传输可靠性。
4.UDP 与 TCP 核心对比
表格
| 对比项 | UDP | TCP |
|---|---|---|
| 连接属性 | 无连接 | 面向连接 (三次握手 / 四次挥手) |
| 可靠性 | 不可靠,丢包不重传 | 可靠传输,确认 + 重传 |
| 传输模式 | 数据报 | 字节流,存在粘包 |
| 资源开销 | 小 | 大 |
| 适用场景 | 音视频直播、DNS | HTTP、文件传输 |
三、HTTP 协议(基于 TCP 的应用层协议)
HTTP 是跑在 TCP 之上的应用层协议,浏览器、网络接口请求都使用 HTTP。
1. 模型:C/S 和 B/S
- C/S(客户端服务器):独立客户端程序 + 服务端;本地资源,速度快,但是多平台需要单独开发。
- B/S(浏览器服务器):浏览器充当客户端;资源全部放在服务端,无需安装客户端,跨平台通用,每次请求都从服务器拉取资源。
HTTP 属于 B/S 模型。
2.URL 统一资源定位符
格式:
<协议>://<主机IP/域名>:<端口>/资源路径
- http 默认端口
80;https 加密默认端口443。 - DNS 域名解析会把域名转换为 IP 地址。
3.HTTP 通信完整流程
- 浏览器解析 URL 得到域名、端口、路径;DNS 解析域名得到服务器 IP。
- 浏览器客户端和服务器建立 TCP 连接(三次握手)。
- 浏览器发送HTTP 请求报文。
- 服务器处理请求,返回HTTP 响应报文。
- TCP 四次挥手断开连接。
4.HTTP 请求报文格式
整体分为:请求行、首部行、空行 CRLF、实体主体(POST 才带数据)
- 请求行 :
方法 URL HTTP版本- GET:获取服务器资源;
- POST:向服务器提交数据;PUT、DELETE 等。
- 首部行 :键值对,
字段名: 值\r\n,可以携带浏览器信息、内容长度、编码。 \r\n:单独一行空行,代表头部结束。- 实体主体:POST 提交的表单、JSON 数据。
示例极简 GET 请求报文:
GET /index.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
5.HTTP 响应报文格式
分为:状态行、响应首部、空行 CRLF、响应实体主体
- 状态行 :
版本 状态码 描述1xx:提示信息;2xx:请求成功(最常见 200 OK);3xx:重定向跳转;4xx:客户端错误,404 资源找不到;5xx:服务器内部错误。
- 响应首部:服务器返回的元信息,如
Content-Length、Content-Type。 - 空行分割头部和 body。
- 实体主体:返回 HTML、JSON、图片二进制数据。
拓展实战小练习
使用 C 语言 socket 写 TCP 客户端,访问天气 API,拿到 HTTP 响应,再使用
cJSON库解析返回 JSON,提取天气数据打印终端。 关键点:发送 http GET 请求字符串;从 socket 读取完整响应报文;剥离 HTTP 头部,只解析后面 JSON 内容。
四、总结
- UDP 无连接,简单快速,适合实时业务;TCP 面向连接可靠传输,适合业务数据交互。
- UDP 使用
sendto/recvfrom;TCP 使用connect/listen/accept/send/recv;TCP 必须理解三次握手四次挥手和粘包。 - HTTP 构建在 TCP 字节流之上,要掌握请求报文、响应报文结构、状态码含义。
- 网络编程务必注意主机字节序、网络字节序转换问题;IPv4 结构体
sockaddr_in是所有套接字编程基础。
后续可以自己手写最小版 UDP 聊天程序、TCP 回声服务器,加深对 API 理解。