多路I/O转接之select模型

I/O复用使得程序可以同一时候监听多个文件描写叙述符。这对提高程序的性能至关重要。通常,网络程序同一时候处理或者监听多个socket文件描写叙述符的时候可以考虑使用I/O复用模型。

值得强调的是。I/O复用尽管可以同一时候监听多个文件描写叙述符。但它本身是堵塞的。当有多个文件描写叙述符就绪的时候,假设不採取额外的措施,程序就仅仅能按顺序依次处理当中的每个文件描写叙述符,这使得server程序看起来像串行工作的。

假设要实现并发。仅仅可以使用多进程或者多线程的手段。

select 系统调用的用途是:在一段指定的时间内,监听用户感兴趣的文件描写叙述符上的可读,可写和异常等事件。

值得说明的是:

1.select 能监听的文件描写叙述符个数受限于FD_SETSIZE。一般默觉得1024,单纯改变进程打开的文件符个数并不能改变select监听的文件描写叙述符的个数

2.解决1024一下client时候使用select非常合适(比方局域网中)。可是假设连接client过多,select採用是轮询模型(程序中会有所体现)。这样会大大减少server的响应效率。

相关系统调用函数介绍

#include <sys/select.h> //所需头文件

int select(int nfds, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, struct timeval *timeout);
1.nfds 监控的文件描写叙述符集里最大文件描写叙述符加1,由于此參数会告诉内核检測前多少个文件描写叙述符的状态,之所以加1 由于文件描写叙述符是从0開始编号的
2.readfds 监控有读数据到达文件描写叙述符集合,传入传出參数
3.writefds监控有写数据到达的文件描写叙述符集合,传入传出參数
4.exceptfds 监控异常发生达文件描写叙述符集合,如带外数据到达异常,传入传出參数
timeout:定时堵塞监控时间,3种情况
1.NULL,永远等下去,堵塞在这里
2.设置timeval,等待固定时间
3.设置timeval里时间均为0。检查描写叙述字后马上返回。轮询
struct timeval {
     long tv_sec; /* seconds */
     long tv_usec; /* microseconds */
};

void FD_CLR(int fd, fd_set *set);//将fd_set集中相应的文件描写叙述符清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

select 监听程序:

#include<stdio.h>
#include<string.h>
#include<sys/select.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<errno.h>
int create_listen(int port)
{
	int listen_st,on;
	struct sockaddr_in s_addr;
	listen_st =socket(AF_INET,SOCK_STREAM,0);
	if(listen_st==-1)

	{
		perror("socket error ");
		return -1;
	}
	if(setsockopt(listen_st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))==-1)
	{
		perror("setsockopt error");
		return -1;
	}
	s_addr.sin_port=htons(port);
	s_addr.sin_family=AF_INET;
	s_addr.sin_addr.s_addr=htonl(INADDR_ANY);

	if(bind(listen_st,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in))==-1)
	{
		perror("bind error");
		return -1;
	}
	if (listen(listen_st, 5) == -1) // 设置文件描写叙述符具有监听的功能
    {
        perror("listen error");
        return -1;
    }
    return listen_st;
}

int run_server(int port)
{
	int i,maxi,maxfd,listen_st,conn_st,sockaddr_len;
	int nready,client[FD_SETSIZE];
	char buf[1024];
	struct sockaddr_in c_addr;
	fd_set rset,allset;
	listen_st=create_listen(port);
	if(listen_st==-1)
	{
		return -1;
	}
	for(i=0;i<FD_SETSIZE;i++)
	{
		client[i]=-1;
	}
	FD_ZERO(&allset);
	FD_SET(listen_st, &allset);
	maxfd = listen_st;
	maxi=-1;
	while(1)
	{
		rset=allset;
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);//select 開始监听
		if(nready<0)
		{
			perror("select error");
			break;
		}
		if(FD_ISSET(listen_st,&rset))//检測listen_st 在fd_set有没有被事件到达
		{
			sockaddr_len=sizeof(c_addr);
			conn_st=accept(listen_st,(struct sockaddr *)&c_addr,&sockaddr_len);
			printf("received form %s at port:%d \n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
			for(i=0;i<FD_SETSIZE;i++)
			{
				if(client[i]<0)
				{
					client[i]=conn_st;
					break;
				}
			}
			if(i==FD_SETSIZE)
			{
				printf("too many client \n");
				close(conn_st);
			}else
			{
				FD_SET(conn_st,&allset);
				if(i>maxi) //记录最大下标
				{
					maxi=i;
				}
				if(conn_st>maxfd)//记录最大文件描写叙述符用于select用
				{
					maxfd=conn_st;
				}
			}
			if(--nready==0) continue;
		}

		for(i=0;i<=maxi;i++)
		{
			if((conn_st=client[i])<0)
			{
				continue;
			}

			if(FD_ISSET(conn_st,&rset))
			{
				memset(buf,0,sizeof(buf));
				if(read(conn_st,buf,sizeof(buf))==0)
				{
					printf("close client \n");
					close(conn_st);
					FD_CLR(conn_st,&allset);
					client[i]=-1;
				}
				else
				{
					printf("recv from client:%s \n",buf);
					write(conn_st,buf,strlen(buf));
				}
				if (--nready == 0) break;  //就绪个数减一
			}

		}
		sleep(1);
	}
	close(listen_st);
	return 0;
}

