• I/O复用模型之select学习


    linux下的I/O模型可以分为5种:

    1、阻塞式I/O模型

    2、非阻塞式I/O模型

    3、I/O复用模型

    4、信号驱动I/O模型

    5、异步I/O模型


    简单解释:

    阻塞和非阻塞:就是说需要做一件事的时候能不能立即得到返回应答,如果不能立即得到返回应答而需要等待,那就是阻塞了,否则就是非阻塞。

    同步和异步:就是说每次只能昨晚一件事才能做另外一件事,那就是同步,反之,如果你能够同时做几件事,并且不一定非要一件事做完了再去做另外一件事,这就是异步。


    下面具体说一下上面提到的5种I/O模型:

    阻塞I/O就是指那种recv,read之类的,一直需要等,等到有了拷贝数据才返回。

    非阻塞I/O就是不用等,立即返回,只要设置描述符为非阻塞就可以了,但是要进程自己检查是否可读。

    I/O复用其实也是阻塞的,只是可以用来等很多描述符。

    信号驱动采用信号机制等待。

    异步I/O就不用等了,当他告知你的时候,已经可以返回了,数据都拷贝好了。

    其中前4种I/O模型都是同步模型,只有最后一种才是异步模型。


    I/O复用模型详细介绍:

    1、select系统调用:

    select系统调用是用来让我们的程序监视多个文件描述符的状态变化,程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态变化。

    select()机制中提供了fd_set的数据结构,实际上是long类型的数组,每一个数组元素都能与一打开的文件句柄建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据I/O状态更改fd_set的内容,由此来通知执行了select()的进程那哪些socket或者文件可读可写。


    select函数原型:

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


    函数参数说明:

    1、nfds:select监视的文件句柄数,视进程中的打开文件数而定,一般设为要监视各文件中最大的文件描述符加1。

    2、readfds:这个文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回时,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符。

    3、writefds:这个文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回时,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符。

    4、exceptfds:这个文件集将监视文件集中的任何文件是否发生了错误,当select函数返回的时候,exceptfds将清除其中的其他文件描述符,只留下带有标记的OOB数据的文件描述符。

    5、timeout:本次select()的超时结束时间,它可以使select处于三种状态。

    (1)若将NULL以形参传入,即不传时间结构体,就是讲select置于阻塞状态,一定等到监视描述文件符集合中的某个文件描述符发生变化为止。

    (2)若将时间设置为0秒0毫秒,就变成了一个纯粹的非阻塞函数,不过文件描述符是否发生变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值。

    (3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超过时间之内有事件到来就返回,否则在超时之后不管怎样一定返回,返回值同上所述。

    其中timeval结构体如下:

    struct timeval { long tv_sec; long tv_usec; } ;其中tv_sec是秒,tv_usec是微秒。


    函数返回值:

    正值:表示监听的文件集中有文件描述符符合要求。

    零值:表示select监视超时。

    负值:表示发生了错误,错误值有errno指定。


    宏操作:

    FD_ZERO(fd_set * set):用来清除描述词组set的全部位。

    FD_SET(int fd, fd_set * set):用来设置描述词组set中的相关fd的位。

    FD_ISSET(int fd, fd_set *set):检测集合中的某一个描述符是否准备就绪了。

    FD_CLR(int fd, fd_set *set):用来清除描述词组set中相关fd的位。


    需要注意的事项:

    (1)对于可写性的检查,最好放在需要写数据的时候进行检查,如果和可读性放在同一个地方进行检查, 那么select很可能每次都会因为可写性检查成功而返回。

    (2)select()调用会清空传递给它的集合参数中的内容,也就是会清空readfds, writefds, exceptfds这三个指针参数所指定的描述符集合。因此,在每次调用select()之前,必须重新初始化并把需要监视的描述符填写到相应的描述符集合中。select()调用也会清空timeout指针所指向的struct timeval结构,所以在每次调用之前select()之前也要重新填充timeout指针所指向的struct timeval结构。


    一个简单的select调用例子:

    服务器端:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    
    #define MYPORT 1234 //连接时使用的端口
    #define MAXCLINE 7 //连接队列中的个数
    #define BUF_SIZE 200
    
    int fd[MAXCLINE]; //连接的fd
    
    int conn_amount; //当前的连接数
    
    
    void showclient()
    {
    	printf("client amount: %d
    ", conn_amount);
    	for (int i = 0; i < MAXCLINE; ++i) {
    		printf("[%d]:%d ", i, fd[i]);
    	}
    	puts("");
    }
    
    
    int main()
    {
    	int sock_fd, new_fd; //监听套接字, 连接套接字
    	struct sockaddr_in server_addr; //服务器的地址信息
    	struct sockaddr_in client_addr; //客户端的地址信息
    
    	socklen_t sin_size;
    	
    	int yes = 1;
    	char buf[BUF_SIZE];
    	
    	//建立sock_fd套接字
    	if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    		perror("can not create socket");
    		exit(1);
    	}
    	
    	//设置与sock_fd套接字关联的选项
    	//SOL_SOCKET表示普通的套接字, SO_REUSEADDR表示允许重用本地地址和端口
    	if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
    		perror("setsockopt error");
    		exit(1);
    	}
    
    	server_addr.sin_family = AF_INET; //主机字节序
    	server_addr.sin_port = htons(MYPORT); 
    	server_addr.sin_addr.s_addr = INADDR_ANY; //通配IP
    
    	memset(server_addr.sin_zero, '', sizeof(server_addr.sin_zero));
    
    	if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    		perror("bind error");
    		exit(1);
    	}
    
    	if (listen(sock_fd, MAXCLINE) == -1) {
    		perror("listen error");
    		exit(1);
    	}
    
    	printf("listen port %d
    ", MYPORT);
    
    	fd_set fdsr; //定义文件描述符集
    
    	int maxsock = sock_fd + 1;
    	struct timeval tv;
    
    	conn_amount = 0;
    	sin_size = sizeof(client_addr);
    
    	while (true) {
    
    		//初始化文件描述符集合
    		FD_ZERO(&fdsr); //清除描述符集
    		FD_SET(sock_fd, &fdsr); //把sock_fd加入描述符集
    
    		//设定超时时间
    		tv.tv_sec = 40;
    		tv.tv_usec = 0;
    
    		//添加活动连接
    		for (int i = 0; i < MAXCLINE; ++i) {
    			if (fd[i] != 0) {
    				FD_SET(fd[i], &fdsr);
    			}
    		}
    
    		//如果文件描述符中有连接请求,会做出相应的处理,实现I/O复用,多用户的连接通讯
    		int ret = select(maxsock, &fdsr, NULL, NULL, &tv);
    		
    		//没有找到有效的连接,失败
    		if (ret < 0) {
    			perror("select error");
    			break;
    		} else if (ret == 0) {  //指定的时间到了,超时
    			puts("timeout");
    			continue;
    		} 
    
    		//循环判断有效的连接是否有数据到达
    		int cnt = 0;
    		for (int i = 0; i < conn_amount; ++i) {
    			if (FD_ISSET(fd[i], &fdsr)) {
    				ret = recv(fd[i], buf, sizeof(buf), 0);
    				//客户端连接关闭,清除文件描述符集中的相应的位
    				if (ret <= 0) {
    					printf("client [%d] close
    ", i);
    					close(fd[i]);
    					FD_CLR(fd[i], &fdsr);
    					fd[i] = 0;
    					++cnt;
    				} else {
    					//有相应的数据发送过来
    				 	if (ret < BUF_SIZE) {
    						memset(&buf[ret], '', 1);
    					}
    					printf("client [%d] send:%s
    ", i, buf);
    				} 
    			}
    		}
    		
    		conn_amount -= cnt;
    
    
    		if (FD_ISSET(sock_fd, &fdsr)) {
    			new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size); //每次返回的是一个新文件描述符
    			if (new_fd <= 0) {
    				perror("accept error");
    				continue;  
    			}
    			
    			//添加新的fd到数组中,判断有效的连接数是否小于最大的连接数,
    			//如果小于的话,就把新的连接套接字加入集合
    			if (conn_amount < MAXCLINE) {
    				for (int i  = 0; i < MAXCLINE; ++i) {
    					if (fd[i ] == 0) {
    						fd[i]  = new_fd;
    						break;
    					}
    				}
    
    				++conn_amount; //连接数加1
    				printf("new connection client [%d]%s:%d
    ", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    				if (new_fd > maxsock - 1) {
    					maxsock = new_fd + 1;
    				}
    			} else {
    				puts("max connections arrive, exit...");
    				send(new_fd, "bye", 4, 0);
    				close(new_fd);
    				continue; 
    			}
    		}
    
    		showclient();
    
    	}
    
    	for (int i = 0; i < MAXCLINE; ++i) {
    		if (fd[i] != 0) {
    			fd[i] = 0;
    			close(fd[i]);
    		}
    	}
    
    	return 0;
    }
    
    

    客户端:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <netdb.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    #define SERVPORT 1234
    
    int main()
    {
    	int sock;
    	struct sockaddr_in sa;
    	char buffer[1024];
    
    	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if (sock == -1) {
    		perror("can not create socket");
    		exit(1);
    	}
    	
    	memset(&sa, 0, sizeof(struct sockaddr_in));
    	
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(SERVPORT);
    	sa.sin_addr.s_addr = htonl(0x7f000001);
    
    	if (connect(sock, (struct sockaddr *)&sa, sizeof(struct sockaddr)) == -1) {
    		perror("connect error");
    		exit(1);
    	}
    	freopen("2_select_client", "r", stdin);
    	while (gets(buffer)) {
    		int sendbytes = write(sock, buffer, 100);
    		if (sendbytes == -1) {
    			perror("send error");
    			exit(1);
    		}
    	}
    	fclose(stdin);
    	close(sock);
    	return 0;
    }


  • 相关阅读:
    中文乱码—Servlet—SpringMVC
    (转+整理)C# BinaryFormatter进行序列化与反序列化
    (转)C# 之泛型详解
    (转)C#中base关键字的几种用法
    (转)C# Where关键词的用法
    (转)c# 断言类
    (转+整理)C#中动态执行代码
    (转)c# control.Invoke control.BeginInvoke
    (转)c# String与StringBuilder
    (转)winform之ListView
  • 原文地址:https://www.cnblogs.com/wally/p/4477060.html
Copyright © 2020-2023  润新知