Administrator
发布于 2023-09-20 / 9 阅读 / 0 评论 / 0 点赞

9月20日

10:30起床

资源

网络编程

网络演变过程

阻塞IO、非阻塞IO

IO多路复用第一版、第二版

异步IO

主流网络模型
网络框架源码

编写一个server的步骤?
  1. Socket()方法, 获得一个serverfd

  2. Bind()方法,绑定serverfd和地址(ip和port)

  3. Listen()方法,监听绑定的地址

  4. Accept()方法,不断的循环等待接受客户端的链接请求

server怎么处理 建立连接后的client请求的?
  1. read()方法,不断从客户端clintfd里 读传来的数据,存在buf里

  2. write()方法,不断往客户端clintfd里 写n个字节数据,写的数据在buf中

server和client的完整交互过程

网络演变是为了什么?
阻塞IO
  1. 应用层先发起系统调用

  2. 内核层等待数据

  3. 内核层copy数据给应用层

  4. 应用层处理该数据

注意,这里有2个地方阻塞,一个是内核在等待数据的时候,一个是复制数据的时候,应用只能等内核的回应

优点

  • 实现了client和server的通信

  • 实现简单,一个client分配一个线程

缺点:

  • 一个server的线程有限

  • 大量的线程会造成上下文切换过多,性能大量下降

改进

  • 阻塞IO在 内核等待数据 和 内核copy数据 这2个地方阻塞,应用只能干等,效率低

  • 当应用向内核发起系统调用时,如果内核还在等待数据,则直接返回EWOULDBLOCK错误,不让应用一直干等。

  • 应用得到EWOULDBLOCK错误时,知道内核还在等待数据,会换一个时间再请求。

注意:所以,非阻塞IO需要内核的支持

如何设置非阻塞?

方法一:

socket(),type设为SOCK_NONBLOCK

方法二(常用):

fcntl(), 将args参数设置O_NONBLOCK

非阻塞IO有什么问题?

优点:

  • 读取时,数据未就绪就直接返回,因为这非阻塞的特性可以一个线程管理多个client连接

缺点:

  • 不断的询问内核数据是否就绪,涉及过多的系统调用,太频繁了

怎么优化非阻塞IO的缺点?(IO多路复用第一版)
  1. 应用层会频繁的系统调用询问有没有数据,

    1. 改变方法就是应用等待内核的主动通知。

  2. 每一个client都有一个流程,这也是系统调用频繁的原因。

    1. 、改进方法是原先单个client的系统调用变成多个client一起的一个系统调用

IO多路复用到底复用的是啥?

多路复用复用的是系统调用,原先每个client都要单独向内核询问,变成了通过一次系统调用select/poll,由内核主动通知client数据是否就绪。

fd_set 结构体实现

一个微数组,每个位代表一个client的状态

void FD_ZERO()

整个微数组清零

void FD_SET()

将某个client的位 打开???把我的client加入

void FD_CLR()

将某个client的位 关闭???把我的client删除

int FD_ISSET()

查找某个client是否 打开???查询我的client在不在里面

select()
int select(int maxdp1,fd_set *readset,fd_set *writeset,fd_set *exceptest,const struct timeval *timeout);
int maxdp1						// 描述符的个数+1
fd_set *readset					// 读事件 的微数组
fd_set *writeset				// 写事件 的微数组
fd_set *exceptest				// 异常事件 的微数组
const struct timeval *timeout	// 超时   0 立即返回  正数 等待时间  null或者负数 一直等待
poll()

select和poll的区别?
  • 一个位数组,一个pollfd数组

  • 一个默认大小1024,修改要编译内核,一个是变长数组

  • 共同点:

    • 都是IO多路复用的第一版经典实现

    • 调用时,都需要从用户态复制 管理的全量描述符 到内核态;返回时,用户态遍历 全量描述符判断有哪些描述符有就绪事件

IO多路复用第一版有啥问题?

优点:

  • 一次系统调用实现管理多个client的事件,降低了非阻塞IO频繁无效系统调用

  • 应用态的主动询问内核 变成 等待内核通知,性能提升

缺点:

  • 每次select()/poll()都需要将注册管理的多个client从用户态到内核态。在管理百万连接时,拷贝带来的资源开销太大,性能不足

怎么改进?(第二版IO多路复用epoll)
  • 拷贝变成不拷贝 (应用层调用时)

  • 模糊通知就绪 变成 明确告诉哪些client事件就绪 (内核返回时)

epoll三大核心接口

epoll_create()
  1. size参数已经被忽略,但必须大于0

  2. epoll_create() 返回的是 epollfd 是一个指向内核的一个epoll实例

  3. epollfd不再使用时,要close()关闭

  4. 成功创建时,epollfd 是大于0的数,失败返回-1,需要errno查看错误

epoll_ctl()
  1. epfd:哪个epoll

  2. op: 什么操作(添加、更新、删除)

  3. fd:哪个客户端

  4. event:要监听的fd事件

事件宏定义

  • EPOLLIN // 该文件描述符可读

  • EPOLLOUT // 可写

  • EPOLLPRI // 有紧急数据可读

  • EPOLLERR // 文件描述符发生错误

  • EPOLLHUP // 文件描述符被挂断

  • EPOLLET // 将EPOLL设置为边缘触发(Edge Triggered)模式,这是相对水平触发Level Triggered来说的

  • EPOLLONSHOT // 只监听一次事件

epoll_wait()
  • epfd 哪个epoll

  • event 返回的就绪事件队列的指针

  • maxevents 返回的就绪事件队列的长度 共同构成一个可变长数组

  • timeout 超时时间

  • cnt 0 表示无就绪列表;大于0 就绪列表的个数 ; -1 错误 需要errno查询

epoll简单实例
epoll的ET模式和LT模式的区别?

触发时机:

仅当被监控的描述符有事件就绪时触发; 有事件将就绪或就绪事件未完全处理完 都会触发

性能消耗:

系统调用次数少;系统调用次数相比较多

编程难度:

高,数据完整性由上层用户态保证;低,有内核保证,也是epoll默认的模式

相同点:都是epoll的内置模式

都是实现网络传输功能

epoll内核实现

结构

内核空间:

红黑树:

就绪列表:

用户空间:


评论