int main(int argc,char *argv[])
{
	if(argc<2)
	{
		printf("usage:%s port \n",argv[0]);
		return 0;
	}
	int port=atoi(argv[1]);
	if(port==0)
	{
		printf("port error \n");
		return 0;
	}
	printf("start server \n");
	run_server(port);
	return 0;
}

client程序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define BUFFSIZE 1024
#define ERRORCODE -1

static void *thread_send(void *arg)
{
	char buf[BUFFSIZE];
	int sd = *(int *) arg;
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		read(STDIN_FILENO, buf, sizeof(buf));
		if (send(sd, buf, strlen(buf), 0) == -1)
		{
			printf("send error:%s \n", strerror(errno));
			break;
		}
	}
	return NULL;
}
static void* thread_recv(void *arg)
{
	char buf[BUFFSIZE];
	int sd = *(int *) arg;
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		int rv = recv(sd, buf, sizeof(buf), 0);
		if (rv <= 0)
		{
			if(rv == 0) //server socket关闭情况
			{
				printf("server have already full !\n");
				exit(0);//退出整个客服端
			}
			printf("recv error:%s \n", strerror(errno));
			break;
		}
		printf("%s", buf);//输出接收到的内容
	}
	return NULL;
}
int run_client(char *ip_str, int port)
{
	int client_sd;
	int con_rv;
	pthread_t thrd1, thrd2;
	struct sockaddr_in client_sockaddr; //定义IP地址结构

	client_sd = socket(AF_INET, SOCK_STREAM, 0);
	if (client_sd == -1)
	{
		printf("socket create error:%s \n", strerror(errno));
		return ERRORCODE;
	}

	memset(&client_sockaddr, 0, sizeof(client_sockaddr));
	client_sockaddr.sin_port = htons(port); //指定一个端口号并将hosts字节型传化成Inet型字节型(大端或或者小端问题)
	client_sockaddr.sin_family = AF_INET;	//设置结构类型为TCP/IP
	client_sockaddr.sin_addr.s_addr = inet_addr(ip_str);
	//将字符串的ip地址转换成int型,客服端要连接的ip地址
	con_rv = connect(client_sd, (struct sockaddr*) &client_sockaddr,
			sizeof(client_sockaddr));
	//struct sockaddr 是非常早曾经定义的 struct sockaddr_in 是后定义的,眼下用的比較多
	//调用connect连接到指定的ip地址和端口号,建立连接后通过socket描写叙述符通信
	if (con_rv == -1)
	{
		printf("connect error:%s \n", strerror(errno));
		return ERRORCODE;
	}
	if (pthread_create(&thrd1, NULL, thread_send, &client_sd) != 0)
	{
		printf("thread error:%s \n", strerror(errno));
		return ERRORCODE;

	}
	if (pthread_create(&thrd2, NULL, thread_recv, &client_sd) != 0)
	{
		printf("thread error:%s \n", strerror(errno));
		return ERRORCODE;
	}
	pthread_join(thrd2, NULL);
	pthread_join(thrd1, NULL);
	close(client_sd);
	return 0;
}
int main(int argc, char *argv[])
{
	if (argc < 3)
	{
		printf("Usage:ip port,example:127.0.0.1 8080 \n");
		return ERRORCODE;
	}
	int port = atoi(argv[2]);
	char *ip_str = argv[1];
	run_client(ip_str,port);
	return 0;
}
时间: 2024-10-18 03:07:38

