网编(11):服务器端的I/O复用

版权声明:转载请声明 https://blog.csdn.net/qq_40732350/article/details/88978584

多进程服务器端的缺点和解决方法

为了构建并发服务器,只要有客户端连接请求就会创建新进程。这的确是实际操作中采用的一种方案,但并非十全十美,因为创建进程时需要付出极大代价。这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。各位应该也感到需要IPC时会提高编程难度。

多进程服务器端模型                                                            I/O复用服务器端模型

         

用select 函数实现并行服务器端

select 函数的功能和调用顺序
使用select函数时可以将多个文件描述符集中到一起统一监视,项目如下。

  • 是否存在套接字接收数据?
  • 无需阻塞传输数据的套接字有哪些?
  • 哪些套接字发生了异常?

select函数的调用方法和顺序

设置文件描述符

FD _ZERO(fd_set * fdset) ;//将fd_set变量的所有位初始化为0 。
FD_SET(int fd, fd_set * fdset) ;//在参数fdset指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set * fdset) ;//从参数fdset指向的变量中清除文件描述符fd 的信息。
FD_ISSET(int fd, fd_set * fdset) ;//若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真” 。

设置检查( 监视) 范图及超时

#incl_ude <sys/select. h>
#include <sys/time.h>
int select(int maxfd, fd_set * readset, fd_set * writeset, 
                      fd_set * exceptset, const struct timeval * timeout);
//成功时返回大千0 的值,失败时返回-1 。

#maxfd 是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。
#readset 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
#writeset 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。
#exceptset 将所有关注”是否发生异常"的文件描述符注册到fd_set型变量,并传递其地址值。
#timeout 调用select 函数后,为防止陷入无限阻塞的状态,传递超时( time-out ) 信息。
#返回值 发生错误时返回-1, 超时返回时返回0。因发生关注的事件返回时,返回大千0的值,该值是发生事件的文件描述符数。
struct timeval
{
	long tv_sec; //seconds
	long tv_usec; //microseconds
}

文伴描述符的变化

文件描述符变化是指监视的文件描述符中发生了相应的监视事件。例如,通过select 的第二个参数传递的集合中存在需要读数据的描述符时,就意味着文件描述符发生变化。

实例代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
	fd_set reads, temps;
	int result, str_len;
	char buf[BUF_SIZE];
	struct timeval timeout;
	FD_ZERO(&reads);
	FD_SET(0, &reads); //0 is standard input(console)
	/*
	timeout. tv_ sec=S;
	timeout . tv_usec=5000;
	*/
	while(1)
	{
		temps=reads;
		timeout.tv_sec=5;
		timeout.tv_usec=0;
		result=select(l, &temps, 0, 0, &timeout);
		if(result==-1)
		{
			puts("select() error! ");
			break;
		}
		else if(result==0)
		{
			puts("Time-out!");
		}
		else
		{
			if(FD_ISSET(0, &temps))
			{
				str_len=read(0, buf, BUF_SIZE);
				buf[str_len]=0;
				printf("message from console : %s", buf);
			}
		}
	}
	return 0;
}

第14 、1 5行看似复杂,实则简单。首先在第1 4行初始化fd_set变量,第1 5行将文件描述符0对应的位设置为1 。换言之,需要监视标准输入的变化。

第24行将准备好的fd_set变量reads 的内容复制到temps 变量,因为之前讲过,调用sel ect函数后,除发生变化的文件描述符对应位外,剩下的所有位将初始化为0 。因此,为了记住初始值,必须经过这种复制过程。这是使用select 函数的通用方法,希望各位牢记。

第18 、19行请观察被注释的代码,这是为了设置sel ect函数的超时而添加的。但不能在此时设置超时。因为调用select 函数后, 结构体timeval 的成员tv_sec 和tv_usec 的值将被替换为超时前剩余时间。因此,调用se lect 函数前,每次都需要初始化timeval结构体变量。

第25 、26行将初始化timeva l结构体的代码插入循环后,每次调用select 函数前都会初始化新值。
第27行调用select 函数。如果有控制台输入数据,则返回大于0 的整数;如果没有输入数据而引发超时,则返回0 。
第39~44行select 函数返回大于0 的值时运行的区域。验证发生变化的文件描述符是否为标准输入。若是, 则从标准输入读取数据并向控制台输出。

实现I/O复用服务器端

实例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void error_handling(char *buf);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	struct timeval timeout;
	fd_set reads, cpy_reads;

	socklen_t adr_sz;
	int fd_max, str_len, fd_num, i;
	char buf[BUF_SIZE];
	if(argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	fd_max=serv_sock;

	while(1)
	{
		cpy_reads=reads;//由于reads可能会发生变化,因此复制并保留原有信息
		timeout.tv_sec=5;
		timeout.tv_usec=5000;

		if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))== -1)
			break;
		if(fd_num==0)
			continue;

		for(i=0; i<fd_max+1; i++)
		{
			if(FD_ISSET(i, &cpy_reads))
			{
				if(i==serv_sock)// connection request!
				{		
					adr_sz=sizeof(clnt_adr);
					clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);
					if(fd_max<clnt_sock)
						fd_max=clnt_sock;
					printf("connected client: %d\n", clnt_sock);

				}else{//read message!
					str_len=read(i, buf, BUF_SIZE);
					if(str_len==0) // close request!
					{
						FD_CLR(i, &reads);
						close(i);
						printf("closed client: %d \n", i);
					}else{
						write(i , buf, str_len); // echo!
					}
				}
			}
		}
	}
	close(serv_sock);
	return 0;
}
void error_handling(char * message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

猜你喜欢

转载自blog.csdn.net/qq_40732350/article/details/88978584