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的区别

pollepoll 都是 Linux 下的多路复用机制,用于监听多个文件描述符的 I/O 事件。

它们的区别主要体现在以下几个方面:

  1. 工作方式:poll 函数和 epoll 函数都是通过内核将 I/O 事件通知给用户进程,但是 poll 是基于轮询的方式,而 epoll 是基于事件通知的方式。在 poll 中,用户进程需要不停地轮询所有的文件描述符,直到有事件发生,而 epoll 只需要在事件发生时通知用户进程,从而减少了不必要的 CPU 开销。

  2. 可扩展性:在大量连接的情况下,poll 的效率会逐渐下降,因为每次调用 poll 都需要遍历所有的文件描述符。而 epoll 利用了内核中的红黑树和双向链表等数据结构,可以支持数十万甚至百万级别的连接。

  3. 接口复杂度:epoll 接口比 poll 接口复杂,需要使用多个函数来完成不同的操作。例如,epoll 需要使用 epoll_create() 函数创建一个 epoll 实例,然后使用 epoll_ctl() 函数来注册和删除文件描述符,最后使用 epoll_wait() 函数等待事件的发生。而 poll 只需要使用一个函数即可完成上述操作。

  4. 可移植性:poll 是 POSIX 标准的一部分,可以在大多数 UNIX 系统上使用,而 epoll 只能在 Linux 系统上使用。

综上所述,epoll 通常比 poll 更适合高并发的网络编程,因为它的效率更高、可扩展性更好。但是,在简单的应用程序中,使用 poll 也是可以的,因为它的接口简单、可移植性好。
poll例子
epoll例子见网络字典项目