网络IO 多路IO复用 之 select

网络IO 多路IO复用 

select

poll

epoll

 

为什么要用多路io复用呢?

如果不用多路io复用,可以开辟多个线程,或者一个线程循环的去读取每个io的数据,这样的开销是非常的大的,而且会大大的影响系统的性能,因此引入多路io复用

 

select 用于多路io复用,具体是如何实现的?

内部使用fd_set的方式来进行实现,因为做select 只需要标志自己内部的fd 可用还是不可用,则相当于是1个bit位, 0/1的取值。

因此使用fd_set 位图的方式来进行对select的实现

 

select的代码实现流程

1、创建sockfd

socket(AF_INET,SOCK_STREAM,0)

2、初始化addr 属性 struct sockaddr_in (sa_handler ,sa_port,sa_addr.s_addr)

3、bind sockfd

4、listen 设置最大连接数

5、定义select 的集合,fd_set,定义检测集合 rfds,具体使用的集合rsets

6、传入select 参数中,select 的参数,5个,maxfd+1,可读集合,可写集合,异常集合,时间 -- 返回值为 select监听到的连接数 nready

7、判断 sockfd 是否在rset集合里面,如果在,则定义客户端的数据结构, 和定义客户端数据结构的长度,需要传入到accept中

8、开始iaccept,返回clientfd, 把 clientfd 放入 rfds中, 且更新maxfd的大小

9、如果--nready ==0 ,则继续select 阻塞监听,若--nready 大于零, 则是 客户端和服务器进行数据通信

10、数据通信的时候服务器 使用 recv/read 读取数据,放入到buff中,

如果函数返回值是 <0 ,则需要判断,错误号若为 EAGAIN || EWOULDBLOCK -- 被信号打断了,未读取到数据,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd

如果函数返回值是 == 0,说明客户端断开连接了,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd

如果函数返回值是 > 0,说明正常读取到数据,开始对数据进行处理

 

最后 关闭sockfd 套接字 close(sockfd)

 

编码

写一个简单的demo,来感受一下select的使用


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>

#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/poll.h>
#include <unistd.h>



#define BUFFER_LENGTH	1024

int main(int argc,char * argv[])
{
//端口从命令行输入   如 : ./server 8888
	if(argc < 2)
	{
		perror("please input port nunber");
		return -1;
	}
//建立tcp套接字
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}
//初始化属性
	struct sockaddr_in seraddr;
	memset(&seraddr,0,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = INADDR_ANY;
//bind
	int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in));
	if(ret < 0){
		perror("bind");
		return -1;
	}
//设置最大连接数
	ret = listen(sockfd,10);
	if(ret < 0){
		perror("listen");
		return -1;
	}

//select

	fd_set rset,rfds;
	FD_ZERO(&rfds);
	FD_SET(sockfd,&rfds);

	int maxfd = sockfd;

//开始select监听
	while(1){

		rset = rfds;
		int nready = select(maxfd+1,&rset,NULL,NULL,NULL);
		if (nready < 0) {
			printf("select error : %d\n", errno);
			continue;
		}
//开始accept
		if(FD_ISSET(sockfd,&rset)){
			struct sockaddr_in client_addr;
			memset(&client_addr, 0, sizeof(struct sockaddr_in));
			socklen_t client_len = sizeof(client_addr);
			
			int clientfd = accept(sockfd,(struct sockaddr*)&client_addr,&client_len);
			if(clientfd <= 0){
				printf("accept error\n");
				continue;
			}
//打印消息
			char str[BUFFER_LENGTH] = {0};
			printf("recvived from %s at port %d, sockfd:%d, clientfd:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
				ntohs(client_addr.sin_port), sockfd, clientfd);
		
			if (maxfd == FD_SETSIZE) {
				printf("clientfd --- out range\n");
				break;
			}
			
			FD_SET(clientfd,&rfds);
			if (clientfd > maxfd) maxfd = clientfd;

			printf("\nsockfd:%d, maxfd:%d, clientfd:%d\n\n", sockfd, maxfd, clientfd);
			printf("nready == %d\n",nready);
			if (--nready == 0) continue; //说明 当前请求的都是连接,并没有发数据

		}

//客户端开始和服务器进行数据的传输
		for(int i = sockfd+1;i<=maxfd;i++){
			if(FD_ISSET(i,&rset)){
				char buffer[BUFFER_LENGTH] = {0};
				int ret = recv(i, buffer, BUFFER_LENGTH, 0);
				if (ret < 0) {
					if (errno == EAGAIN || errno == EWOULDBLOCK) {
						printf("read all data");
					}
					FD_CLR(i, &rfds);
					close(i);
				} else if (ret == 0) {
					printf(" disconnect %d\n", i);
					FD_CLR(i, &rfds);
					close(i);
					break;
				} else {
					printf("Recv: %s, %d Bytes\n", buffer, ret);
					//将客户端发过的数据,发回去
					send(i, buffer, BUFFER_LENGTH, 0);
				}
				//减去一个客户端的fd
				if (--nready == 0) break;
			}
		}
	}

	close(sockfd);


	return 0;
}




验证和调试

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值