select实现IO多路复用

本文介绍了如何使用select函数实现IO多路复用,涉及文件描述符的管理、超时设置以及处理多个套接字的读写和异常事件。通过创建和管理fd_set集合,程序可以在非阻塞模式下监控多个文件描述符的状态变化,从而提高系统效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在了解了IO的五种模型之后,我们来实现一下比较重要的一种——IO多路复用

本文我们以select函数为例来实现IO的多路复用

多路复用模型的回顾:

 select函数的介绍:

select函数

int select(int nfds, fd_set *readfds, fd_set *writefds,  fd_set *exceptfds, struct timeval *timeout)

参数和返回值:
nfds:监视对象文件描述符数量。
readfds:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
writefds: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
exceptfds:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息。
返回值:错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数
 

使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况—— 读写或是异常 

由上图可知,我们在调用select函数之前应当确认需要监视的文件描述符和监视的范围,超时我们一般设置为0.

步骤一:

select可以同时监视多个文件描述符(套接字)。这些文件描述符被放在一个集合中,值为1代表状态位被监视。

fd_set set

 此定义代表定义了一个set数组来存放文件描述符的信息

对set操作:

/*将文件描述符从集合中删除*/

void FD_CLR(int fd, fd_set *set);

/*查看文件描述符是否存在于集合当中,返回的值为文件描述符对应位置的集合的值*/

int  FD_ISSET(int fd, fd_set *set);

/*添加文件描述符,也就是将文件描述符对应位置的值 = 1*/

void FD_SET(int fd, fd_set *set);

/*初始化集合,让集合中所有的值都为0*/

void FD_ZERO(fd_set *set);

调用和结果在程序中体现,下面见程序:

此程序为封装的.h文件。

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
#define Max_Set_Length 1024
void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);

方法实现:

#include "net.h"

void Argment(int argc, char *argv[]){
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}
}
int CreateSocket(char *argv[]){
	/*创建套接字*/
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
		ErrExit("socket");
	/*允许地址快速重用*/
	int flag = 1;
	if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
		perror("setsockopt");
	/*设置通信结构体*/
	Addr_in addr;
	bzero(&addr, sizeof(addr) );
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	/*绑定通信结构体*/
	if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
		ErrExit("bind");
	/*设置套接字为监听模式*/
	if( listen(fd, BACKLOG) )
		ErrExit("listen");
	return fd;
}
int DataHandle(int fd){
	char buf[BUFSIZ] = {};
	Addr_in peeraddr;
	socklen_t peerlen = sizeof(Addr_in);
	getpeername(fd,(Addr *)&peeraddr,&peerlen);
	int ret = recv(fd, buf, BUFSIZ, 0);
	if(ret < 0)
		perror("recv");
	if(ret > 0)
		printf("[%s,%d]data: %s\n",
				inet_ntoa(peeraddr.sin_addr),
				ntohs(peeraddr.sin_port),buf);
	return ret;
}

服务器端:

#include "net.h"

int main(int argc, char *argv[])
{
	int ret,i;
	fd_set set,temset;
	/*检查参数,小于3个 直接退出进程*/
	Argment(argc, argv);
	Addr_in addr;
	socklen_t addrlen = sizeof(Addr_in);
	/*创建已设置监听模式的套接字*/
	int fd = CreateSocket(argv);
	FD_ZERO(&set);
	FD_ZERO(&temset);
	FD_SET(fd,&set);
	/*接收客户端连接,并生成新的文件描述符*/
	while(1){
		temset = set;
		ret = select(Max_Set_Length,&temset,NULL,NULL,NULL);
		if(ret < 0)
			ErrExit("select");
		if(FD_ISSET(fd,&temset)){
			int newfd = accept(fd,(Addr *)&addr , &addrlen);
			if(newfd < 0)
				perror("accept");
			printf("[%s ,%d]has connect\n",
					inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
			FD_SET(newfd,&set);
		}else{
			for(i = fd+1; i < Max_Set_Length;i++){
				/*处理客户端数据*/
				if(FD_ISSET(i,&temset)){
					if((DataHandle(i))<=0){
						getpeername(i,(Addr *)&addr,&addrlen);
						printf("[%s,%d]has exit\n",
								inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
						FD_CLR(i,&set);
					}
				}
			}
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值