I/O非阻塞函数实践:select

select函数实现主要依托于下列函数

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

需要注意的是select既可监听读端口也可监听写端口以及异常事件,但是每一次调用select之前都必须要要清空端口重新赋值才行。因为当select返回的时候,rset位都将被置0,除了那些有变化的fd位,为了得到每次大循环时fd的变化需要每次重新赋值,这也是导致select不能支持太大量请求的原因。

那么select何时返回呢?我猜测是轮询完所有fd之后返回。不过这个我也不确定,希望有大神能够解释一下。

下面程序为自己了解select时所写。


程序的目的用select函数实现服务器的并发处理,并试验其所支持的最大连接数。

因此,只判断监听套接词是否有事件发生,对于已建立连接的套接词事件并不关心,同时对于后来断开的连接也不关心。以webbench作压力测试,发送1w个请求。注意在此之前应将ulimit -n的值改为10240。

先贴上程序,具体与poll,epoll程序的比较另开帖。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_NU		10000
#define MAX_CONN 	5000
int main(int argc, char const *argv[])
{
	int count = 0;
	fd_set fds;
	int maxfdp;
	int fd;
	int yes = 1; 
	struct sockaddr_in servaddr;
	int array[MAX_NU];
	char buff[200];

	memset(array, '\0', MAX_NU);
	memset(buff, '\0', 200);
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0){
		perror("[Error]Create socket");
		exit(1);
	}
	printf("Socket success!\n");

	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror("[Error]setsockopt");
        exit(1);
    }

	memset(&servaddr, '\0', sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(12345);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
		perror("[Error]Bind socket");
		exit(1);
	}
	printf("Bind success!\n");

	if (listen(fd, MAX_CONN) < 0){
		perror("[Error]Listen socket");
		exit(1);
	}
	printf("Listen success!\n");

	array[0] = fd;
	while(1){
		maxfdp = fd;
		FD_ZERO(&fds);
		int i;
		FD_SET(fd, &fds);
		for (i = 1; i < count+1; ++i){
			//检测已关闭的套接词
			/*ret = read(array[i], &buff , sizeof(buff));
			if (ret <= 0 )
			{
				array[i] = array[count];
				count--;
				i--;
				continue;
			}
			*/
			FD_SET(array[i], &fds);
			if (array[i] > maxfdp)
				maxfdp = array[i];
		}
		//printf("maxfdp=%d\n", maxfdp);
		//printf("Before select\n");

		//为保证每次都等待timeout的时间,需要每次重新赋值
		struct timeval timeout={10,0}; 

		switch(select(maxfdp+1, &fds, NULL, NULL, &timeout)){
		case -1:
			perror("[Error]Select");
			exit(1);
		case 0:
			//printf("Nothing\n");
			break;
		//说明一定有数据过来了,接下来FD_ISSET是要查出是哪个套接词有数据,有可能是请求建立连接,也有可能是发数据过来了。
		default:
			//此处fd仅为监听套接词,如果为真则说明有连接请求
			//printf("We get something!\n");
			if (FD_ISSET(fd, &fds))
			 {
			 	struct sockaddr_in cltaddr;
			 	int accept_fd;
			 	socklen_t length = sizeof(cltaddr);
			 	if((accept_fd = accept(fd, (struct sockaddr*)&cltaddr, &length)) < 0){
			 		perror("[Error]Accept socket");
			 		exit(1);
			 	}
			 	count++;
			 	array[count] = accept_fd;
			 	printf("recv connect ip=%s port=%d\n", inet_ntoa(cltaddr.sin_addr), ntohs(cltaddr.sin_port));
			 	printf("count = %d\n", count);
			 } 
			 //此处不管已连接的套接词是否有数据,所以只判断监听套接词是否有数据变动
	}//end switch
	}//end while


	return 0;
}//end main


测试结果如图所示,到5164时crash,无法再支持更多的并发请求。


其实类似于上面这种测试没有太大意义,还是要落实到具体的实例才能看出他的性能优劣。我用之前写的web服务器改了一下,以select实现并发请求。

