Linux IO模型与并发服务器 学习笔记

阻塞IO

用户层:

cs 复制代码
char buf[1024];

recv(fd, buf, sizeof(buf), 0);// 程序卡在这里等待数据

内核

1、检查socket接收缓冲区是否有数据

2、若无数据:

•将进程状态设为TASK_INTERRUPTIBLE(可中断睡眠)

•进程从CPU运行队列移出,触发调度器切换其他进程运行

3、数据到达:

•网卡触发硬件中断→ 内核将数据拷贝到接收缓冲区

•唤醒进程(状态改为TASK_RUNNING),重新加入运行队列

非阻塞IO

函数原型:int fcntl(int fd, int cmd, ... /* arg*/ );

用户层:

cs 复制代码
fcntl(fd, F_SETFL, O_NONBLOCK); // 设置非阻塞标志
while (1) {
    int n = recv(fd, buf, sizeof(buf), 0);
    if (n >= 0) break;          // 数据就绪
    if (errno != EAGAIN) exit(1); // 错误处理
    usleep(1000);               // 避免CPU占满
}

1、检查接收缓冲区,无论是否就绪都立即返回结果

2、无数据时:返回EAGAIN错误码,进程继续执行(不进入睡眠)

IO多路复用

IO多路复用是一种单线程或单进程管理多个文件描述符(如套接字)的技术,核心是通过系统调用监视多个IO操作的状态,当某个IO操作就绪(可读、可写或发生异常)时,通知应用程序进行处理

select

select函数

头文件:#include <sys/socket.h> #include <sys/types.h>

函数原型:int select(int nfds, fd_set*readfds, fd_set*writefds,fd_set*exceptfds, struct timeval*timeout);

参数:

•nfds要监视的最大的文件描述符+1

•readfds要监视的读文件描述符集合,如果不关心,可以传NULL

•writefds要监视的写文件描述符集合,如果不关心,可以传NULL

•exceptfds要监视的异常文件描述符集合,如果不关心,可以传NULL

•timeout超时时间如果是NULL表示永久阻塞

返回值:成功返回返回就绪的文件描述符的个数,失败返回-1(并重置错误码)

poll

poll函数

头文件:#include <sys/socket.h> #include <sys/types.h>

函数原型:int poll(struct pollfd*fds, nfds_tnfds, int timeout);

参数:

•fds一个指向pollfd结构体数组的指针,它描述了要监视的文件描述符及其事件

•nfdsfds数组中有效描述符元素的数量

•timeout是等待时间,单位为毫秒

返回值:成功返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0,失败返回-1(并重置错误码)

IO多路复用

并发服务器

服务器模型

服务器模型有两种:循环服务器、并发服务器

循环服务器:循环服务器在同一个时刻只能响应一个客户端的请求

并发服务器:并发服务器在同一个时刻可以响应多个客户端的请求

TCP服务器默认的就是一个循环服务器,因为有两个阻塞的函数accept和recv之间相互影响

UDP服务器默认的就是一个并发服务器,因为只有一个阻塞的函数recvfrom

多线程并发服务器

实现原理:

流程:

主线程

创建监听Socket → bind() → listen()

while (1) {accept()接收新连接→ 创建子线程处理连接}

子线程

recv()/send()处理客户端请求

close()关闭连接

实现原理:

fork()模型:

•主进程监听连接,子进程处理请求

•父子进程完全独立,崩溃互不影响

预fork优化:

•启动时预先创建多个子进程(类似Apache)

•通过共享监听socket(SO_REUSEPORT)实现负载均衡

主进程:

•1.创建监听Socket → bind() → listen()

•2. while (1) {accept()接收新连接→ fork()创建子进程处理连接}

子进程:

•1. close()关闭监听Socket(继承自父进程)

•2. recv()/send()处理客户端请求

•3. exit()退出

IO多路复用并发服务器

|---------------------------------|-----------------------------|----------------------------------------------------------------------------------------------------------------------------|
| 主线程 |||
| 1.创建监听Socket → bind() →listen() | 2.初始化fd_set集合,将监听Socket加入集合 | 3. while (1) { select()监听所有fd →返回就绪的fd数量 for (每个就绪的fd) { if (是监听Socket) →accept()新连接并加入fd_set else → recv()/send()处理数据 } } |

pollfd结构体

cs 复制代码
struct pollfd {
    int fd;         // 文件描述符
    short events;   // 等待的事件
    short revents;  // 实际发生的事件
};
事件类型 常值 说明
读事件 POLLIN 普通或优先带数据可读
读事件 POLLPRI 高优先级数据可读
写事件 POLLOUT 普通或优先带数据可写
写事件 POLLWRNORM 普通数据可写
错误事件 POLLERR 发生错误
错误事件 POLLHUP 发生挂起
错误事件 POLLNVAL 描述符不是打开的文件

TCP/IP协议

常见协议头分析

TCP/IP协议网络封包格式

|------------|----------|-----|-----|-----|-----|-----|-----|-----------|
| 源端口(16bit) || 目的端口(16bit) |||||||
| 序号(32bit) |||||||||
| 确认号(32bit) |||||||||
| 源IP地址(32bit) |||||||||
| 数据偏移(4bit) | 保留(6bit) | URG | ACK | PSH | PSH | SYN | FIN | 窗口(16bit) |
| 校验和(16bit) || 紧急指针(16bit) |||||||
| 选项(长度可变) ||||| 填充 ||||

|-----|------|-----------|-----|-----|
| 版本号 | 首部长度 | 服务类型(TOS) | 总长度 ||
| 标识 ||| 标志位 | 片偏移 |
| 生存时间(TTL) || 协议 | 首部检验和 ||
| 源IP地址 |||||
| 目的IP地址 |||||
| 选项字段(长度可变) ||| 填充 ||
| 数据 |||||

TCP/IP协议数据封装过程

抓包工具wireshark的使用

TCP沾包问题及解决方案

TCP沾包问题

TFTP协议

TFTP协议格式

TFTP通讯流程

TFTP文本传输

相关推荐
通信小呆呆9 小时前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
H__Rick9 小时前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee9 小时前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
Alsn8610 小时前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker
YM52e10 小时前
买菜计算器小应用 - HarmonyOS ArkUI 开发实战-PC版本
学习·华为·harmonyos·鸿蒙·鸿蒙系统
小雨下雨的雨10 小时前
HarmonyOS ArkUI训练营入门-组件掌握系列-Animation 动画效果实现-PC版本
学习·华为·harmonyos·鸿蒙
闪闪发亮的小星星11 小时前
高斯光以及高斯光公式解释
笔记
cqbzcsq11 小时前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
YangYang9YangYan13 小时前
2026初入职场学习数据分析的价值
学习·数据挖掘·数据分析
阿米亚波13 小时前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm