一、网络基础概念
1. 核心定义
| 概念 | 本质说明 | 关键特性 |
|---|---|---|
| 网络 | 将独立自主的计算机通过传输介质、网络设备连接起来,实现数据交互的系统 | 核心组件:计算机(主机)、传输介质、网络设备、网络协议 |
| 互联网 | 由多个独立网络相互连接形成的全球性网络(因特网是互联网的典型代表) | 基于 TCP/IP 协议簇,实现跨网络、跨地域的主机通信 |
| IP 地址 | 网络中主机的唯一标识(类似 "学号"),用于定位主机 | 分类:IPv4(32 位,如192.168.2.27)、IPv6(64 位,如248e:35:1816:558:91f5:1772:2c4:8fb7) |
| MAC 地址 | 网络设备(网卡)的物理地址(48 位,类似 "姓名"),用于链路层识别设备 | 全球唯一,固化在网卡硬件中,适用于局域网内设备通信 |
| 端口号 | 主机上应用程序的唯一标识(short类型,范围 0-65535),用于区分同一主机的不同进程 |
核心作用:IP+端口确定网络中唯一进程(套接字地址);常用端口:HTTP 80、HTTPS 443 |
| 传输介质 | 数据传输的物理载体 | 分类:双绞线、同轴电缆、光纤(有线);无线(电磁波,如 Wi-Fi) |
| 网络设备 | 实现网络连接、数据转发的硬件设备 | 核心设备:交换机(局域网内数据转发)、路由器(跨网络数据转发) |
| 网络协议 | 网络通信的规则集合(软件层面),确保数据有序、正确传输 | 核心协议:TCP(可靠传输)、UDP(快速传输)、IP(地址路由)、HTTP/HTTPS(应用层交互) |
2. 网络分层模型
(1)OSI 七层模型(从下到上)
| 层级 | 核心功能 | 对应组件 / 协议 |
|---|---|---|
| 物理层 | 传输原始比特流,定义物理接口、传输介质特性 | 双绞线、光纤、网卡物理接口 |
| 数据链路层 | 封装帧,处理链路错误、MAC 地址寻址 | 交换机、MAC 地址、ARP 协议 |
| 网络层 | 实现路由转发,确定跨网络的传输路径 | 路由器、IP 地址、ICMP 协议(ping 基于此) |
| 传输层 | 提供端到端传输服务,负责流量控制、差错恢复 | TCP 协议、UDP 协议、端口号 |
| 会话层 | 建立、维护、终止进程间通信会话 | 会话管理、同步机制 |
| 表示层 | 数据格式转换、加密解密、压缩解压 | 字符编码转换、SSL/TLS(HTTPS 底层) |
| 应用层 | 提供具体应用服务,面向用户需求 | HTTP/HTTPS、FTP、DNS 等协议 |
(2)TCP/IP 四层模型(实际应用主流)
| 层级 | 对应 OSI 层级 | 核心功能 |
|---|---|---|
| 网际接口层 | 物理层 + 数据链路层 | 处理硬件接口和局域网内数据传输 |
| 网络层 | 网络层 | IP 地址路由、跨网络转发 |
| 传输层 | 传输层 | TCP/UDP 协议、端到端传输 |
| 应用层 | 会话层 + 表示层 + 应用层 | HTTP/HTTPS、FTP 等应用协议 |
(3)分层的核心优势
- 独立性:各层独立实现功能,无需关注下层实现细节(如应用层无需关心物理层是有线还是无线);
- 灵活性:某层技术更新(如物理层从双绞线升级为光纤),不影响其他层级;
- 易实现维护:复杂问题分解为独立子问题,降低开发和调试难度;
- 标准化:明确各层功能边界,促进不同厂商设备、软件的兼容性。
3. 常用网络指令
| 指令 | 功能说明 | 示例 |
|---|---|---|
ifconfig(Linux)/ipconfig(Windows) |
查看网络接口信息(IP 地址、MAC 地址、子网掩码等) | ifconfig(Linux 输出网卡 IP、MAC 等详情) |
ping + IP地址 |
测试主机是否可达(基于 ICMP 协议) | ping 127.0.0.1(测试本机网络正常性)、ping 192.168.2.27(测试局域网主机) |
netstat -natp |
查看网络连接状态、端口占用、TCP/UDP 协议状态 | netstat -natp(查看所有 TCP/UDP 连接,包括监听、建立连接状态) |
二、同步模型(生产者 - 消费者、读者 - 写者)
1. 生产者 - 消费者模型
(1)核心概念
- 生产者:生成数据并放入共享 "缓冲区" 的进程 / 线程(如生成日志数据的程序);
- 消费者:从缓冲区取出数据并处理的进程 / 线程(如分析日志的程序);
- 缓冲区:中间媒介(队列 / 数组结构),平衡生产者生产速度和消费者消费速度;
- 核心约束:同一时刻,生产者和消费者不可同时访问缓冲区(互斥访问)。
(2)核心优点
- 解耦:生产者与消费者无需直接通信,仅通过缓冲区交互,降低模块依赖;
- 平衡速度:生产者过快时缓冲区暂存数据,消费者过快时缓冲区缓存不足则等待;
- 支持并发:多个生产者可同时生产,多个消费者可同时消费,提高系统吞吐量。
(3)实现示例(基于线程 + 信号量 + 队列)
cpp
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUF_SIZE 5 // 缓冲区大小
int buffer[BUF_SIZE]; // 共享缓冲区(队列)
int in = 0; // 生产者写入位置
int out = 0; // 消费者读取位置
sem_t empty; // 信号量:缓冲区空位数(初始为BUF_SIZE)
sem_t full; // 信号量:缓冲区数据量(初始为0)
pthread_mutex_t mutex; // 互斥锁:保护缓冲区访问
// 生产者线程函数
void* producer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 10; i++) {
int data = rand() % 100; // 生成随机数据
sem_wait(&empty); // P操作:获取空缓冲区(无空位则阻塞)
pthread_mutex_lock(&mutex); // 加锁:保护缓冲区写入
// 写入缓冲区
buffer[in] = data;
printf("生产者%d:写入数据%d,位置%d\n", id, data, in);
in = (in + 1) % BUF_SIZE;
pthread_mutex_unlock(&mutex); // 解锁
sem_post(&full); // V操作:增加数据量(唤醒消费者)
sleep(1); // 模拟生产耗时
}
pthread_exit(NULL);
}
// 消费者线程函数
void* consumer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 10; i++) {
sem_wait(&full); // P操作:获取数据(无数据则阻塞)
pthread_mutex_lock(&mutex); // 加锁:保护缓冲区读取
// 读取缓冲区
int data = buffer[out];
printf("消费者%d:读取数据%d,位置%d\n", id, data, out);
out = (out + 1) % BUF_SIZE;
pthread_mutex_unlock(&mutex); // 解锁
sem_post(&empty); // V操作:增加空位数(唤醒生产者)
sleep(2); // 模拟消费耗时
}
pthread_exit(NULL);
}
int main() {
// 初始化信号量和互斥锁
sem_init(&empty, 0, BUF_SIZE);
sem_init(&full, 0, 0);
pthread_mutex_init(&mutex, NULL);
pthread_t p1, p2, c1, c2;
int p_id1 = 1, p_id2 = 2, c_id1 = 1, c_id2 = 2;
// 创建2个生产者、2个消费者
pthread_create(&p1, NULL, producer, &p_id1);
pthread_create(&p2, NULL, producer, &p_id2);
pthread_create(&c1, NULL, consumer, &c_id1);
pthread_create(&c2, NULL, consumer, &c_id2);
// 等待所有线程执行完毕
pthread_join(p1, NULL);
pthread_join(p2, NULL);
pthread_join(c1, NULL);
pthread_join(c2, NULL);
// 销毁资源
sem_destroy(&empty);
sem_destroy(&full);
pthread_mutex_destroy(&mutex);
return 0;
}
编译运行:
bash
gcc producer_consumer.c -o pc -lpthread
./pc
2. 读者 - 写者模型
(1)核心概念
- 读者:仅读取共享资源,不修改数据(如查询数据库的进程);
- 写者:修改共享资源(如更新数据库的进程);
- 共享资源:被多进程 / 线程共同访问的数据(如配置文件、数据库表);
- 核心约束 (读写锁特性):
- 读 - 读可并发:多个读者可同时读取,不阻塞;
- 读 - 写互斥:读者读取时写者阻塞,写者写入时读者阻塞;
- 写 - 写互斥:多个写者不可同时写入,避免数据覆盖。
(2)应用场景
读操作远多于写操作的场景(如新闻网站文章浏览、配置文件读取),比互斥锁效率更高。
(3)实现示例(基于读写锁)
cpp
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_data = 100; // 共享资源
pthread_rwlock_t rwlock; // 读写锁
// 读者线程函数
void* reader(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 5; i++) {
pthread_rwlock_rdlock(&rwlock); // 加读锁
printf("读者%d:读取数据=%d\n", id, g_data);
sleep(1); // 模拟读耗时
pthread_rwlock_unlock(&rwlock); // 解读锁
sleep(1); // 读者之间间隔
}
pthread_exit(NULL);
}
// 写者线程函数
void* writer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 3; i++) {
pthread_rwlock_wrlock(&rwlock); // 加写锁
g_data += 50;
printf("【写者%d】:修改数据=%d\n", id, g_data);
sleep(2); // 模拟写耗时
pthread_rwlock_unlock(&rwlock); // 解写锁
sleep(2); // 写者之间间隔
}
pthread_exit(NULL);
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
pthread_t r1, r2, r3, w1;
int r_id1 = 1, r_id2 = 2, r_id3 = 3, w_id1 = 1;
// 创建3个读者、1个写者
pthread_create(&r1, NULL, reader, &r_id1);
pthread_create(&r2, NULL, reader, &r_id2);
pthread_create(&r3, NULL, reader, &r_id3);
pthread_create(&w1, NULL, writer, &w_id1);
// 等待所有线程结束
pthread_join(r1, NULL);
pthread_join(r2, NULL);
pthread_join(r3, NULL);
pthread_join(w1, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
编译运行:
cpp
gcc reader_writer.c -o rw -lpthread
./rw
三、核心网络协议(TCP/UDP)(都是重点)
1. TCP 协议(传输控制协议)
(1)核心特性
- 面向连接:通信前需建立连接(三次握手),通信后需释放连接(四次挥手);
- 可靠传输:通过应答确认、超时重传、乱序重排、去重机制保证数据不丢失、不重复;
- 流式服务:数据无边界,以字节流形式传输(类似水流持续传输);
- 流量控制:通过滑动窗口机制避免发送方发送过快导致接收方缓冲区溢出;
- 一对一通信:一个 TCP 连接仅支持两个端点通信。
(2)TCP 三次握手(建立连接)
1. 核心原理
- 目的:确认双方发送和接收能力正常,同步序列号(确保数据有序传输);
- 参与方:客户端(主动发起连接)、服务器(被动监听连接);
- 关键标志位:
SYN(同步请求,值为 1 表示发起同步)、ACK(确认应答,值为 1 表示确认); - 序列号:
seq(发送方的序列号)、ack(应答序列号,= 对方seq+1,表示确认收到对方数据)。 
2. 握手流程(结合示例)
| 步骤 | 发起方 | 报文内容 | 状态变化 |
|---|---|---|---|
| 1 | 客户端 | SYN=1,seq=x(如 x=100) |
客户端:CLOSED → SYN-SENT(等待服务器响应) |
| 2 | 服务器 | SYN=1(发起服务器端同步)、ACK=1(确认客户端同步)、seq=y(如 y=200)、ack=x+1=101 |
服务器:LISTEN → SYN-RECV(等待客户端最终确认) |
| 3 | 客户端 | ACK=1(确认服务器同步)、seq=x+1=101、ack=y+1=201 |
客户端:SYN-SENT → ESTABLISHED(连接建立,可传输数据);服务器:SYN-RECV → ESTABLISHED |
3. 形象类比
- 客户端:"我要和你建立连接(SYN=1),我的序列号是 100(seq=100)";
- 服务器:"收到你的请求(ACK=1,ack=101),我也要和你同步(SYN=1),我的序列号是 200(seq=200)";
- 客户端:"收到你的同步(ACK=1,ack=201),现在可以传数据了(seq=101)"。
(3)TCP 四次挥手(释放连接)
1. 核心原理
- 目的:确保双方数据都已传输完毕,优雅释放连接;
- 参与方:客户端(主动关闭)、服务器(被动关闭);
- 关键标志位:
FIN(终止连接请求,值为 1 表示请求关闭)。 
2. 挥手流程(结合示例)
| 步骤 | 发起方 | 报文内容 | 状态变化 |
|---|---|---|---|
| 1 | 客户端 | FIN=1(请求关闭)、seq=u(如 u=300) |
客户端:ESTABLISHED → FIN-WAIT-1(等待服务器确认) |
| 2 | 服务器 | ACK=1(确认关闭请求)、seq=v(如 v=400)、ack=u+1=301 |
服务器:ESTABLISHED → CLOSE-WAIT(准备关闭自身连接,仍可发送剩余数据);客户端:FIN-WAIT-1 → FIN-WAIT-2(等待服务器关闭请求) |
| 3 | 服务器 | FIN=1(服务器请求关闭)、seq=w(如 w=450)、ack=u+1=301 |
服务器:CLOSE-WAIT → LAST-ACK(等待客户端最终确认) |
| 4 | 客户端 | ACK=1(确认服务器关闭)、seq=u+1=301、ack=w+1=451 |
客户端:FIN-WAIT-2 → TIME-WAIT(等待 2MSL 时间,避免迟来报文)→ CLOSED;服务器:LAST-ACK → CLOSED(连接释放) |
4. 关键状态说明
TIME-WAIT状态:客户端独有的状态,等待 2MSL(报文最大生存时间),确保迟来的报文被丢弃,避免端口复用导致数据错乱;CLOSE-WAIT状态:服务器独有的状态,此时服务器仍可向客户端发送未传输完的数据。
5. 形象类比
- 客户端:"我没有数据要发了(FIN=1),我的序列号是 300(seq=300)";
- 服务器:"收到你的关闭请求(ACK=1,ack=301),我还有点数据没发完(seq=400)";
- 服务器:"我数据发完了(FIN=1,seq=450),可以关闭了";
- 客户端:"收到(ACK=1,ack=451),我等一会儿再彻底关闭(TIME-WAIT)"。
(4)TCP 网络编程核心步骤
1. 核心头文件
cpp
#include <sys/socket.h> // 套接字操作
#include <netinet/in.h> // 网络地址结构体
#include <arpa/inet.h> // IP地址转换函数
2. 服务器端示例代码
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
// 1. 创建监听套接字(IPv4,TCP协议)
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket创建失败");
exit(1);
}
// 2. 绑定IP+端口(服务器端IP和端口固定)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPv4地址族
server_addr.sin_port = htons(6000); // 端口号(主机字节序转网络字节序)
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地回环IP
if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind绑定失败");
close(listen_fd);
exit(1);
}
// 3. 设置监听队列(最大等待连接数5)
if (listen(listen_fd, 5) == -1) {
perror("listen监听失败");
close(listen_fd);
exit(1);
}
printf("服务器启动,监听端口6000...\n");
while (1) {
// 4. 阻塞等待客户端连接,创建通信套接字
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (conn_fd == -1) {
perror("accept接收连接失败");
continue;
}
printf("客户端连接成功,conn_fd=%d\n", conn_fd);
// 5. 接收客户端数据
char buf[128] = {0};
ssize_t recv_len = recv(conn_fd, buf, sizeof(buf)-1, 0);
if (recv_len <= 0) {
printf("客户端断开连接\n");
close(conn_fd);
continue;
}
printf("收到客户端数据:%s\n", buf);
// 6. 向客户端发送响应
send(conn_fd, "收到你的消息!", strlen("收到你的消息!"), 0);
// 7. 关闭通信套接字(短连接,一次通信后关闭)
close(conn_fd);
}
// 8. 关闭监听套接字(实际不会执行,需信号处理)
close(listen_fd);
return 0;
}
3. 客户端示例代码
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
// 1. 创建通信套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket创建失败");
exit(1);
}
// 2. 连接服务器(指定服务器IP+端口)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect连接服务器失败");
close(sock_fd);
exit(1);
}
printf("连接服务器成功!\n");
// 3. 向服务器发送数据
char buf[128] = {0};
printf("请输入要发送的消息:");
fgets(buf, sizeof(buf), stdin);
send(sock_fd, buf, strlen(buf)-1, 0); // 去除fgets读取的换行符
// 4. 接收服务器响应
memset(buf, 0, sizeof(buf));
ssize_t recv_len = recv(sock_fd, buf, sizeof(buf)-1, 0);
if (recv_len > 0) {
printf("服务器响应:%s\n", buf);
}
// 5. 关闭套接字
close(sock_fd);
return 0;
}
4. 编译运行
cpp
# 编译服务器端
gcc tcp_server.c -o tcp_server
# 编译客户端
gcc tcp_client.c -o tcp_client
# 终端1运行服务器
./tcp_server
# 终端2运行客户端
./tcp_client
(5)tcp协议下的有限状态机

