多路复用IO模型之select

多路复用I/O

多路I/O复用表示支持多个任务同时对某一进程的I/O进程操作,普通的read/write只能实现同一时间操作一个,无法实现网络通信的并发操作。那么多路复用I/O分为三种机制:select/poll/epoll

多进程多线程的socket模型具有明显缺陷

1.占用内存多 2.进程(线程)切换时间多。3.进程(线程)之间同步麻烦

多路复用的解决理念:

在主控线程中将需要监控的文件描述符保存到文件描述符集中,该文件描述符集为一个位图,我们知道文件描述符正常情况下总是累加上去的,也是一个整数,因此这个整数巧好可以表示该文件描述符在位图的位置(例如位图上的3号位置为1,表示文件描述符等于3有事件发生,否则为空闲。),将服务器和客户端的文件描述符加入到该(文件描述符集)位图中进行监控,若有事件发生则才处理。
在这里插入图片描述

API分析:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout:定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置 timeval,等待固定时间
3.设置 timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; / 秒 /
long tv_usec; / 微妙 */
};

void FD_CLR(int fd, fd_set *set); 把文件描述符集合里fd清0

int FD_ISSET(int fd, fd_set *set); 测试文件描述符集合里fd是否置1

void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1

void FD_ZERO(fd_set *set); 把文件描述符集合里所有位清0

函数2-5均为宏定义函数

通过上述API可知道:
select监控的有三种事件的文件描述符集(读、写、异常)。以及可设置阻塞时间,大大地解决死等的弊端,select无请求后,主控函数将执行后续指令。

但是要注意的是:

select监听的文件描述符集最大支持1024,大小可用宏定义表示FD_SETSIZE( = 1024),解决1024以下客户端时使用select是很合适的,后面会讲解poll,解决1024的限制。
select采用的是轮询模型,数量级在千级还是很适合,但是如果更大会导致每次监控都要从文件描述符集从0到maxfd+1进行遍历,会大大降低服务器响应效率,不应在select上投入更多精力

这是客户端


/*./client serv_ip serv_port */
#include "net.h"

void usage (char *s)
{
	printf ("\n%s serv_ip serv_port", s);
	printf ("\n\t serv_ip: server ip address");
	printf ("\n\t serv_port: server port(>5000)\n\n");
}

int main (int argc, char **argv)
{
	int fd = -1;

	int port = -1;
	struct sockaddr_in sin;

	if (argc != 3) {
		usage (argv[0]);
		exit (1);
	}
	/* 1. 创建socket fd */
	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	port = atoi (argv[2]);
	if (port < 5000) {
		usage (argv[0]);
		exit (1);
	}
	/*2.连接服务器 */

	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons (port);	//网络字节序的端口号
#if 0
	sin.sin_addr.s_addr = inet_addr (SERV_IP_ADDR);
#else
	if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif

	if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");

	int ret = -1;
	fd_set rset;
	int maxfd = -1;
	struct timeval tout;
	char buf[BUFSIZ];

	while (1) {
		FD_ZERO (&rset);		
		FD_SET (0, &rset);		//这个是处理键盘输入的进程
		FD_SET (fd, &rset);
		maxfd = fd;

		tout.tv_sec = 5;
		tout.tv_usec = 0;

		select (maxfd + 1, &rset, NULL, NULL, &tout);
		if (FD_ISSET (0, &rset)) {	//标准键盘上有输入
			//读取键盘输入,发送到网络套接字fd
			bzero (buf, BUFSIZ);
			do {
				ret = read (0, buf, BUFSIZ - 1);
			} while (ret < 0 && EINTR == errno);
			if (ret < 0) {
				perror ("read");
				continue;
			}
			if (!ret)
				continue;		//服务器关闭 

			if (write (fd, buf, strlen (buf)) < 0) {
				perror ("write() to socket");
				continue;
			}

			if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
				printf ("Client is exiting!\n");
				break;
			}
		}

		if (FD_ISSET (fd, &rset)) {	//服务器给发送过来了数据
			//读取套接字数据,处理
			bzero (buf, BUFSIZ);
			do {
				ret = read (fd, buf, BUFSIZ - 1);
			} while (ret < 0 && EINTR == errno);
			if (ret < 0) {
				perror ("read from socket");
				continue;
			}
			if (!ret)
				break;			/* 服务器关闭 */

			//There is a BUG,FIXME!!
			printf ("server said: %s\n", buf);
			if ((strlen(buf) > strlen(SERV_RESP_STR)) 
				&& !strncasecmp (buf+strlen(SERV_RESP_STR), QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
				printf ("Sender Client is exiting!\n");
				break;
			}

		}
	}

	/*4.关闭套接字 */
	close (fd);
}

参考:
https://blog.csdn.net/weixin_42889383/article/details/102367621?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158434937319724835849784%2522%252C%2522scm%2522%253A%252220140713.130056874…%2522%257D&request_id=158434937319724835849784&biz_id=0&utm_source=distribute.pc_search_result.none-task

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值