news 2026/4/15 17:57:15

应用——Linux Socket编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
应用——Linux Socket编程

Linux Socket编程

TCP服务器编程模型

基本流程

// 1. 创建监听socket int listfd = socket(AF_INET, SOCK_STREAM, 0); // 2. 绑定地址和端口 struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); // 3. 开始监听 listen(listfd, 3); // 4. 接受连接 int conn = accept(listfd, (SA)&cli, &len); // 5. 通信 recv() / send() // 6. 关闭 close();

关键结构体

struct sockaddr_in { sa_family_t sin_family; // 地址族: AF_INET in_port_t sin_port; // 端口号(网络字节序) struct in_addr sin_addr; // IP地址 }; // 字节序转换函数 htons() // 主机字节序转网络字节序(short) htonl() // 主机字节序转网络字节序(long) ntohs() // 网络字节序转主机字节序(short) ntohl() // 网络字节序转主机字节序(long)

select模型详解

核心代码分析

tcp_select_ser.c
#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> typedef struct sockaddr*(SA); int main(int argc, char** argv) { // 1. 创建监听socket int listfd = socket(AF_INET, SOCK_STREAM, 0); // 2. 绑定地址 struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); // 3. 监听 listen(listfd, 3); // 4. select初始化 fd_set rd_set, tmp_set; FD_ZERO(&rd_set); FD_ZERO(&tmp_set); FD_SET(listfd, &tmp_set); int maxfd = listfd; while (1) { rd_set = tmp_set; // 5. 等待事件 select(maxfd + 1, &rd_set, NULL, NULL, NULL); // 6. 处理事件 for(int i = listfd; i < maxfd + 1; i++) { if(FD_ISSET(i, &rd_set) && i == listfd) // 监听socket有新连接 { int conn = accept(listfd, (SA)&cli, &len); FD_SET(conn, &tmp_set); if(conn > maxfd) maxfd = conn; } else if(FD_ISSET(i, &rd_set)) // 已连接socket有数据 { // 处理数据收发 char buf[512] = {0}; int ret = recv(i, buf, sizeof(buf), 0); if(ret <= 0) // 客户端断开 { FD_CLR(i, &tmp_set); close(i); } else { // 处理数据并回复 send(i, buf, strlen(buf), 0); } } } } close(listfd); return 0; }

select模型特点

特点说明
优点1. 跨平台性好
2. 实现相对简单
3. 支持多个文件描述符
缺点1. 每次调用需要复制fd_set
2. 需要遍历所有fd
3. 默认最大支持1024个连接
4. 内核每次都要遍历所有fd
适用场景连接数较少(<1000)的场景

select相关函数

// 清除集合中所有fd void FD_ZERO(fd_set *set); // 将fd加入集合 void FD_SET(int fd, fd_set *set); // 将fd从集合移除 void FD_CLR(int fd, fd_set *set); // 检查fd是否在集合中 int FD_ISSET(int fd, fd_set *set); // 等待事件发生 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 模型工作流程

epoll模型详解

核心代码分析

tcp_epollser.c
#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include <sys/epoll.h> typedef struct sockaddr*(SA); // 添加fd到epoll int add_fd(int epfd, int fd) { struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; return epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); } // 从epoll移除fd int del_fd(int epfd, int fd) { struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; return epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev); } int main(int argc, char** argv) { // 1. 创建监听socket int listfd = socket(AF_INET, SOCK_STREAM, 0); // 2. 绑定和监听 struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); // 3. 创建epoll实例 int epfd = epoll_create(100); // 4. 添加监听socket到epoll add_fd(epfd, listfd); struct epoll_event rev[100] = {0}; while (1) { // 5. 等待事件 int ep_ret = epoll_wait(epfd, rev, 100, -1); // 6. 处理事件 for(int i = 0; i < ep_ret; i++) { if(rev[i].data.fd == listfd) // 新连接 { int conn = accept(listfd, (SA)&cli, &len); add_fd(epfd, conn); } else // 数据可读 { int conn = rev[i].data.fd; char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if(ret <= 0) // 客户端断开 { del_fd(epfd, conn); close(conn); } else { // 处理并回复数据 send(conn, buf, strlen(buf), 0); } } } } close(listfd); return 0; }

epoll模型特点

特点说明
优点1. 事件驱动,无需遍历所有fd
2. 支持边缘触发(ET)和水平触发(LT)
3. 内存拷贝少,性能高
4. 支持大量连接(万级别)
缺点1. Linux特有,跨平台性差
2. 编程复杂度稍高
适用场景高并发、大量连接场景

epoll相关函数

// 创建epoll实例 int epoll_create(int size); // size已废弃,但需>0 // 控制epoll事件 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 等待事件 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); // epoll_event结构 struct epoll_event { uint32_t events; // EPOLLIN, EPOLLOUT等 epoll_data_t data; // 用户数据 }; union epoll_data { void *ptr; int fd; // 常用fd uint32_t u32; uint64_t u64; };

epoll触发模式

// 水平触发(默认) - 只要缓冲区有数据就会触发 ev.events = EPOLLIN; // 边缘触发 - 只在状态变化时触发一次 ev.events = EPOLLIN | EPOLLET; // 同时监听读和写事件 ev.events = EPOLLIN | EPOLLOUT;

epoll 模型工作流程

fork多进程模型

核心代码分析

tcp_forkser.c
#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> typedef struct sockaddr*(SA); // 子进程退出处理函数 void myhandle(int num) { wait(NULL); // 回收子进程 } int main(int argc, char** argv) { // 注册SIGCHLD信号处理,避免僵尸进程 signal(SIGCHLD, myhandle); // 创建监听socket int listfd = socket(AF_INET, SOCK_STREAM, 0); // 绑定和监听 struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); while (1) { // 接受连接 int conn = accept(listfd, (SA)&cli, &len); // 创建子进程处理连接 pid_t pid = fork(); if (pid > 0) // 父进程 { close(conn); // 父进程关闭连接socket // 由信号处理函数回收子进程 } else if (0 == pid) // 子进程 { close(listfd); // 子进程关闭监听socket // 处理客户端通信 while (1) { char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) break; // 处理并回复数据 send(conn, buf, strlen(buf), 0); } close(conn); exit(0); // 子进程退出 } else { perror("fork"); continue; } } close(listfd); return 0; }

fork模型特点

特点说明
优点1. 编程简单直观
2. 进程间隔离性好
3. 稳定性高(一个进程崩溃不影响其他)
缺点1. 资源消耗大
2. 进程间通信复杂
3. 创建销毁开销大
适用场景连接数较少,需要高稳定性的场景

关键点说明

// 1. 父子进程文件描述符继承 // fork()后,子进程继承父进程所有打开的文件描述符 // 需要及时关闭不需要的fd,避免资源泄露 // 2. 僵尸进程处理 // 子进程退出后,父进程需要wait()回收 // 使用signal(SIGCHLD, SIG_IGN)可让内核自动回收 // 3. 父子进程地址空间 // fork()后,子进程获得父进程地址空间的拷贝 // 父子进程内存空间独立,修改互不影响

fork 模型工作流程

pthread多线程模型

核心代码分析

tcp_threadser.c
#include <netinet/in.h> #include <netinet/ip.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include <semaphore.h> typedef struct sockaddr*(SA); sem_t sem_cli; // 信号量,用于线程同步 // 线程处理函数 void* th(void* args) { int conn = *(int*)args; sem_post(&sem_cli); // 通知主线程参数已复制 // 设置线程为分离状态,线程退出后自动回收资源 pthread_detach(pthread_self()); // 处理客户端通信 while (1) { char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) break; // 处理并回复数据 send(conn, buf, strlen(buf), 0); } close(conn); return NULL; } int main(int argc, char** argv) { // 创建监听socket int listfd = socket(AF_INET, SOCK_STREAM, 0); // 绑定和监听 struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); // 初始化信号量 sem_init(&sem_cli, 0, 0); while (1) { // 接受连接 int conn = accept(listfd, (SA)&cli, &len); // 创建线程处理连接 pthread_t tid; pthread_create(&tid, NULL, th, &conn); // 等待线程复制conn参数 sem_wait(&sem_cli); } close(listfd); sem_destroy(&sem_cli); return 0; }

线程模型特点

特点说明
优点1. 创建销毁开销小
2. 共享内存,通信方便
3. 资源消耗相对较小
缺点1. 编程复杂度高
2. 线程安全问题
3. 一个线程崩溃可能影响整个进程
适用场景需要共享数据、频繁创建销毁的场景

线程同步机制

// 1. 信号量(semaphore) sem_t sem; sem_init(&sem, 0, 0); // 初始化,初始值0 sem_wait(&sem); // P操作,等待信号量 sem_post(&sem); // V操作,释放信号量 sem_destroy(&sem); // 销毁 // 2. 互斥锁(mutex) pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); // 3. 条件变量(condition variable) pthread_cond_t cond; pthread_cond_init(&cond, NULL); pthread_cond_wait(&cond, &mutex); pthread_cond_signal(&cond); pthread_cond_destroy(&cond);

线程属性

// 设置线程为分离状态 pthread_detach(thread_id); // 或创建时指定分离属性 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&tid, &attr, th_func, arg);

TCP客户端

统一客户端代码

所有模型的客户端代码基本相同:

cli.c
#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 创建socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 2. 连接服务器 struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP int ret = connect(sockfd, (SA)&ser, sizeof(ser)); // 3. 通信 int i = 5; while (i) { char buf[512] = "this is tcp test"; send(sockfd, buf, strlen(buf), 0); bzero(buf, sizeof(buf)); recv(sockfd, buf, sizeof(buf), 0); printf("ser:%s\n", buf); sleep(1); i--; } // 4. 关闭 close(sockfd); return 0; }

客户端关键点

// 1. 连接服务器 // connect()会阻塞直到连接建立或失败 // 2. 数据收发 // send()和recv()默认是阻塞的 // 可以设置为非阻塞模式 // 3. 错误处理 // 连接失败、发送失败、接收失败都需要处理 // 4. 资源释放 // 确保socket被正确关闭

总结与对比

五种模型对比

模型并发处理方式资源消耗编程复杂度适用场景性能
select轮询所有fd中等简单连接数<1000
epoll事件通知中等高并发场景
fork多进程简单稳定优先中等
pthread多线程中等复杂需要共享数据
原始模型单线程阻塞简单学习/测试很低

模型对比图

选择建议