缓冲区问题:

这个图展示的是TCP 的 "字节流服务" 特性 ,核心就是TCP 的发送 / 接收缓冲区对数据的 "拼接 / 拆分" 处理。我结合图给你详细讲解 TCP 缓冲区的工作逻辑:
一、先明确核心概念:TCP 是 "字节流",无数据边界
TCP 不像 UDP 那样以 "数据报" 为单位传输,而是把数据当作连续的字节流 处理 ------ 发送端多次send()的数据会被拼接成一个 "流",接收端recv()时也会从 "流" 里读取数据,不保证和发送端的send()次数对应。
二、结合图拆解 TCP 缓冲区的工作流程
这个图分为发送端 和接收端,涉及 "应用层→传输层(TCP 缓冲区)→网络层" 的处理:
1. 发送端的处理(左侧)
- 应用层 :发送端调用了 3 次
send(),分别发送hello、abcdef、test12345; - TCP 发送缓冲区 :这 3 次
send()的数据不会直接发出去,而是先被拼接成连续的字节流 (helloabcdeftest12345),存入 "TCP 发送缓冲区"; - 传输层(TCP 报文段) :TCP 会根据网络状况(如 MTU 最大传输单元),将缓冲区里的字节流拆分成多个 TCP 报文段 (图中拆成了 2 段:
helloabc和deftest12345),再发送到网络。
2. 接收端的处理(右侧)
- 传输层(TCP 报文段):接收端收到发送端拆分的 2 个 TCP 报文段;
- TCP 接收缓冲区 :TCP 会把这 2 个报文段的内容重新拼接成完整的字节流 (
helloabcdeftest12345),存入 "TCP 接收缓冲区"; - 应用层 :接收端调用 1 次
recv(),就能从缓冲区里读取到完整的字节流(helloabcdeftest12345)。
三、TCP 缓冲区的关键特性(结合图总结)
-
发送缓冲区:拼接数据 发送端多次
send()的数据会被 TCP 自动 "拼接" 成连续字节流,不会保留send()的边界; -
接收缓冲区:重组数据 接收端收到的多个 TCP 报文段,会被 TCP 自动 "重组" 成原始字节流,
recv()读取的是连续的字节(可能一次读走多次send()的内容); -
与应用层的 "解耦" 应用层只需要调用
send()/recv()操作字节流,不需要关心 TCP 如何拆分 / 重组报文段(这部分由 TCP 协议栈自动处理); -
缓冲区的 "暂存" 作用
- 发送缓冲区:如果网络拥塞,发送端的数据会暂存在缓冲区,等网络空闲时再发送;
- 接收缓冲区:如果应用层
recv()不及时,接收的数据会暂存在缓冲区,避免数据丢失。
四、举个实际例子(对应图的场景)
发送端代码:
cpp
send(sock, "hello", 5, 0); // 第1次send
send(sock, "abcdef", 6, 0); // 第2次send
send(sock, "test12345", 9, 0); // 第3次send
接收端代码:
cpp
char buf[1024] = {0};
recv(sock, buf, sizeof(buf)-1, 0); // 1次recv就读到了"helloabcdeftest12345"
五、需要注意的问题
- 接收端
recv()的长度 :如果接收端recv()的缓冲区比发送的字节流小,会分多次读取(比如缓冲区只有 10 字节,第一次读helloabcde,第二次读ftest12345); - "粘包" 问题 :因为 TCP 是字节流,多次
send()的数据会被拼接,接收端无法区分 "原始send()的边界"------ 如果需要区分不同的消息,得自己在应用层加 "分隔符"(比如用\n分割)或 "消息长度"(比如先发送 4 字节的长度,再发送消息内容)。
总结来说:TCP 的发送 / 接收缓冲区是实现 "字节流服务" 的核心,它自动完成了数据的 "拼接、拆分、暂存",让应用层可以像操作 "流" 一样读写数据,不用关心底层的报文段拆分。
2. UDP 协议(用户数据报协议)
(1)核心特性
- 面向无连接:无需建立连接,直接发送数据,节省连接开销;
- 不可靠传输:无应答确认、超时重传机制,可能出现数据丢失、乱序;
- 数据报服务:数据有边界,每次发送一个完整报文,接收方需完整接收;
- 支持一对多通信:一个 UDP 端口可接收多个客户端的数据;
- 速度快:无连接和重传开销,适用于实时性要求高的场景。
(2)UDP 与 TCP 的核心区别
| 对比维度 | TCP 协议 | UDP 协议 |
|---|---|---|
| 连接方式 | 面向连接(三次握手 + 四次挥手) | 无连接 |
| 可靠性 | 可靠(应答、重传、去重、排序) | 不可靠(无应答、可能丢失 / 乱序) |
| 数据传输方式 | 流式服务(无边界,字节流) | 数据报服务(有边界,完整报文) |
| 通信模式 | 一对一 | 一对一、一对多、多对多 |
| 适用场景 | 文件传输、HTTP/HTTPS 通信、邮件发送等对可靠性要求高的场景 | 实时音视频、游戏数据、DNS 查询等对实时性要求高的场景 |
| 开销 | 高(连接建立、流量控制、重传机制) | 低(无额外开销) |
(3)、UDP 编程完整示例(服务器 + 客户端)
1. UDP 服务器端代码(接收客户端数据并回复)
服务器端逻辑:绑定端口 → 循环接收客户端数据 → 解析发送方地址 → 回复数据。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 6000 // 服务器监听端口
#define BUF_SIZE 1024 // 数据缓冲区大小
int main() {
// 1. 创建UDP套接字(AF_INET=IPv4,SOCK_DGRAM=UDP数据报)
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd == -1) {
perror("socket 创建失败");
exit(EXIT_FAILURE);
}
printf("UDP套接字创建成功\n");
// 2. 绑定IP和端口(服务器必须绑定,否则客户端无法定位)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 初始化地址结构体
server_addr.sin_family = AF_INET; // IPv4地址族
server_addr.sin_port = htons(PORT); // 端口(主机序转网络序)
// INADDR_ANY = 0.0.0.0,监听所有网卡的6000端口
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(udp_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind 绑定失败");
close(udp_fd);
exit(EXIT_FAILURE);
}
printf("UDP服务器启动,绑定端口 %d,等待客户端数据...\n", PORT);
// 3. 循环接收客户端数据
char buf[BUF_SIZE] = {0};
struct sockaddr_in client_addr; // 存储客户端地址
socklen_t client_len = sizeof(client_addr); // 地址长度
while (1) {
// 清空缓冲区
memset(buf, 0, BUF_SIZE);
// 接收客户端数据(阻塞等待)
ssize_t recv_len = recvfrom(udp_fd, buf, BUF_SIZE-1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (recv_len == -1) {
perror("recvfrom 接收失败");
continue;
}
// 解析客户端信息
char client_ip[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
int client_port = ntohs(client_addr.sin_port); // 网络序转主机序
// 打印客户端数据
printf("收到客户端 [%s:%d] 数据:%s\n", client_ip, client_port, buf);
// 4. 回复客户端(使用同一个套接字,指定客户端地址)
char reply_buf[BUF_SIZE] = {0};
snprintf(reply_buf, BUF_SIZE, "服务器已收到:%s", buf);
sendto(udp_fd, reply_buf, strlen(reply_buf), 0,
(struct sockaddr*)&client_addr, client_len);
printf("已回复客户端 [%s:%d]\n", client_ip, client_port);
}
// 5. 关闭套接字(实际不会执行,需手动终止程序)
close(udp_fd);
return 0;
}
2. UDP 客户端代码(发送数据到服务器并接收回复)
客户端逻辑:创建套接字 → 指定服务器地址 → 发送数据 → 接收服务器回复。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1" // 服务器IP(本地回环)
#define SERVER_PORT 6000 // 服务器端口
#define BUF_SIZE 1024 // 数据缓冲区大小
int main() {
// 1. 创建UDP套接字
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd == -1) {
perror("socket 创建失败");
exit(EXIT_FAILURE);
}
printf("UDP客户端启动\n");
// 2. 配置服务器地址(指定要发送的目标)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
// 字符串IP转网络序整数
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("inet_pton IP转换失败");
close(udp_fd);
exit(EXIT_FAILURE);
}
// 3. 输入要发送的数据
char send_buf[BUF_SIZE] = {0};
printf("请输入要发送给服务器的内容(输入quit退出):");
fgets(send_buf, BUF_SIZE-1, stdin);
// 去除fgets读取的换行符
send_buf[strcspn(send_buf, "\n")] = '\0';
// 退出条件
if (strcmp(send_buf, "quit") == 0) {
close(udp_fd);
return 0;
}
// 4. 发送数据到服务器
ssize_t send_len = sendto(udp_fd, send_buf, strlen(send_buf), 0,
(struct sockaddr*)&server_addr, sizeof(server_addr));
if (send_len == -1) {
perror("sendto 发送失败");
close(udp_fd);
exit(EXIT_FAILURE);
}
printf("已发送 %zd 字节到服务器 [%s:%d]\n", send_len, SERVER_IP, SERVER_PORT);
// 5. 接收服务器回复
char recv_buf[BUF_SIZE] = {0};
socklen_t server_len = sizeof(server_addr);
ssize_t recv_len = recvfrom(udp_fd, recv_buf, BUF_SIZE-1, 0,
(struct sockaddr*)&server_addr, &server_len);
if (recv_len == -1) {
perror("recvfrom 接收回复失败");
close(udp_fd);
exit(EXIT_FAILURE);
}
printf("收到服务器回复:%s\n", recv_buf);
// 6. 关闭套接字
close(udp_fd);
return 0;
}
三、编译与运行步骤
1. 编译代码
cpp
# 编译服务器端
gcc udp_server.c -o udp_server
# 编译客户端
gcc udp_client.c -o udp_client
四、应用层协议(HTTP/HTTPS)(了解)
1. 核心概念
- 属于应用层协议,底层基于 TCP 协议(可靠传输);
- HTTP:明文传输,端口 80,安全性低;
- HTTPS:加密传输(SSL/TLS 协议),端口 443,安全性高;
- 核心功能:实现客户端(浏览器 / 应用)与服务器(Web 服务器)的交互(如请求网页、提交表单)。
2. 核心方法(HTTP)
| 方法 | 功能说明 |
|---|---|
| GET | 从服务器获取资源(如浏览网页),参数拼接在 URL 中,长度有限制 |
| POST | 向服务器提交资源(如登录、上传文件),参数放在请求体中,长度无限制 |
3. 实现示例(C 语言 HTTP 客户端)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
// 1. 创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket创建失败");
exit(1);
}
// 2. 连接HTTP服务器(以百度为例)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80); // HTTP默认端口80
server_addr.sin_addr.s_addr = inet_addr("180.101.50.242"); // 百度IP(示例)
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect连接失败");
close(sock_fd);
exit(1);
}
// 3. 发送HTTP GET请求
char request[] = "GET / HTTP/1.1\r\n"
"Host: www.baidu.com\r\n"
"Connection: close\r\n\r\n";
send(sock_fd, request, strlen(request), 0);
// 4. 接收服务器响应(HTML文档)
char buf[4096] = {0};
ssize_t recv_len;
while ((recv_len = recv(sock_fd, buf, sizeof(buf)-1, 0)) > 0) {
printf("%s", buf);
memset(buf, 0, sizeof(buf));
}
// 5. 关闭套接字
close(sock_fd);
return 0;
}
编译运行:
bash
gcc http_client.c -o http_client
./http_client
- 运行结果:接收百度服务器返回的 HTML 文档(浏览器解析后即为百度首页)。
五、关键补充说明
1. 套接字地址(IP + 端口)
- 本质:
IP地址+端口号的组合,唯一标识网络中的一个进程; - 存储方式:通过
struct sockaddr_in结构体存储(IPv4),包含地址族、端口号、IP 地址; - 核心转换函数:
htons():主机字节序转网络字节序(端口号转换);inet_addr():字符串格式 IP(如"127.0.0.1")转无符号整数格式。
2. 长连接与短连接
- 短连接:一次通信后立即关闭连接(如 TCP 服务器示例中的
close(conn_fd)); - 长连接:多次通信复用同一个连接(如 HTTP/1.1 默认长连接),减少连接建立开销;
- 适用场景:短连接适用于通信频率低的场景(如查询天气),长连接适用于通信频繁的场景(如聊天软件)。
3. 线程安全补充
- 线程安全:多线程程序无论调度顺序如何,都能得到正确结果(如使用互斥锁、读写锁保护共享资源);
- 网络编程中的线程安全:多线程处理客户端连接时,需为每个客户端分配独立的通信套接字和缓冲区,避免共享资源竞争。