概念:
多路网络连接复用一个IO线程。
如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事。如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了。
由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多;缺点是编程复杂度高
优点:开销低。
缺点:编程复杂度高。
注意:
下面3种模式,会将监听到的那一位置一,其余位置零。
select模式:
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);
maxfd——需要监视的最大的文件描述符值+1。
rdset——需要检测的可读文件描述符的集合。
wrset——需要检测的可写文件描述符的集合。
exset——需要检测的异常文件描述符的集合。
timeout——超时时间。
struct timeval结构体:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值——=0为超时;-1为出错;>0为获取到数据的数目。
FD_ZERO(fd_set *fdset);
清空文件描述符。
FD_SET(int fd,fd_set *fd_set);
向文件描述符集合中增加一个新的文件描述符。
FD_CLR(int fd,fd_set *fd_set);
在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fd_set);
测试指定的文件描述符是否在该集合中。
FD_SETSIZE————256
select编码流程:
缺点:
1.需要修改传入的参数数组;
2.不能确切指定有数据的socket;
3.只能监视FD_SETSIZE个链接;
4.线程不安全。
代码:
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define max(a,b) ((a)>(b)?(a):(b))
void echo(int connfd){
ssize_t len;
char buf[BUFSIZ];
while((len = read(connfd,buf,BUFSIZ))>0){
write(connfd,buf,len);
}
close(connfd);
}
int main(int argc,char* argv[]){
if(2 > argc || 3 < argc){
printf("usage:%s <#port> [<#backlog>]\n",argv[0]);
return 1;
}
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == listenfd){
perror("listenfd open err");
return 1;
}
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(atoi(argv[1]));
if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
perror("bind err");
return 1;
}
printf("bind ok\n");
int backlog = (argc == 3)?atoi(argv[2]):10;
if(listen(listenfd,backlog)){
perror("listen err");
return 1;
}
printf("listen ok\n");
struct sockaddr_in remote_addr;
socklen_t remote_addr_len = sizeof(remote_addr);
fd_set rset;
FD_ZERO(&rset);
int maxfdp1 = listenfd + 1;
int connfds[FD_SETSIZE-1];// listenfd已经占用一个名额
memset(connfds,-1,FD_SETSIZE-1);
int connfds_cnt = 0;
for(;;){
// 设置监视的描述符
FD_SET(listenfd,&rset);
int i;
for(i=0;i<connfds_cnt;i++){
if(-1 == connfds[i]){
size_t cnt = connfds_cnt-i-1;
if(cnt > 0){
memcpy(connfds+i,connfds+i+1,cnt);
FD_SET(connfds[i],&rset);
maxfdp1 = max(maxfdp1,connfds[i]) + 1 ;
}
connfds[connfds_cnt-1] = -1;
connfds_cnt--;
}else{
FD_SET(connfds[i],&rset);
maxfdp1 = max(maxfdp1,connfds[i]) + 1 ;
}
}
// 阻塞监视
if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){
// 判断是否有新的连接
if(FD_ISSET(listenfd,&rset)){
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
if(-1 == connfd){
//慢系统调用accept中断重启兼容性处理
if(EINTR == errno){
continue;
}
perror("accept err");
return 1;
}
if(connfds_cnt+1 == FD_SETSIZE -1){
perror("over fdset size");
shutdown(connfd,SHUT_RDWR);
}else{
connfds[connfds_cnt] = connfd;
connfds_cnt++;
}
}
// 判断是否有请求数据
int i;
for(i=0;i<connfds_cnt;i++){
if(-1 == connfds[i]){
continue;
}
if(FD_ISSET(connfds[i],&rset)){
ssize_t len;
char buf[BUFSIZ];
if((len = read(connfds[i],buf,BUFSIZ))>0){
write(connfds[i],buf,len);
}else{
close(connfds[i]);
FD_CLR(connfds[i],&rset);
connfds[i] = -1;
}
}
}
}
}
close(listenfd);
}
客户机:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#define max(a,b) ((a)>(b)?(a):(b))
int main(int argc,char* argv[]){
if(3 != argc){
printf("usage:%s <ip> <#port>\n",argv[0]);
return 1;
}
int connfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == connfd){
perror("connfd open err");
return 1;
}
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
remote_addr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
perror("connect err");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
fd_set rset;
FD_ZERO(&rset);
for(;;){
FD_SET(STDIN_FILENO,&rset);
FD_SET(connfd,&rset);
int maxfdp1 = max(STDIN_FILENO,connfd) + 1;
if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){
if(FD_ISSET(connfd,&rset)){
ssize_t len = 0;
bzero(buf,BUFSIZ);
if((len = read(connfd,buf,BUFSIZ))>0){
buf[len] = '\0';
fputs(buf,stdout);
}else{
printf("exit %s\n",argv[0]);
return 0;
}
}
if(FD_ISSET(STDIN_FILENO,&rset)){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ,stdin)){
if(strcmp(buf,"q\n") == 0){
shutdown(connfd,SHUT_WR);
FD_CLR(STDIN_FILENO,&rset);
}else{
write(connfd,buf,strlen(buf));
}
}
}
}
}
}
poll模式:
int poll(struct pollfd *fdarray,unsigned long nfds,int timeout);
fdarray:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fd——文件描述符。
events(监视fd事件):
输入:
POLLRDNORM——普通数据。
POLLRDBAND——优先级带数据。
POLLIN——普通或者优先级带数据。
输出:
POLLWRNORM——普通数据。
POLLWRBAND——优先级带数据。
POLLOUT——普通或者优先级带数据。
revent(fd实际发生的事件):
输入:
POLLRDNORM——普通数据。
POLLRDBAND——优先级带数据。
POLLIN——普通或者优先级带数据。
输出:
POLLWRNORM——普通数据。
POLLWRBAND——优先级带数据。
POLLOUT——普通或者优先级带数据。
出错:POLLERR——发生错误。
POLLHUP——发生挂起。
POLLNVAL——描述符非法。
nfds——数组元素个数。
timeout(等待时间):
INFTIM——永久等待。
0—————立即返回。
>0————等待秒数。
返回值:
0————超时;
-1————出错;
正数———就绪描述符个数。
概念:
普通数据——>正规的TCP数据;所有的UDP数据。
优先级带数据——>TCP带外数据。
优点:
不需要修改传入的参数数组;
可以监视任意个链接——cat /proc/sys/fs/file-max
缺点:
不能确切指定有数据的socket;
线程不安全。
代码:
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <poll.h>
#include <linux/fs.h>
#include <limits.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#ifndef INFTIM
#define INFTIM -1
#endif
void echo(int connfd){
ssize_t len;
char buf[BUFSIZ];
while((len = read(connfd,buf,BUFSIZ))>0){
write(connfd,buf,len);
}
close(connfd);
}
int main(int argc,char* argv[]){
if(2 > argc || 3 < argc){
printf("usage:%s <#port> [<#backlog>]\n",argv[0]);
return 1;
}
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == listenfd){
perror("listenfd open err");
return 1;
}
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(atoi(argv[1]));
if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
perror("bind err");
return 1;
}
printf("bind ok\n");
int backlog = (argc == 3)?atoi(argv[2]):10;
if(listen(listenfd,backlog)){
perror("listen err");
return 1;
}
printf("listen ok\n");
struct sockaddr_in remote_addr;
socklen_t remote_addr_len = sizeof(remote_addr);
struct pollfd pollfds[INR_OPEN_MAX];
int i;
for(i=0;i<INR_OPEN_MAX;i++){
pollfds[i].fd = -1;
}
pollfds[0].fd = listenfd;
pollfds[0].events = POLLRDNORM;
int pollfds_cnt = 1;
for(;;){
if(poll(pollfds,pollfds_cnt,INFTIM) > 0){
if(pollfds[0].revents & POLLRDNORM){
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
if(-1 == connfd){
if(EINTR == errno){
continue;
}
perror("accept err");
return 1;
}
if(pollfds_cnt+1 == INR_OPEN_MAX){
perror("over open size");
shutdown(connfd,SHUT_RDWR);
}else{
int i;
for(i=0;i<INR_OPEN_MAX;i++){
if(pollfds[i].fd == -1){
break;
}
}
pollfds[i].fd = connfd;
pollfds[i].events = POLLRDNORM;
pollfds_cnt++;
}
}
int i;
for(i=1;i<INR_OPEN_MAX;i++){
if(pollfds[i].fd == -1){
continue;
}
if(pollfds[i].events & POLLRDNORM){
ssize_t len;
char buf[BUFSIZ];
if((len = read(pollfds[i].fd,buf,BUFSIZ))>0){
write(pollfds[i].fd,buf,len);
}else{
shutdown(pollfds[i].fd,SHUT_RDWR);
close(pollfds[i].fd);
pollfds[i].fd = -1;
pollfds_cnt--;
}
}
}
}
}
close(listenfd);
}
客户机:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <poll.h>
#ifndef INFTIM
#define INFTIM -1
#endif
int main(int argc,char* argv[]){
if(3 != argc){
printf("usage:%s <ip> <#port>\n",argv[0]);
return 1;
}
int connfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == connfd){
perror("connfd open err");
return 1;
}
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
remote_addr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
perror("connect err");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
struct pollfd pollfds[] = {{STDIN_FILENO,POLLRDNORM},{connfd,POLLRDNORM}};
int pollfds_cnt = sizeof(pollfds)/sizeof(pollfds[0]);
for(;;){
if(poll(pollfds,pollfds_cnt,INFTIM) > 0){
if(pollfds[1].revents & POLLRDNORM){
ssize_t len = 0;
bzero(buf,BUFSIZ);
if((len = read(connfd,buf,BUFSIZ))>0){
buf[len] = '\0';
fputs(buf,stdout);
}else{
printf("exit %s\n",argv[0]);
return 0;
}
}
if(pollfds[0].revents & POLLRDNORM){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ,stdin)){
if(strcmp(buf,"q\n") == 0){
shutdown(connfd,SHUT_WR);
}else{
write(connfd,buf,strlen(buf));
}
}
}
}
}
}
epoll模式:
优点:
能确切指定有数据的socket;
线程安全。
创建:int epoll_create(int size);
size——监听的数目。
返回值——文件描述符;/proc/进程id/fd/
控制:int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epfd——epoll文件描述符。
op——操作:
EPOLL_CTL_ADD——注册
EPOLL_CTL_MOD——修改(modify)
EPOLL_CTL_DEL——删除
fd——关联的文件描述符。
event——指向epoll_event的指针
Epoll events 说明
EPOLLOUT 表示对应的文件描述符可以写
EPOLLPRI 表示对应的文件描述符有紧急的数据可读
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 表示对应的文件描述符设定为edge模式
返回值——0为成功;-1为失败。
轮询I/O事件:int epoll_wait(int epfd,struct epoll_event events,int maxevents,int timeout);
epfd——epoll文件描述符。
epoll_event——用于回传待处理事件的数组。
maxevents——买次能处理的事件数。
timeout——等待I/O事件发生的超时值ms——0为立即返回;-1为永不超时。
返回值——正数为发生事件数;-1为错误。
模式:
ET(edge triggered)模式
LT(level triggered)模式
1. 标示管道读者的文件句柄注册到epoll中;
2. 管道写者向管道中写入2KB的数据;
3. 调用epoll_wait可以获得管道读者为已就绪的文件句柄;
4. 管道读者读取1KB的数据
5. 一次epoll_wait调用完成
如果是ET模式,管道中剩余的1KB被挂起,再次调用epoll_wait,得不到管道读者的文件句柄,除非有新的数据写入管道。如果是LT模式,只要管道中有数据可读,每次调用epoll_wait都会触发。
另一点区别就是设为ET模式的文件句柄必须是非阻塞的。
代码:
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <limits.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <linux/fs.h>
#include <fcntl.h>
#define max(a,b) ((a)>(b)?(a):(b))
#ifndef INFTIM
#define INFTIM -1
#endif
int main(int argc,char* argv[]){
if(2 > argc || 3 < argc){
printf("usage:%s <#port> [<#backlog>]\n",argv[0]);
return 1;
}
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == listenfd){
perror("listenfd open err");
return 1;
}
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(atoi(argv[1]));
int flag = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag))){
perror("setsockopt err");
return 1;
}
if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
perror("bind err");
return 1;
}
printf("bind ok\n");
int backlog = (argc == 3)?atoi(argv[2]):10;
if(listen(listenfd,backlog)){
perror("listen err");
return 1;
}
printf("listen ok\n");
struct sockaddr_in remote_addr;
socklen_t remote_addr_len = sizeof(remote_addr);
int epoll_fd = epoll_create(INR_OPEN_MAX);
struct epoll_event listenfd_event;
//fcntl(listenfd,F_SETFL,O_NONBLOCK);
listenfd_event.data.fd = listenfd;
listenfd_event.events = EPOLLIN;//|EPOLLET;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listenfd,&listenfd_event);
int evts_cnt = 1;
for(;;){
struct epoll_event evts[evts_cnt];
int fd_cnt = epoll_wait(epoll_fd,evts,evts_cnt,-1);
int i;
for(i = 0;i < fd_cnt; i++){
if(evts[i].data.fd == listenfd){
printf("accept connect\n");
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
if(-1 == connfd){
if(EINTR == errno){
continue;
}
perror("accept err");
return 1;
}
if(evts_cnt+1 == INR_OPEN_MAX){
perror("over open size");
close(connfd);
}else{
struct epoll_event connfd_evt;
connfd_evt.data.fd = connfd;
//fcntl(listenfd,F_SETFL,O_NONBLOCK);
connfd_evt.events = EPOLLIN;//|EPOLLET;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connfd,&connfd_evt);
evts_cnt++;
printf("server new connfd:%d\n",connfd);
}
}else{
if(evts[i].events & EPOLLIN){
ssize_t len;
char buf[BUFSIZ];
if((len = read(evts[i].data.fd,buf,BUFSIZ))>0){
write(evts[i].data.fd,buf,len);
}else{
close(evts[i].data.fd);
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,evts[i].data.fd,&evts[i]);
evts_cnt--;
}
}
}
}
}
close(epoll_fd);
close(listenfd);
}
客户机:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/fs.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#ifndef INFTIM
#define INFTIM -1
#endif
int main(int argc,char* argv[]){
if(3 != argc){
printf("usage:%s <ip> <#port>\n",argv[0]);
return 1;
}
int connfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == connfd){
perror("connfd open err");
return 1;
}
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
remote_addr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
perror("connect err");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
int epollfd = epoll_create(INR_OPEN_MAX);
struct epoll_event in_evts[2];
in_evts[0].data.fd = STDIN_FILENO;
in_evts[0].events = EPOLLIN;
in_evts[1].data.fd = connfd;
in_evts[1].events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,STDIN_FILENO,in_evts);
epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,in_evts+1);
int out_evts_cnt = 2;
for(;;){
struct epoll_event out_evts[out_evts_cnt];
int fd_cnt = epoll_wait(epollfd,out_evts,out_evts_cnt,INFTIM);
int i;
for(i=0;i<fd_cnt;i++){
if(out_evts[i].data.fd == STDIN_FILENO && (out_evts[i].events & EPOLLIN)){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ,stdin)){
if(strcmp(buf,"q\n") == 0){
shutdown(connfd,SHUT_WR);
}else{
write(connfd,buf,strlen(buf));
}
}
}else if(out_evts[i].data.fd == connfd && (out_evts[i].events & EPOLLIN)){
ssize_t len = 0;
bzero(buf,BUFSIZ);
if((len = read(connfd,buf,BUFSIZ))>0){
buf[len] = '\0';
fputs(buf,stdout);
}else{
printf("exit %s\n",argv[0]);
return 0;
}
}
}
}
}