IO管理
记录在零声学院所学的点点滴滴,方便后续查看。欢迎提出问题!相互探讨
什么是网络IO
客户端连接服务器所产生的fd
为什么要使用select,epoll,poll
当多个客户端连接在服务器时,如何判断哪个有数据。
传统做法:
1、开多线程
缺点: fd 做的不多
2、while循环receive所有io
缺点: 当网络io成千上万时,大量的时候是没有数据的。
因此,专门用个组件(类似于select等多路复用),判别io里面有没有数据。
IO三种状态
- 是否可读
- 是否可写
- 是否出错
IO五种网络模型
阻塞IO (blocking IO)
非阻塞IO(non-blocking IO)
多路复用IO (IO multiplexing)
select
其实select就是利用位图进行操作
主要代码
// fd_set: 位图。存储所有fd的状态,0和1
fd_set rfds, rest;
// 将所有的fd置0
FD_ZERO(&rfds);
// 将位图上sockfd对应的状态置1
FD_SET(sockfd, &rfds);
int maxfd = sockfd + 1;
// 不断检测io里面有没有数据
while (1) {
rset = rfds;
// param1: 最大的fd数+1; param2: 是否可读; param3: 是否可写; param4: 是否出错; param5: 时间戳
// param5: 为 NULL:如果关注fd没有一个值,则一直阻塞。 有值:在该值范围内筛选
// nready:满足需求的个数。如下为筛选可读的io个数。
int nready = select(maxfd, &rset, NULL, NULL, NULL);
if (nready < 0) continue;
// 判断sockfd,是否在rset里面
if(FD_ISSET(sockfd, &rset)) {
struct sockaddr_in clientaddr;
int clientfd = accept(sockfd, (struct aockaddr*) &client_addr, &client_len);
if (clientfd < 0) continue;
FD_SET(clientfd, &rfds);
if(clientfd > maxfd) maxfd = clientfd;
printf("sockfd: %d, max_fd:%d, clientfd: %d\n", sockfd, max_fd, clientfd);
if(--nready == 0) continue;
}
for (i = sockfd +1; i <= max_fd; i++) {
if (FD_ISSET(i, &rset)) {
char buffer[BUFFER_LENGTH] = {0};
int ret = recv(i, buffer, BUFFER_LENGTH,0);
if (ret < 0) {
// recv里面没有数据
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("read all data");
}
FD_CLR (i, &rfds);
close(i);
} else if(ret == 0) {
printf ("disconnect %d\n", i);
FD_CLR(i, &rfds);
close(i);
break;
}else {
printf("Recv: %s, %d Betys\n", buffer, ret);
}
if (-- nready == 0) break;
}
}
总结:
- 用一个位图存储fd
- select函数 选出 满足需求(可读、可写) 的fd位图
- 对位图上的客户端进行遍历,得到数据。依次循环。
epoll
概述:
类似于蜂巢快递柜, 快递员往快递柜存快递, 小区的人在快递柜拿快递
重要函数
epoll_create(int size)
: 为小区请快递员。创建跟节点
size :只有大于0和等于0的区别。传一和十万是一样的。最初实现的时候是有意义的,为就绪队列的长度
epoll_ctl()
: 信息的修改。 红黑树添加修改节点
epoll_wait()
: 快递员多长时间跑一下小区。多长时间抓就绪队列里的节点
实质:
由红黑树和队列组成。红黑树包含所有的fd, 队列里面包含的是有数据的fd, 也叫做就绪队列。
红黑树和就绪队列共享节点。当有数据时, fd加入就绪队列。
ET
:边沿触发。无数据到有数据则触发
LT
:水平触发。recvbuff里面有数据就一直触发
大块数据(发送数据大于recvbuff)建议用LT, 小块数据建议ET。
listen 的时候 建议用ET。避免丢失少许客户端的连接。用LT的话,accpect可能会丢。
总结:
- epoll_create()创建跟节点
- epoll_ctrl() epoll_fd 与 socketfd相连接。
- epoll_wait()得出有数据的就绪队列。
- 对就绪队列里面的fd进行循环获取数据
poll
仅仅是把select函数里的param2,param3,param4合在了一起。
异步IO (Asynchronous I/O)
信号驱动IO (signal driven I/O)
信号量与信号
信号量
: PV操作
信号
: 给每个进程通知的信号
举例说明
kill -9 进程名字: 向进程发送信号
使用场景
当进程初始化连接数据库,不可控条件退出时,可以用信号函数捕获或清空网络
其他
信号处理函数具体是在内核态用户态切换时调用的
相关函数
// 信号与进程绑定
sigaction(SIGIO, &sigio_action, NULL);
// 给fd设置相关的参数,比如阻塞和非阻塞
fcntl(sockfd, F_SETOWN, getpis());
其他
异步怎么理解(形容两者间的关系)
检测IO是否有数据与读写数据这两步操作不在一个流程里面
非阻塞和非阻塞(设置IO的状态)
非阻塞
:有没有数据都返回
阻塞
:有数据就读取,没有数据则不读