2.1.1 Select 实现

一、多路复用IO

IO multiplexing 这个词可能有点陌生,但是提到 select/epoll,大概就都能明白了。有些地方
也称这种 IO 方式为事件驱动 IO(event driven IO)。我们都知道,select/epoll 的好处就在于单
个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select/epoll 这个 function
会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流
程如图:
在这里插入图片描述
当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视”所
有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这
个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

二、Select 接口介绍

(1) fd_set 结构体

fd_set是一种数据类型,在select函数中包含了3个参数,就是这个fd_set类型,fd_set也是理解select模型的关键.
fd_set 类型可以简单的理解为按 bit 位标记句柄的队列,例如要在某 fd_set 中标记一个值为 16 的句柄,则该 fd_set 的第 16 个 bit 位被标记为 1

typedef struct{
    long int fds_bits[32];
}fd_set;

实际上 fd_set 就是一个long int类型的数组。因为每一位可以代表一个文件描述符。所以 fd_set 最多表示1024个文件描述符

(2) 四个操作fd_set 的宏

(a) FD_ZERO

FD_ZERO清除文件描述符集fdset中的所有位(既把所有位都设置为0)

void FD_ZERO(fd_set *fdset);
FD_ZERO(&clientSet);

(b) FD_SET

FD_SET设置文件描述符集fdset中对应于文件描述符fd的位(设置为1)

void FD_SET(int fd, fd_set *fdset);

© FD_CLR

FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为0)

void FD_CLR(int fd, fd_set *fdset);

(d) FD_ISSET

FD_ISSET来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置

void FD_ISSET(int fd, fd_set *fdset);

(3) select函数

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout)
  • int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1

  • fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

  • fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

  • fd_set *errorfds 用来监视文件错误异常文件。

  • struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

  • 函数返回

  • (1) 当监视的相应的文件描述符集中满足条件时,读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0 的数。

  • (2) 当没有满足条件的文件描述符,且设置的timeval 监控时间超时时,select函数会返回一个为0的值。

  • (3) 当select返回负值时,发生错误。

三、Select 服务端程序实现

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <sys/poll.h>
#include <sys/epoll.h>


#include <pthread.h>


#define MAXLNE	4096


int main(int argc, char **argv){

	int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
	
 	//create socket
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
	
 	//bind
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
	
 	//listen
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
	

	fd_set rfds,rset,wfds,wset;
	FD_ZERO(&rfds);
	FD_SET(listenfd,&rfds);
	FD_ZERO(&wfds);
	int max_fd = listenfd;

	while(1){
		// Select
		
		rset = rfds;
		wset = wfds;
		
		int nready = select(max_fd+1, &rset, &wset, NULL, NULL);
		// accept
		if(FD_ISSET(listenfd,&rset)){
			struct sockaddr_in client;
			socklen_t len = sizeof(client);
			if((connfd = accept(listenfd,(struct sockaddr *)&client,&len)) == -1){
				printf("accept error: %s(error: %d\n)",strerror(errno),errno);
				return 0;
			}
			// 对connfd 操作
			FD_SET(connfd,&rfds);

			if(connfd > max_fd)	max_fd = connfd;

			if(--nready == 0) continue;
				
		}
		int i = 0;
		for(i = listenfd+1; i<=max_fd; i++){
			if(FD_ISSET(i,&rset)){
				n = recv(i,buff,MAXLNE,0);
				if(n>0){
					buff[n] = '\0';
					printf("recv msg from client: %s\n", buff);

					FD_SET(i, &wfds);
					
				}else if (n == 0){
					FD_CLR(i, &rfds);
					close(i);
				}
				if (--nready == 0) break;
			}else if(FD_ISSET(i,&wset)){
				send(i,buff,n,0);
				FD_SET(i,&rfds);
			}
		}
		
		
	}
	
	close(listenfd);
	return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值