IO模型
阻塞IO
读阻塞
-
当缓冲区中无数据,或者需要等待某种资源时,就会发送读阻塞
- read、recv、fgets、scan…
写阻塞
-
当缓冲区没有足够空间时,发送写阻塞
- write、send
在程序中使用阻塞IO时,有可能会阻塞其他代码的执行
非阻塞IO
非阻塞IO会频繁调用函数,开销较大
fcntl()
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
fd:文件描述符
cmd:
F_GETFL:获取文件属性状态
F_SETFL:设置文件属性状态
返回值:
根据参数cmd的不同返回不同的值
cmd -- F_GETFL:返回获取的文件属性状态
例:
1、获取文件描述符对应属性状态
int flag = fcntl(fd, F_GETFL, 0);
2、将flag添加设置非阻塞方式
flag = flag | O_NONBLOCK;
3、将新的状态属性设置到文件描述符中
fcntl(fd, F_SETFL, flag);
IO多路复用
select
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.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
void FD_CLR(int fd, fd_set *set); //从表中删除文件描述符
int FD_ISSET(int fd, fd_set *set); //判断指定的文件描述符是否有响应,结果为真--有响应
void FD_SET(int fd, fd_set *set); //向表中添加文件描述符
void FD_ZERO(fd_set *set); //清0文件描述符表
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
timeout的使用方法:
struct timeval mt;
mt.tv_sec = 10;
mt.tv_usec = 500;
select 流程
1、定义监测表,清空表
fd_set rfds;
FD_ZERO(&rfds);
2、向表中添加要监测的文件描述符0、3
FD_SET(0, &rfds);
FD_SET(3, &rfds);
int maxfd = 3+1;
3、循环监测表
int i = 0;
while(1)
{
select(maxfd, &rfds, NULL, NULL, NULL);
for(i = 0; i < maxfd; i++)
{
if(FD_ISSET(i, &rfds))
{
if(i == 0)
{
//实现对应IO操作
}
else if(i == 3)
{
//实现对应IO操作
}
}
}
}
自己写的select代码如下:
同时打印键盘上输入的值和鼠标移动产生的内容
注意加sudo去执行,因为访问了鼠标的文件
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
fd_set rfds;
FD_ZERO(&rfds);
char buf1[64]={0};
char buf2[64]={0};
int fd=open("/dev/input/mice",O_RDONLY);
FD_SET(0,&rfds);
FD_SET(fd,&rfds);
int maxfd=fd+1;
int i=0;
fd_set old_rfds=rfds;
while(1){
//key:不这样设置的话rfds的值会改变,就不会接收到其他文件描述符的改变了
rfds=old_rfds;
select(maxfd,&rfds,NULL,NULL,NULL);
for(i=0;i<maxfd;i++){
if(FD_ISSET(i,&rfds)){
if(i==0){
fgets(buf1,64,stdin);
printf("recv info : %s\n",buf1);
}else if(i==fd){
read(fd,buf2,64);
printf("mouse said : %d\n",buf2[0]);
}
}
}
}
return 0;
}
poll()
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
fds:文件描述符监测表的首地址,本质上是一个结构体数组的首地址
nfds:监测表中监测的文件描述符的个数
timeout:设置超时检测的时间,单位是ms
返回值:
-1,错误 0 -- 超时
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
events:
POLLIN:读监测
POLLOUT:写监测
POLLERR:异常监测
revent:
revent & POLLIN == 真 -- 有读响应
revent & POLLOUT == 真 -- 有写响应
创建表:
int i = 0;
struct pollfd pfds[100];
for(i = 0; i < 100; i++)
{
pfds[i].fd = -1;
}
添加要监测的文件描述符
int pos = -1;
pfds[++pos].fd = 0;
pfds[pos].events = POLLIN;
pfds[++pos].fd = 3;
pfds[pos].events = POLLIN;
循环进行poll检测:
while(1)
{
ret = poll();
for(i = 0; i <= pos; i++)
{
if(pfds[i].revents & POLLIN)
{
if(pfds[i].fd == 0)
{
//对应IO操作
}
}
}
}
epoll()
#include <sys/epoll.h>
int epoll_create(int size);
//创建用于控制监测表的句柄
参数:
size:大于0的正整数
返回值:
成功返回控制监测表的句柄,失败返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd:句柄
op:控制选项
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
fd:要控制的文件描述符的值
event:监测表中某一个元素的地址
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
参数:
epfd:句柄
events:监测表的首地址
maxevents:监测文件描述符的个数
timeout:设置超时的时间
流程:
int epfd = epoll_create(1);
struct epoll_event epfds[100];
for(i = 0; i < 100; i++)
{
epfds[i].data.fd = -1;
}
int pos = -1;
epfds[++pos].data.fd = 0;
epfds[pos].events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &epfds[pos]);
epfds[++pos].data.fd = 3;
epfds[pos].events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, 3, &epfds[pos]);
while(1)
{
int ret = epoll_wait(epfd, epfds, pos+1, -1);
for(i = 0; i < ret; i++)
{
if(epfds[i].events & EPOLLIN)
{
if(epfds[i].data.fd == 0)
{
//对应IO操作
}
}
}
}
poll和epoll的区别
poll 和 epoll 都是 Linux 下的多路复用机制,用于监听多个文件描述符的 I/O 事件。
它们的区别主要体现在以下几个方面:
-
工作方式:
poll函数和epoll函数都是通过内核将 I/O 事件通知给用户进程,但是poll是基于轮询的方式,而epoll是基于事件通知的方式。在poll中,用户进程需要不停地轮询所有的文件描述符,直到有事件发生,而epoll只需要在事件发生时通知用户进程,从而减少了不必要的 CPU 开销。 -
可扩展性:在大量连接的情况下,
poll的效率会逐渐下降,因为每次调用poll都需要遍历所有的文件描述符。而epoll利用了内核中的红黑树和双向链表等数据结构,可以支持数十万甚至百万级别的连接。 -
接口复杂度:
epoll接口比poll接口复杂,需要使用多个函数来完成不同的操作。例如,epoll需要使用epoll_create()函数创建一个epoll实例,然后使用epoll_ctl()函数来注册和删除文件描述符,最后使用epoll_wait()函数等待事件的发生。而poll只需要使用一个函数即可完成上述操作。 -
可移植性:
poll是 POSIX 标准的一部分,可以在大多数 UNIX 系统上使用,而epoll只能在 Linux 系统上使用。
综上所述,epoll 通常比 poll 更适合高并发的网络编程,因为它的效率更高、可扩展性更好。但是,在简单的应用程序中,使用 poll 也是可以的,因为它的接口简单、可移植性好。
poll例子
epoll例子见网络字典项目