定义:
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程
作用:
应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。
CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。
使用并发处理的成本:
线程/进程创建成本
CPU切换不同线程/进程成本 Context Switch
多线程的资源竞争
有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是IO多路复用。
IO模型
1、阻塞IO
2、非阻塞IO EAGAIN 忙等待 errno
3、信号驱动IO SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5, IO多路复用 select、poll、epoll
1、阻塞IO ===》最常用 默认设置
示例:
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
sleep(3);
}
close(fd);
return 0;
}
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]={0};
read(fd,buf,sizeof(buf));
printf("fifo %s\n",buf);
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
printf("terminal:%s\n",buf);
}
close(fd);
//remove("myfifo");
return 0;
}
2、非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。
在程序执行阶段调整文件的执行方式为非阻塞:
===》fcntl() ===>动态调整文件的阻塞属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
cmd 要调整的文件属性宏名称
... 可变长的属性值参数。
返回值:成功 不一定,看cmd
失败 -1;
eg:修改文件的非阻塞属性:
int flag ;
flag = fcntl(fd,F_GETFL,0); ///获取fd文件的默认属性到flag变量中。
flag = flag | O_NONBLOCK; ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag); ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
示例:
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
int flag = fcntl(fd,F_GETFL);
flag = flag|O_NONBLOCK ;
fcntl(fd,F_SETFL,flag);
flag = fcntl(0,F_GETFL);
fcntl(0,F_SETFL,flag|O_NONBLOCK);
while(1)
{
char buf[256]={0};
if(read(fd,buf,sizeof(buf))>0)
{
printf("fifo %s\n",buf);
}
bzero(buf,sizeof(buf));
if(fgets(buf,sizeof(buf),stdin))
{
printf("terminal:%s\n",buf);
}
}
close(fd);
//remove("myfifo");
return 0;
}
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
sleep(3);
}
close(fd);
return 0;
}
3.信号驱动io
文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。
1.追加标志
int flag ;
flag = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3.对信号进行捕获
signal(SIGIO,myhandle);//
示例:
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
int fd;
void handle(int num)
{
char buf[256]={0};
read(fd,buf,sizeof(buf));
printf("fifo %s\n",buf);
}
int main(int argc, char *argv[])
{
signal( SIGIO,handle);
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
int flag = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL ,flag|O_ASYNC);
fcntl(fd,F_SETOWN, getpid() );
while(1)
{
char buf[256]={0};
fgets(buf,sizeof(buf),stdin);
printf("terminal:%s\n",buf);
}
close(fd);
//remove("myfifo");
return 0;
}
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
sleep(3);
}
close(fd);
return 0;
}
4.并发
1.进程
示例:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/signal.h>
typedef struct sockaddr* (SA);
void handle(int num)
{
wait(NULL);
}
int main(int argc, char *argv[])
{
signal(SIGCHLD,handle);
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通信套接字
while(1)
{
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
continue;
}
pid_t pid = fork();
if(pid>0)
{
//父进程不需要调用recv
close(conn);
}
else if(0 == pid)
{
//子进程不需要调用accept
close(listfd);
while(1)
{
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{// 0 对方断开连接 -1 错误
printf("cli offline");
exit(0);
}
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
}
else
{
perror("fork");
continue;
}
}
close(listfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int i = 5;
while(i)
{
char buf[512]="hello,this is tcp test";
send(sockfd,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("buf :%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
2.线程
IO 多路复用 ===》并发服务器 ===》TCP协议
示例:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
typedef struct sockaddr* (SA);
void* th(void* arg)
{
pthread_detach(pthread_self());
int conn = *(int*)arg;
while(1)
{
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{// 0 对方断开连接 -1 错误
printf("cli offline\n");
close(conn);
break;
}
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
return NULL;
}
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
while(1)
{ //通信套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
pthread_t tid;
pthread_create(&tid,NULL,th,&conn);
}
close(listfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int i = 5;
while(i)
{
char buf[512]="hello,this is tcp test";
send(sockfd,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("buf :%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
3、select循环服务器 ===> 用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 只读描述符集
writefds 只写描述符集
exceptfds 异常描述符集
以上三个参数都是 fd_set * 的描述符集合类型
timeout 检测超时 如果是NULL表示一直检测不超时 。
返回值:超时 0
失败 -1
成功 >0
为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。
int FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,
如果在则返回真,否则返回假。
void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。
void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。
示例:
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
//1 create set
fd_set rd_set,tmp_set;
//2 add fd
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
FD_SET(fd,&tmp_set);
FD_SET(0,&tmp_set);
struct timeval tv;
while(1)
{
tv.tv_sec =2;
tv.tv_usec = 0;
//4 clean flag
rd_set = tmp_set;
//3 wait event
int sel_ret = select(fd+1,&rd_set,NULL,NULL,&tv);
if(sel_ret>0)
{
char buf[256]={0};
if(FD_ISSET(fd,&rd_set))
{
read(fd,buf,sizeof(buf));
printf("fifo %s\n",buf);
}
if(FD_ISSET(0,&rd_set))
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
printf("terminal:%s\n",buf);
}
}
else if(0 == sel_ret)
{
printf("time out\n");
}
else
{
perror("select");
exit(1);
}
}
close(fd);
//remove("myfifo");
return 0;
}
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
sleep(3);
}
close(fd);
return 0;
}
TCP——SELECT
示例:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//1create set
fd_set rd_set,tmp_set;
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
//2 add fd
FD_SET(listfd,&tmp_set);
int maxfd = listfd;
while(1)
{
rd_set =tmp_set;
select(maxfd+1,&rd_set,NULL,NULL,NULL);
//通信套接字
int i = 0 ;
for(i = listfd ;i<maxfd+1;i++)
{
if(FD_ISSET(i,&rd_set)&& i == listfd)
{
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
continue;
}
FD_SET(conn,&tmp_set);
if(conn>maxfd)
maxfd = conn;
}
if(FD_ISSET(i,&rd_set) &&i!=listfd)
{
int conn =i;
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{// 0 对方断开连接 -1 错误
printf("cli offline\n");
FD_CLR(conn,&tmp_set);
close(conn);
break;
}
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
}
}
close(listfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int i = 5;
while(i)
{
char buf[512]="hello,this is tcp test";
send(sockfd,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("buf :%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
select poll epoll的区别
select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
② 要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
③ timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④ select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可 写,异常),通常在处理事件时 会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。
select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。
3. epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
示例:
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
int add_fd(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret= epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
if(-1 == ret)
{
perror("add fd");
exit(1);
}
return ret;
}
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
struct epoll_event rev[2]={0};
//1 create set
int epfd = epoll_create(2);
if(-1 == epfd)
{
perror("epoll create");
exit(1);
}
//2 add fd
add_fd(epfd,0);
add_fd(epfd,fd);
while(1)
{
//3 wait
int ep_ret= epoll_wait(epfd,rev,2,-1);
int i = 0 ;
for(i=0;i<ep_ret;i++)
{
if(rev[i].data.fd == fd)
{
char buf[256]={0};
read(fd,buf,sizeof(buf));
printf("fifo %s\n",buf);
}
if(0 == rev[i].data.fd )
{
char buf[256]={0};
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
printf("terminal:%s\n",buf);
}
}
}
close(fd);
//remove("myfifo");
return 0;
}
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
sleep(3);
}
close(fd);
return 0;
}
TCP——EPOLL
示例:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <sys/epoll.h>
typedef struct sockaddr* (SA);
int add_fd(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret= epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
if(-1 == ret)
{
perror("add fd");
exit(1);
}
return ret;
}
int del_fd(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret= epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);
if(-1 == ret)
{
perror("delfd");
exit(1);
}
return ret;
}
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
struct epoll_event rev[10];
// 1 create set
int epfd = epoll_create(10);
if(-1 == epfd)
{
perror("epoll_create");
exit(1);
}
// 2 add fd
add_fd(epfd,listfd);
while(1)
{
int ep_ret = epoll_wait(epfd,rev,10,-1);
int i = 0;
for(i=0;i<ep_ret;i++)
{
if(rev[i].data.fd == listfd)
{
//通信套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
continue;;
}
add_fd(epfd,conn);
}
else
{
int conn = rev[i].data.fd;
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{// 0 对方断开连接 -1 错误
printf("cli offline\n");
del_fd(epfd,conn);
close(conn);
break;
}
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
}
}
close(listfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int i = 5;
while(i)
{
char buf[512]="hello,this is tcp test";
send(sockfd,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("buf :%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
epoll 解决了select和poll的几个性能上的缺陷:①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;②监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;③使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发和边沿触发。