用ab测试,ab -c 100 -n 1000 http://107.167.184.223:12345/。大约抗了500个请求,然后就不行了。这个程序写得很不精炼,参考意义不大,主要select逻辑与上面程序类似。


/* Open the login page of renren then check 
if the client have the right to enter */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <ctype.h>          
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h> 
#include <unistd.h>


#define KNOWN_PORT 			12345
#define MAX_QUEUE_LENGTH 	512
#define MAXLINE				1024
#define	MAX_NU 				1000

void doit(int fd);
void serve_home(int fd, char *method);
void serve_add(int fd, char *para, char uri[MAXLINE]);
void select_srv(int fd);
void setnoneblocking(int fd);


int main(int argc, char const *argv[])
{
	int sockfd;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	//设置socket的地址信息,地址及端口号
	bzero(&server_addr, sizeof(server_addr));

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(KNOWN_PORT);
	server_addr.sin_addr.s_addr = htons(INADDR_ANY);
	

	//建立socket,作为所设网络接口,并开始监听网络
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		printf("Creat socket error");
		exit(1);
	}
	setnoneblocking(sockfd);
	if(bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr))){
		printf("Server bind port %d failed\n", KNOWN_PORT);
		exit(1);
	}
	if (listen(sockfd, MAX_QUEUE_LENGTH))
	{
		printf("Server listen port %d failed\n", KNOWN_PORT);
		exit(1);
	}
	//服务器始终运行
	select_srv(sockfd);

	return 0;
}

void doit(int fd){
	int len, is_get, fp, n_read, n_write;
	char buf[MAXLINE], uri[MAXLINE], method[MAXLINE], version[MAXLINE];
	char *para;
	int i;
	//得到method, uri, version

	//获取请求头第一行包含method, uri, version的内容
	len = recv(fd, buf, MAXLINE, 0);
	for (i = 0; buf[i] == '\n'; i++)	
			buf[i + 1] = '\0';
	if (len < 0)
	{
		printf("Can not get the request!\n");
		exit(0);
	}
	//recv(fd, buf, MAXLINE, 0);
	sscanf(buf, "%s %s %s", method, uri, version);		//此处uri均为"/"
	//request_igrest(fd);									//忽略其余的请求
	is_get = strcmp(method, "GET");
	if (is_get)
	{
		perror("The web server doesn't implement this method!");
		exit(1);
	}
	para = strchr(uri, '?');
	if (!para)
	{
		fp = open("hu.html", O_RDONLY, "0744");
		if (fp < 0)
		{
			printf("Open Erro!\n");
			exit(1);
		}
		while((n_read = read(fp, buf, MAXLINE)) != 0){
			n_write = send(fd, buf, n_read, 0);
			if (n_read != n_write)
			{
				printf("Writing Erro!\n");
				exit(1);
			}
		}
		close(fp);
	}
	else if(!strstr(uri, "cgi"))
		serve_home(fd, para);
	else serve_add(fd, para, uri);
	close(fd);
}

//此函数为用户第一次登录时出现
void serve_home(int fd, char *para){
	int fp1, fp2, result, n_read, n_write, is_right, len;
	char buf[MAXLINE], username[MAXLINE], password[MAXLINE], bufp[MAXLINE], action[MAXLINE];
	char usern[MAXLINE], pwd[MAXLINE];			//用于存储已经注册过的用户名密码
	char clientun[MAXLINE], clientpwd[MAXLINE];

	//从浏览器中获取username和password
	strcpy(bufp, para + 1);
	sscanf(bufp, "%[^&]&%[^&]&%[^&]", username, password, action);

	//从本地txt中获取username和password
	fp1 = open("database.txt", O_RDONLY, "0744");
	len = read(fp1, buf, MAXLINE);
	buf[len] = '\0';
	sscanf(buf, "%s %s", usern, pwd);
	strcpy(clientun, "usern=");
	strcpy(clientpwd, "pwd=");
	strcat(clientun, usern);
	strcat(clientpwd, pwd);
	close(fp1);
	//比较两者是否一样
	if(strcmp(clientun, username)){
		printf("You haven't registered yet!\n");
		exit(0);
	}
	if (strcmp(clientpwd, password))
	{
		printf("Wrong password\n");
		exit(0);
	}
	fp2 = open("home.html", O_RDONLY, "0744");
	while((n_read = read(fp2, buf, MAXLINE)) != 0){
		n_write = send(fd, buf, n_read, 0);
		if (n_read != n_write)
		{
			printf("Writing Erro!\n");
			exit(1);
		}
	}
	close(fp2);
	//关闭已断开连接的套接词
	/*ret = read(array[i], &buff , sizeof(buff));
	if (ret <= 0 )
	{
		array[i] = array[count];
		count--;
		i--;
		continue;
	}
	*/
}