多路I/O转接之select模型的相关文章

【Nginx】I/O多路转接之select、poll、epoll

从socket中读取数据可以使用如下的代码: while( (n = read(socketfd, buf, BUFSIZE) ) >0) if( write(STDOUT_FILENO, buf, n) = n) { printf(“write error”); exit(1); } 当代码中的socketfd描述符所对应的文件表项是处于阻塞时,它会一直阻塞,直到有数据从网络的另一端发送过来.如果它是一个服务器程序,它要读写大量的socket,那么在某一个socket上的阻塞很明显会影响与其它

I/O多路转接之select

系统提供select函数来实现多路复用输入/输出模型.select系统调用时用来让我们程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变.关于文件句柄,其实就是一个整数,我们最熟悉的文件句柄是0,1,2三个,0是标准输入,1是标准输出,2是标准错误输出. 函数原型: 参数: nfds 输入型参数   readfds,writefds,exceptfds,timeout即为输入型参数又为输出型参数 nfds:最大文件描述符+1: rea

linux网络编程-----&gt;高并发---&gt;epoll多路I/O转接服务器

做网络服务的时候并发服务端程序的编写必不可少.前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定. 常见的linux并发服务器模型: 多进程并发服务器 多线程并发服务器 select多路I/O转接服务器 poll多路I/O转接服务器 epool多路I/O转接服务器. 本次主要讨论poll多路I/转接并发服务器模型: 前几章介绍完了多进程并发服务器,  多线程并发服务器, selete多路I/O转接服务器,  poll多路I/O转接服务器, 本章开始介

服务器select模型

多路转接I/O服务器中的一种:select服务器,该模型的服务器是将文件描述符放入队列中保存并监听,以轮询的机制去监听这些文件描述符,当相对应的文件描述符有读请求.写情况或异常发生时,对应的位将发生变化.select模型需要对所有监听的套接字实行轮询监听处理,当需要监听的套接字过多时,就可能出现响应不及时等问题,从而降低了服务器性能. 下面是服务器的实现(服务器将客户端发送过来的数据全部转换为大写) #include <stdio.h> #include <fcntl.h> #in

linux网络编程-----&gt;高并发---&gt;poll多路I/O转接服务器

做网络服务的时候并发服务端程序的编写必不可少.前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定. 常见的linux并发服务器模型: 多进程并发服务器 多线程并发服务器 select多路I/O转接服务器 poll多路I/O转接服务器 epool多路I/O转接服务器. 本次主要讨论poll多路I/转接并发服务器模型: 前几章介绍完了多进程并发服务器,  多线程并发服务器, selete多路I/O转接服务器,  本章开始介绍poll(linux特有)多路

基于select模型的udp客户端实现超时机制

参考:http://www.cnblogs.com/chenshuyi/p/3539949.html 多路选择I/O — select模型 其思想在于使用一个集合,该集合中包含需要进行读写的fd,通过轮询这个集合,直到有一个fd可读写,才返回.与阻塞I/O不同的是,阻塞I/O仅使用了一次系统调用,就是对fd的读写,如果没有fd处于就绪状态,则进程一直阻塞,而多路选择I/O使用了两次系统调用,第一次是轮询并返回可读写fd数,第二次是对fd进行读写,阻塞只发生在轮询fd的过程. select函数的原

socket编程:多路复用之select模型

系统提供select函数来实现多路复用输入/输出模型. select函数让我们的程序监视多个文件描述符的状态变化.程序会停在select这里等待,直到被监视的文件描述符中有一个或多个发生了状态变化 函数原型如下: 返回值:   成功返回就绪描述符的个数,超过timeout时间且没有任何事件发生返回0,失败返回-1 参数解释: nfds:    被监视的文件描述符中值最大描述符值加1(描述符是从0开始的,描述符0.1.2...nfds-1均将被测试) 下面三个参数readset.writeset和

select模型的原理、优点、缺点

关于I/O多路复用: I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一 个通知.这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不 做纯返回-1和EAGAIN的无用功.写操作类似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现,这些函数都可以同时 监视多个描述符的读写就绪状况,这样,**多个描

Winsock IO模型之select模型

之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int select( int nfds,                                                 // 忽略,仅是为了与Berkeley套接字兼容 fd_set* readfds,                                  // 指向一个套接字集合,