select()函数是实现服用服务器端的一种方法。这里介绍Linux环境下select()函数的用法,Windows下的大同小异。

select函数的功能
select函数可以同时监视多个文件描述符,并且可以监视三种事件。一旦某个文件描述符所指的对象发生了相应事件,就可以进行相应的处理。

监视的三种事件:

(1)是否有对象需要接受数据。(2)是否有对象需要传输数据。(3)是否有对象发生了异常。

select函数的调用过程:

(1)设置文件描述符,指定监视范围,设置超时。

(2)调用select函数。

(3)查看调用结果。

下面一一说明。

设置文件描述符

先介绍fd_set数组变量,该结构是存有0和1的位数组。如果该数组第0位是1,代表文件描述符为0的对象是监视对象。操作该数组的方法和普通数组不一样,一般通过以下宏来对该数组进行操作。

FD_ZERO(fd_set *fdset);   将fd_set所指变量的所有位置0。

FD_SET(int fd,fd_set *fdset);   将fdset所指变量的第fd位置1,此时fd文件描述符所指的对象成为监视对象。

FD_CLR(int fd,fd_set *fdset);   将fdset所指变量的第fd位置0。

FD_ISSET(int fd,fd_set *fdset);   如果fd文件描述符所指对象是监视对象(即第fd位为1),则返回true。

select函数参数
int select(int maxfd,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

参数含义:

maxfd:监视对象文件描述符的对象,一般就是所监视的最大文件描述符+1。

readset:readset中为1的文件描述符,将监视它们是否需要接受数据,即上述的事件(1)。

writeset:writeset中为1的文件描述符,将监视它们是否需要传输数据,即上述的事件(2)。

exceptset:同理。

timeout:最大等待时间。

返回值:发生错误时返回-1,超时时返回0。如果发生监视的事件,返回相应的文件描述符。

timeout的结构如下:

struct timeval
{
     long tv_sec;   //秒
     long tv_usec;  //微秒
}
查看结果

select函数返回正整数时,参数中三个fdset地址所指的变量中为位全部置0,除了发生事件的文件描述符。因此,可以通过调用FD_ISSET得知是哪个文件描述符发生了事件。

一个小例子
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<sys/time.h>
#include<sys/select.h>
#include<unistd.h>
using namespace std;
const int BUF_SIZE=30;

int main()
{
	fd_set reads;
	char buf[BUF_SIZE];
	timeval timeout;

	FD_ZERO(&reads);
	FD_SET(0,&reads); 		//0是指控制台输入

	while(1)
	{
		fd_set tmp(reads); 	//每次调用完select,除了发生事件的文件描述符为1,其他全部置0
					//因此每次循环都要复制一遍
		timeout.tv_sec=5; 	//设置超时时间为5秒
		timeout.tv_usec=0;

		int result(select(1,&tmp,0,0,&timeout)); 	//调用select

		if(result==-1)
		{
			cout<<"select() error!\n";
			break;
		}
		else if(result==0) cout<<"Time-out!\n";
		else 
		{
			if(FD_ISSET(0,&tmp)) 	//判断是否是文件描述符0发生事件,即是否需要读数据
			{
				int len(read(0,buf,BUF_SIZE));
				buf[len]=0;
				cout<<"Message from console:"<<buf<<endl;
			}
		}
	}

	return 0;
}

利用select实现复用服务器端

sever.cpp

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<sys/select.h>
using namespace std;
const int BUF_SIZE=30;

void errorHandling(char *message);

int main()
{
	int serverSock,clientSock,fdMax,port;
	sockaddr_in serverAddr,clientAddr;
	fd_set reads;
	socklen_t addrSize;
	char buf[BUF_SIZE];

	cout<<"Input port:";
	cin>>port;

	serverSock=socket(PF_INET,SOCK_STREAM,0);
	memset(&serverAddr,0,sizeof(serverAddr));
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	serverAddr.sin_port=htons(port);

	if(bind(serverSock,(sockaddr *)&serverAddr,sizeof(serverAddr))==-1)
		errorHandling("bind() error!");
	if(listen(serverSock,5)==-1) errorHandling("listen() error!");

	FD_ZERO(&reads);
	FD_SET(serverSock,&reads); 	//设置serverSock为监视对象,监视是否需要接受数据
	fdMax=serverSock;

	while(1)
	{
		fd_set copy(reads);
		timeval timeout;
		timeout.tv_sec=5;
		timeout.tv_usec=0;
		int flag(select(fdMax+1,&copy,0,0,&timeout));

		if(flag==-1) errorHandling("select() error!");
		else if(flag==0) continue;

		for(int i=0;i<=fdMax;i++) 		//循环遍历,看哪个文件描述符发生了事件
		{
			if(FD_ISSET(i,&copy))
			{
				if(i==serverSock) 	//serverSock需要接受数据,说明有新的客户端请求连接
				{
					addrSize=sizeof(clientAddr);
					clientSock=accept(serverSock,(sockaddr *)&clientAddr,&addrSize);

					FD_SET(clientSock,&reads); 	//将新的套接字纳入监视对象中
					fdMax=max(fdMax,clientSock); 	//更新fdMax
					cout<<"connected client:"<<clientSock<<endl;
				}
				else 	//如果不是serverSock,那么就肯定是用来跟客户端通信的套接字发生了事件,即上面的clientSock,对于不同的客户端,通过accept得到的clientSock是不同的
				{
					int len(read(i,buf,BUF_SIZE));
					if(len==0)
					{
						FD_CLR(i,&reads);
						close(i);
						cout<<"closed client:"<<i<<endl;
					}
					else write(i,buf,len);
				}
			}
		}
	}
	
	close(serverSock);

	return 0;
}

void errorHandling(char *message)
{
	cerr<<message<<endl;
	exit(1);
}

client.cpp

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;
const int BUF_SIZE=1024;
const int MAXN=30;

void errorHandling(char *message);

int main()
{
	int sock,len,port;
	char message[BUF_SIZE],ip[MAXN];
	sockaddr_in serverAddr;

	cout<<"Input ip:";
	cin>>ip;
	cout<<"Input port:";
	cin>>port;

	sock=socket(PF_INET,SOCK_STREAM,0);
	if(sock==-1) errorHandling("socket() error!");

	memset(&serverAddr,0,sizeof(serverAddr));
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_addr.s_addr=inet_addr(ip);
	serverAddr.sin_port=htons(port);

	if(connect(sock,(sockaddr*)&serverAddr,sizeof(serverAddr))==-1)
		errorHandling("connect() error!");
	else cout<<"Connected....\n";

	while(1)
	{
		cout<<"Input message(Q to quit):\n";
		cin>>message;

		if(!strcmp(message,"q")||!strcmp(message,"Q"))
			break;

		write(sock,message,strlen(message));
		len=read(sock,message,BUF_SIZE-1);
		message[len]=0;
		cout<<"Message from server:"<<message<<endl;
	}
	
	close(sock);

	return 0;
}

void errorHandling(char *message)
{
	cerr<<message<<endl;
	exit(1);
}

运行截图:


Logo

更多推荐