//为请求提供加法cgi
void serve_add(int fd, char *para, char uri[MAXLINE]){
	char cgiargs[MAXLINE];
	char filename[MAXLINE];
	char *emptylist[]= {NULL};
	char *buf;
	//char*envp[]={"PATH=/bin",NULL};
	//printf("%s\n", uri);

	strcpy(cgiargs, para + 1);
	*para = '\0';
	strcpy(filename, ".");
	strcat(filename, uri);
	//printf("filename = %s, cgiargs = %s\n", filename, cgiargs);

 	if (fork() == 0)
 	{
 		setenv("QUERY_STRING", cgiargs, 1);
 		//buf = getenv("QUERY_STRING");
 		//printf("buf = %s\n", buf);
		dup2(fd, STDOUT_FILENO);
 		execl(filename, "add.cgi");
 		perror("execve failed!");
 	}
 	wait(NULL);
}


//使用select处理并发连接
void select_srv(int fd){
	int maxfdp;
	int i;
	fd_set fds;
	int count = 0;
	char array[MAX_NU];

while(1){
	memset(array, '\0', MAX_NU);
	maxfdp = fd;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	for (i = 1; i < count+1; ++i){
		FD_SET(array[i], &fds);
		if (array[i] > maxfdp)
			maxfdp = array[i];
	}
	//printf("maxfdp=%d\n", maxfdp);
	//printf("Before select\n");
	//为保证每次都等待timeout的时间,需要每次重新赋值
	//struct timeval timeout={10,0}; 

	switch(select(maxfdp+1, &fds, NULL, NULL, 0)){
	case -1:
		perror("[Error]Select");
		exit(1);
	case 0:
		//printf("Nothing\n");
		break;
	//说明一定有数据过来了,接下来FD_ISSET是要查出是哪个套接词有数据,有可能是请求建立连接,也有可能是发数据过来了。
	default:
		//此处fd仅为监听套接词,如果为真则说明有连接请求
		//printf("We get something!\n");
		if (FD_ISSET(fd, &fds))
		{
		 	struct sockaddr_in cltaddr;
		 	int accept_fd;
		 	socklen_t length = sizeof(cltaddr);
		 	if((accept_fd = accept(fd, (struct sockaddr*)&cltaddr, &length)) < 0){
		 		perror("[Error]Accept socket");
		 		exit(1);
		 	}
		 	setnoneblocking(accept_fd);
		 	count++;
		 	array[count] = accept_fd;
		 	printf("recv connect ip=%s port=%d\n", inet_ntoa(cltaddr.sin_addr), ntohs(cltaddr.sin_port));
		 	printf("count = %d\n", count);
		 	doit(array[i]);
		} 
		//此处处理已建立连接的套接词
		/*
		for (i = 1; i < count+1; ++i)
		{
			if (FD_ISSET(array[i], &fds)){
				printf("Serve it\n");
				doit(array[i]);
			}
		}*/

	}//end switch
}//end while
}

//将套接词设为no-block
void setnoneblocking(int fd){
	int yes = 1; 
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror("[Error]setsockopt");
        exit(1);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值