一、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);
返回值:成功返回检测表中的文件描述符个数,失败返回-1,0表示监测超时
参数:
nfds:
要监测的文件描述表中最大的文件描述符的值+1
readfds:
监测读表的首地址,如果没有监测读表,填NULL
writefds:
监测写表的首地址,如果没有监测写表,填NULL
exceptfds:
监测异常表的首地址如果没有监测异常表,填NULL
timeout:
超时监测,当设置时间到,没有对应的读写响应,函数select返回0
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 */
};
eg:
struct timeval t;
t.tv_sec = 5;
t.tc_usec = 500;
监测流程:
监测流程:
1.创建监测表(读、写、异常) ---fd_set rfds,tmp;//创建初始表和临时表
2.清空监测表 ---FD_ZERO();
3.将用到的文件描述符添加到监测表中去 --FD_SET();
while(1)
{
tmp=rfds;
循环监测表--select(nfds,&tmp,NULL,NULL,NULL);
for(int i=0;i<nfds;i++)
{
if(i==0) //stdin
{
执行对应IO操作;
}
else if(i==fd)
{
执行对应IO操作
}
}
}
练习:用IO多路复用select实现TCP服务器的并发
select机制的缺点:
1.监测文件描述符的个数有1024的限制
2.初始表和返回表是分离的,每一次成功监测,我们都需要还原初始表
eg1:实现鼠标移动打印鼠标信息和可以从键盘输出打印
int main(int argc, char *argv[])
{
int fd = open("/dev/input/mice", O_RDONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
fd_set rfds,tmp;
int maxfd = fd+1;
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(fd, &rfds);
char buf[64] = {0};
char buf1[64] = {0};
while(1)
{
tmp = rfds;
if(select(maxfd, &tmp, NULL, NULL, NULL) < 0)
{
perror("select");
exit(-1);
}
for(int i = 0; i < maxfd; i++)
{
if(FD_ISSET(i, &tmp))
{
if(i == 0)
{
fgets(buf, 64, stdin);
printf("%s", buf);
}
else if(i == fd)
{
read(i, buf1, 64);
printf("%d -- %d -- %d\n", buf1[0], buf1[1], buf1[2]);
}
}
}
}
return 0;
}
eg2:select实现服务器并发
int main(int argc, char *argv[])
{
//1、创建套接字 -- socket()
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("socket successful!\n");
//2、绑定本机IP地址和端口号
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //选择ipv4协议
saddr.sin_port = htons(6666);
//saddr.sin_addr.s_addr = inet_addr("192.168.16.163"); //将点分十进制转换为整形之后再赋值
saddr.sin_addr.s_addr = INADDR_ANY;
int s_len = sizeof(saddr); //计算服务器地址结构的大小
int ret = bind(sockfd, (struct sockaddr *)&saddr, s_len);
if(ret < 0)
{
perror("bind");
exit(-1);
}
printf("bind successful!\n");
//3、设置监听套接字 -- listen()
ret = listen(sockfd, 15);
if(ret < 0)
{
perror("listen");
exit(-1);
}
printf("listen successful!\n");
//4、等待客户端连接
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
int c_len = sizeof(caddr);
fd_set rfds,tmp;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
int maxfd = sockfd+1;
int connfd = -1;
struct timeval t;
while(1)
{
t.tv_sec = 5;
t.tv_usec = 0;
tmp = rfds;
ret = select(maxfd, &tmp, NULL, NULL, &t);
if(ret < 0)
{
perror("select");
exit(-1);
}
if(ret == 0)
{
printf("time-out!\n");
continue;
}
for(int i = 0; i < maxfd; i++)
{
if(FD_ISSET(i, &tmp))
{
if(sockfd == i)
{
connfd = accept(i, (struct sockaddr *)&caddr, &c_len);
if(connfd < 0)
{
perror("accept");
exit(-1);
}
printf("link successful! ip -- %s port -- %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
FD_SET(connfd, &rfds);
maxfd = (connfd >= maxfd)?(maxfd+1):(maxfd);
}
if(i > sockfd)
{
char buf[64] = {0};
//memset(buf, 0, 64);
ret = read(i, buf, 64);
if(ret < 0)
{
perror("read");
break;
}
if(ret == 0)
{
//printf("a client leave!\n");
printf("%s leave!\n", inet_ntoa(caddr.sin_addr));
close(i);
FD_CLR(i, &rfds);
maxfd = (i == maxfd-1)?(maxfd-1):(maxfd);
break;
}
printf("recv %dbytes:%s\n", ret, buf);
}
}
}
}
close(sockfd);
return 0;
}
二、poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
返回值:
错误返回-1,超时返回0;
参数:
fds:监测表的首地址,监测表本质是一个结构体数组,每一个监测元素都是一个结构体
nfds:监测表中监测事件的个数
timeout:设置的超时时间,单位为ms,不设置时间写-1
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */ //选择监测方式
short revents; /* returned events */
};
fd:文件描述符
events: 检测方式
POLLIN: 读检测
POLLOUT:写检测
POLLERR:异常监测
revents:监测成功的返回事件
如果revents&POLLIN结果为真,表示该文件描述符有读响应
步骤:
struct pollfd buf[100];
for(int i=0;i<100;i++) //初始化监测表
{
buf[i].fd=-1;
}
int nfds=-1;
struct pollfd[++nfds].fd=0;//buf[++nrfds].fd=0一样 //向表中插入文件描述符0
struct pollfd[nfds].event=POLLIN; //选择检测方式为读监测
struct pollfd[++nfds].fd=3; //插入文件描述符3
struct pollfd[nfds].event=POLLIN; //读监测
while(1)
{
poll(buf,nfds+1,-1);
for(int i=0;i<=nfds;i++)
{
if(buf[i].revents & POLLIN)
{
if(buf[i].fd==0) //stdin
{
//实现对应的IO操作
}
if(buf[i].fd==3)
{
//执行相应操作
//操作完后
close(3);
buf[i].fd=-1; //将文件描述符从表中删除
}
}
}
}
eg1:鼠标移动打印信息,键盘输入字符
int main(int argc, char *argv[])
{
int fd = open("/dev/input/mice", O_RDONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
struct pollfd buf[1000];
for(int i = 0; i < 1000; i++)
{
buf[i].fd = -1;
}
int nfds = -1;
buf[++nfds].fd = 0;
buf[nfds].events = POLLIN;
buf[++nfds].fd = 3;
buf[nfds].events = POLLIN;
int ret;
char buf2[64] = {0};
char buf1[64] = {0};
while(1)
{
ret = poll(buf, nfds+1, 5000);
if(ret < 0)
{
perror("poll");
exit(-1);
}
if(ret == 0)
{
printf("time-out!\n");
exit(-1);
}
for(int i = 0; i <= nfds; i++)
{
if(buf[i].revents & POLLIN)
{
if(buf[i].fd == 0)
{
fgets(buf2, 64, stdin);
printf("%s", buf2);
}
else if(buf[i].fd == fd)
{
read(fd, buf1, 64);
printf("%d -- %d -- %d\n", buf1[0], buf1[1], buf1[2]);
}
}
}
}
return 0;
}
eg2:poll实现服务器并发
int main(int argc, char *argv[])
{
//1、创建套接字 -- socket()
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("socket successful!\n");
//2、绑定本机IP地址和端口号
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //选择ipv4协议
saddr.sin_port = htons(6666);
//saddr.sin_addr.s_addr = inet_addr("192.168.16.163"); //将点分十进制转换为整形之后再赋值
saddr.sin_addr.s_addr = INADDR_ANY;
int s_len = sizeof(saddr); //计算服务器地址结构的大小
int ret = bind(sockfd, (struct sockaddr *)&saddr, s_len);
if(ret < 0)
{
perror("bind");
exit(-1);
}
printf("bind successful!\n");
//3、设置监听套接字 -- listen()
ret = listen(sockfd, 15);
if(ret < 0)
{
perror("listen");
exit(-1);
}
printf("listen successful!\n");
//4、等待客户端连接
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
int c_len = sizeof(caddr);
struct pollfd buf[100];
for(int i = 0; i < 100; i++)
{
buf[i].fd = -1;
}
int nfds = -1;
buf[++nfds].fd = sockfd;
buf[nfds].events = POLLIN;
int connfd = -1;
int j;
while(1)
{
ret = poll(buf, nfds+1, 10000);
if(ret < 0)
{
perror("poll");
exit(-1);
}
if(ret == 0)
{
printf("time-out!\n");
continue;
}
for(int i = 0; i <= nfds; i++)
{
if(buf[i].revents & POLLIN)
{
if(sockfd == buf[i].fd)
{
connfd = accept(buf[i].fd, (struct sockaddr *)&caddr, &c_len);
if(connfd < 0)
{
perror("accept");
exit(-1);
}
printf("link successful! ip -- %s port -- %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
for(j = 0; j < nfds+1; j++)
{
if(buf[j].fd == -1)
{
buf[j].fd = connfd;
buf[nfds].events = POLLIN;
break;
}
}
if(j == nfds+1)
{
buf[++nfds].fd = connfd;
buf[nfds].events = POLLIN;
}
if(buf[i].fd > sockfd)
{
char buf1[64] = {0};
//memset(buf, 0, 64);
ret = read(buf[i].fd, buf1, 64);
if(ret < 0)
{
perror("read");
break;
}
if(ret == 0)
{
//printf("a client leave!\n");
printf("%s leave!\n", inet_ntoa(caddr.sin_addr));
close(buf[i].fd);
buf[i].fd = -1;
break;
}
printf("recv %dbytes:%s\n", ret, buf1);
}
}
}
}
close(sockfd);
return 0;
}
三、epoll
epoll监测表的实质是一个结构体数组存放每一个需要监测的信息,
和poll不同的是epoll的描述符会依次填充
0.epoll执行步骤
int epfd=epoll_create();
struct epoll_event buf[N]; //创建表
epoll_ctl(); //向表中添加元素
while(1)
{
ret=epoll_wait();
for(int i=0;i<ret;i++)
{
if(buf[i].events & EPOLLIN)
{
if(buf[i].data.fd==sockfd)
{
accept();
}
}
}
}
1.epoll_create()
#include <sys/epoll.h>
int epoll_create(int size);
返回值:
成功:返回控制监测表的句柄,本质是一个文件描述符
该句柄的作用:用来操作整个表
失败:返回-1
参数:
size:大于0的整数,可以是任意值
2.epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:向表中添加元素
返回值:失败返回-1,成功返回0
参数:
epfd:控制监测表的句柄
op:
EPOLL_CTL_ADD:添加文描述符
EPOLL_CTL_DEL:删除文件描述符
EPOLL_CTL_MOD:修改文件描述符
fd:
想要操作的文件描述符
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
例:向监测表添加文件描述符3
struct epoll_event buf[100];
for(int i=0;i<100;i++)
{
buf[i].data.fd=-1;
}
int nfds=-1;
buf[++nfds].data.fd=3;
buf[nfds].events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,3,&buf[nfds]);
3.epoll_wait()
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
返回值:
成功返回响应事件的个数,
失败返回-1,
0表示超时
参数:
epfd:句柄
events:监测表的首地址 (监测表的实质是一个结构体数组)
maxevents:监测事件的个数 nfds+1 (nfds为下标)
timeout:设置的超时时间,单位为ms,如果不想设置超时时间,填-1
判断事件是否有响应:
buf[i].events&EPOLLIN----结果为真表示有读响应
eg.用epoll来实现 鼠标移动打印信息,键盘输入字符
int main(int argc, char *argv[])
{
int fd = open("/dev/input/mice", O_RDONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
struct epoll_event buf[100];
for(int i = 0 ; i < 100; i++)
{
buf[i].data.fd = -1;
}
int epfd = epoll_create(1); //
int nfds = -1;
buf[++nfds].data.fd = 0; //将0放进结构体数组中
buf[nfds].events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &buf[nfds]);
buf[++nfds].data.fd = 3; //将描述符3放进监测表
buf[nfds].events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, 3, &buf[nfds]);
char buf1[64] = {0};
char buf2[64] = {0};
while(1)
{
int ret = epoll_wait(epfd, buf, 100, 10000);
for(int i = 0; i < ret; i++)
{
if(buf[i].events & EPOLLIN)
{
if(buf[i].data.fd == 0)
{
fgets(buf1, 64, stdin);
printf("%s", buf1);
}
else if(buf[i].data.fd == fd)
{
read(fd, buf2, 64);
printf("%d -- %d -- %d\n", buf2[0], buf2[1], buf2[2]);
}
}
}
}
return 0;
}
4. 三种机制优缺点
1.select机制:
缺点:
检测文件描述符最大只能管理1024个
初始表和返回表是分离的,每一次在检测之间应该还原初始表
2.poll机制
特点:
没有检测文件描述符1024的上限
初始化表和返回表是结合的
3.epoll机制:
只能在linux下使用