  1. 学习入门:先从简单的fork模型开始

  2. 小规模应用:select或fork模型

  3. 高并发服务:epoll模型

  4. 计算密集型:多线程模型

  5. 稳定要求高:多进程模型

通用优化建议

// 1. 设置socket选项 int reuse = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); // 2. 设置非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // 3. 设置超时 struct timeval tv; tv.tv_sec = 5; // 5秒超时 tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 4. 优雅关闭 shutdown(sockfd, SHUT_RDWR); // 先关闭读写 close(sockfd); // 再关闭socket

常见问题处理

// 1. 地址已在使用 // 设置SO_REUSEADDR选项 // 2. 大量TIME_WAIT // 调整内核参数或设置SO_REUSEADDR // 3. 连接数限制 // 调整系统文件描述符限制 // ulimit -n 65535 // 4. 内存泄漏 // 确保所有socket都被正确关闭 // 使用valgrind检查内存泄漏

编译命令汇总

# 通用编译命令 gcc -o server server.c -lpthread # 需要线程库 gcc -o client client.c # 带调试信息 gcc -g -o server server.c -lpthread # 优化编译 gcc -O2 -o server server.c -lpthread # 指定标准 gcc -std=c99 -o server server.c -lpthread
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 3:58:50

diskinfo命令行工具使用:分析GPU服务器磁盘I/O瓶颈

diskinfo命令行工具使用&#xff1a;分析GPU服务器磁盘I/O瓶颈 在现代AI训练集群中&#xff0c;一块价值数万元的GPU卡可能正因几块老旧SSD而“饥饿”停摆。这种现象并不罕见——当ResNet-50模型每轮训练耗时从25分钟飙升至45分钟&#xff0c;nvidia-smi显示GPU利用率长期徘徊在…

作者头像 李华
网站建设 2026/4/15 6:19:19

HuggingFace Dataset流式加载:处理超大规模token数据集

HuggingFace Dataset流式加载&#xff1a;处理超大规模token数据集 在训练百亿参数语言模型时&#xff0c;你是否曾因加载一个TB级语料库而遭遇内存崩溃&#xff1f;或者花费数小时等待数据预处理完成&#xff0c;结果GPU却闲置了大半时间&#xff1f;这并非个例。随着LLM进入“…

作者头像 李华
网站建设 2026/4/13 18:22:11

YOLOv5s模型转ONNX格式:借助PyTorch-CUDA完成导出

YOLOv5s模型转ONNX格式&#xff1a;借助PyTorch-CUDA完成导出 在现代AI部署流程中&#xff0c;一个训练好的深度学习模型往往不能直接“上线”。尤其是在目标检测这类对实时性要求极高的场景下&#xff0c;从实验室的 .pt 文件到边缘设备上的高效推理引擎之间&#xff0c;横亘…

作者头像 李华
网站建设 2026/4/11 2:37:12

CNN图像分类实战:基于PyTorch-CUDA-v2.8的端到端训练

CNN图像分类实战&#xff1a;基于PyTorch-CUDA-v2.8的端到端训练 你有没有经历过这样的场景&#xff1f;明明买了一块RTX 3090显卡&#xff0c;满怀期待地跑起CNN模型&#xff0c;结果发现训练速度还没隔壁用笔记本的同学快——一查才发现&#xff0c;模型压根没上GPU&#xff…

作者头像 李华
网站建设 2026/4/10 12:28:59

Git下载大型模型权重文件失败?教你用git-lfs和镜像加速解决

Git下载大型模型权重文件失败&#xff1f;教你用git-lfs和镜像加速解决 在尝试克隆一个Hugging Face上的LLaMA-2适配模型仓库时&#xff0c;你是否曾经历过这样的场景&#xff1a;git clone 命令执行到一半卡住、内存爆满、最终报错“fatal: the remote end hung up unexpected…

作者头像 李华
网站建设 2026/4/13 11:57:30

Markdown表格对比不同PyTorch版本特性

PyTorch-CUDA-v2.8 镜像深度解析&#xff1a;从环境配置到高效开发的实践指南 在深度学习项目中&#xff0c;最让人头疼的往往不是模型设计本身&#xff0c;而是“为什么代码在我机器上跑不起来&#xff1f;”——这个经典问题背后&#xff0c;通常是 Python 版本、PyTorch 构…

作者头像 李华