IO多路复用之poll模型

IO多路复用之poll模型

前言

由于本文需要一定基础,当然,吃c语言饭的人除外,如果你是java出身的,请先看我上一篇博客select

poll

io从最开始的bio发展到nio,aio,再到优秀的网络框架mina,netty,都是基于io底层模型从最开始的select到poll再到epoll的一步步优化。

最开始的bio式编程,我们需要为每一个客户端连接建立一个进程或者线程去与他交互(可理解为一个学生对应一个家教,来一个学生就要一个家教,然后家教数量是远小于学生数量的,这样会造成服务端资源浪费,压力过大)

select式非阻塞io编程,一个服务器最大可连接1024个客户端,且是轮询的方式判断哪个客户端有发送数据过来。(可理解为一个家教照顾1024个学生,然而学生A提问老师却不知道是哪个学生提问,他知道的只有 有几个学生提问,然后他要去一个一个去问这1024个学生)

poll骨子里仍然和select一样,都是以轮询的方式去判断哪个客户端有发送数据过来,只是最大可连接的客户端数量没有1024上限了,当然仍然受限于一个进程打开的最大数目的socket文件描述符个数(cat /proc/sys/fs/file-max命令查看,这个数值与你虚拟机的配置内存有关系)

下面是poll函数的描述

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);


struct pollfd {
int fd; /* 文件描述符 */
short events; /* 监控的事件 */
short revents; /* 监控事件中满足条件返回的就绪事件 */
//可以看出poll将监控事件与就绪事件分开了,这一点也是比select更好的地方
//select的读集合既是传入参数又是传出参数
//传入时告诉select需要监控的事件,传出时是select返回的事件就绪集
};

nfds 监控数组中有多少文件描述符需要被监控

timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值

实例

服务端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<errno.h>

#define MAXLINE  80
#define SERV_PORT  9999
#define OPEN_MAX  1024
int main(){
	//maxi:最大文件描述符的下标
	//listenfd:服务器建立socket返回的用户监听客户端连接的文件描述符 , 这个fd固定在client数组0号位置 
	//connfd:客户端连接后返回的文件描述符,用户客户端服务器间通信使用,这个fd 需存在 client数组中 
	//sockfd: 从client数组取出来的文件描述符 
	int i,j,maxi,listenfd,connfd,sockfd;
	//就绪事件个数 
	int nready;
	//read函数返回,字节数 
	ssize_t n;
	//buf读写缓冲区,clip:存储客户端地址 
	char buf[MAXLINE],clip[INET_ADDRSTRLEN];
	// 客户端地址长度 
	socklen_t clilen;
	//	struct pollfd {
	//		int fd; /* 文件描述符 */
	//		short events; /* 监控的事件 */
	//		short revents; /* 监控事件中满足条件返回的事件 */
	//	};
	//client数组保存所有的文件描述符,监控的事件,就绪的事件 
	struct pollfd client[OPEN_MAX];
	//客户端地址,服务器地址 
	struct sockaddr_in cliaddr,servaddr;
	//服务器建立socket返回的用户监听客户端连接的文件描述符 , 这个fd固定在client数组0号位置 
	listenfd = socket(AF_INET,SOCK_STREAM,0);
	printf("listenfd:%d\n",listenfd);//listenfd=3 
	//服务器赋ip和port 
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//绑定并监听 
	bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
	listen(listenfd,128);
	printf("wait for connect...\n");
	//0号位置放listenfd 
	client[0].fd  =listenfd;
	//0号位置即listenfd监听的事件是可读事件 
	client[0].events = POLLRDNORM;
	//初始化除0之外所有文件描述符为-1 
	for(i=1;i<OPEN_MAX;++i)
		client[i].fd = -1;
	//最大文件描述符的位置=0,即listenfd的位置 
	maxi = 0;
	//死循环,accept 
	for(;;){
		// poll(struct pollfd *fds, nfds_t nfds, int timeout); 
		//client 数组 ,最大文件描述符+1,等待时间 -1表示阻塞等 
		nready = poll(client,maxi+1,-1);
		//看看0号事件/listenfd有没有就绪可读事件,有的话说明有客户端连接 
		if(client[0].revents&POLLRDNORM){
			clilen = sizeof(cliaddr);
			//返回connfd,与客户端通信 
			connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
			printf("connfd:%d\n",connfd);
            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, clip, sizeof(clip)),
                    ntohs(cliaddr.sin_port));
            //把 connfd放到client数组合适的位置 
			for(i=1;i<OPEN_MAX;++i){
				if(client[i].fd<0){
					client[i].fd = connfd;
					break;
				}
			}       
			
			if(i==OPEN_MAX){
				fputs("too many clients\n", stderr);
                exit(1);
			} 
			//刚刚的connfd 设置监听事件为可读事件 
			client[i].events = POLLRDNORM;
			//更新 maxi
			if(i>maxi)
			maxi = i;
			//如果 nready==0,没必要再去查看 客户端有没有发送数据过来,因为肯定没有 
			if(--nready<=0)continue;
		}
		//去接收客户端发送的数据 
		for(i=1;i<=maxi;++i){
			if((sockfd = client[i].fd)<0)continue;
			//如果返回的就绪事件是可读事件或发生错误 
			if(client[i].revents&(POLLRDNORM|POLLERR)){
				 //错误处理 
				if((n=read(sockfd,buf,MAXLINE))<0){
					if(errno == ECONNRESET){
						printf("client[%d] aborted connection\n",i);
						close(sockfd);
						client[i].fd = -1;
					}else{
						fputs("READ ERROR\n", stderr);
                		exit(1);
					}
				}else if(n==0){//客户端主动断开连接 
					printf("client[%d] close connection\n",i);
					close(sockfd);
					client[i].fd = -1;
				}else{//接收到数据转大写回写过去 
					printf("%s\n",buf);
					for(j=0;j<n;j++){
						buf[j] = toupper(buf[j]);
					}
					write(sockfd,buf,n);
				}
				// 如果nready==0 直接跳出循环 
				if(--nready<=0)
				break;
			}
		}
	}
	close(listenfd);
	return 0;
}

客户端

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h> 
#define DEST_PORT 9999//目标地址端口号 
#define DEST_IP "127.32.255.2"/*目标地址IP,这里设为本机,不一定非得是127.0.0.1,只要127开头并且不是127.0.0.0和127.255.255.255即可*/ 
#define MAX_DATA 100//接收到的数据最大程度 
 
int main(){
	int sockfd;
	struct sockaddr_in dest_addr;
	char buf[MAX_DATA];
 
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	
	dest_addr.sin_family=AF_INET;
 	dest_addr.sin_port=htons(DEST_PORT);
	dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
	bzero(&(dest_addr.sin_zero),8);
	connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr));

	printf("connect success");
	while(1){
		char send_buf[512] = "";
		scanf("%s",&send_buf);
		write(sockfd,send_buf,sizeof(send_buf));
		
		read(sockfd,send_buf,sizeof(send_buf));
    	printf("client receive:%s\n",send_buf);
	}

	return 0;
} 

centos执行
gcc -o server.out server.c
gcc -o client.out client.c
得到执行文件server.out,client.out
用xshell 对一个虚拟机开两个item窗口,一个执行./server.out ,另一个执行./client.out,可以开多个客户端通信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值