10:30起床
资源
网络编程
网络演变过程
阻塞IO、非阻塞IO
IO多路复用第一版、第二版
异步IO
主流网络模型
网络框架源码
编写一个server的步骤?
Socket()方法, 获得一个serverfd
Bind()方法,绑定serverfd和地址(ip和port)
Listen()方法,监听绑定的地址
Accept()方法,不断的循环等待接受客户端的链接请求
server怎么处理 建立连接后的client请求的?
read()方法,不断从客户端clintfd里 读传来的数据,存在buf里
write()方法,不断往客户端clintfd里 写n个字节数据,写的数据在buf中
server和client的完整交互过程
网络演变是为了什么?
阻塞IO
应用层先发起系统调用
内核层等待数据
内核层copy数据给应用层
应用层处理该数据
注意,这里有2个地方阻塞,一个是内核在等待数据的时候,一个是复制数据的时候,应用只能等内核的回应
优点:
实现了client和server的通信
实现简单,一个client分配一个线程
缺点:
一个server的线程有限
大量的线程会造成上下文切换过多,性能大量下降
改进
阻塞IO在 内核等待数据 和 内核copy数据 这2个地方阻塞,应用只能干等,效率低
当应用向内核发起系统调用时,如果内核还在等待数据,则直接返回EWOULDBLOCK错误,不让应用一直干等。
应用得到EWOULDBLOCK错误时,知道内核还在等待数据,会换一个时间再请求。
注意:所以,非阻塞IO需要内核的支持
如何设置非阻塞?
方法一:
socket(),type设为SOCK_NONBLOCK
方法二(常用):
fcntl(), 将args参数设置O_NONBLOCK
非阻塞IO有什么问题?
优点:
读取时,数据未就绪就直接返回,因为这非阻塞的特性可以一个线程管理多个client连接
缺点:
不断的询问内核数据是否就绪,涉及过多的系统调用,太频繁了
怎么优化非阻塞IO的缺点?(IO多路复用第一版)
应用层会频繁的系统调用询问有没有数据,
改变方法就是应用等待内核的主动通知。
每一个client都有一个流程,这也是系统调用频繁的原因。
、改进方法是原先单个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()
size参数已经被忽略,但必须大于0
epoll_create() 返回的是 epollfd 是一个指向内核的一个epoll实例
epollfd不再使用时,要close()关闭
成功创建时,epollfd 是大于0的数,失败返回-1,需要errno查看错误
epoll_ctl()
epfd:哪个epoll
op: 什么操作(添加、更新、删除)
fd:哪个客户端
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内核实现
结构
内核空间:
红黑树:
就绪列表:
用户空间: