select函数总结

select函数作用:可以实现I/O复用

该函数允许进程指示内核等待对个时间中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定时间后才唤醒它


select不仅限于套接字任何描述字都可使用select

原型:

#include<sys/select.h>
#inclde<sys/time.h>
int select(
            int maxfdp1,
            fd_set* readfds,
            fd_set* writefds,
            fd_set* exceptfds,
            const struct timeval* timeout);
                                 返回:就绪描述字的正数目,0----------超时,-1-----------出错

参数:
timeout指定内核等待任意就绪的最长时间

三种可能:

  • NULL空指针,表示永远等待下去
  • 设置timeout中的值,等待一段固定时间
  • tiemout中的定时器值设置为0,根本不等待直接返回。

前两种情况会出现在阻塞期间被捕获的信号中断,并从信号处理函数返回,有的系统支持中断重启select有的不支持中断重启select

参数二:
中间三个参数,fd_set指定要让内核此时读、写和异常条件的描述字

通过描述字集为这三个参数指定一个或多个描述字

细节使用方法:

void FD_ZERO(fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

void FD_ISSET(int fd, fd_set *fdset);

如果对某个参数不感兴趣可以置其值为NULL指针


参数三:
maxfdp1指定待测试的描述字个数,其值时待测的最大描述字加1;

在头文件<sys/select.h>中定义了FD_SETSIZE常值,是数据类型fd_set中描述字的总数,其值一般为1024


select的作用:

在客户端的影响和使用
在客户端:使用select使阻塞于select调用,可以同时等待标准输入刻度,或套接口可读。

客户套接口上的三个条件处理:
1.如果对端tcp发送数据,那么该套接口变成可读,并且read返回一个大于0的值(即读入字节数)

2.如果对端tcp发送一个FIN(对端进程终止),那么该套接口变成可读,并且read返回0(EOF)

3.如果对端tcp发送一个RST(对端主机崩溃并重新启动),那么该套接口变成可读,并且read返回一个-1,而errno中含有确切的错误码


#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1;
	fd_set		rset;
	char		sendline[MAXLINE], recvline[MAXLINE];

	FD_ZERO(&rset);
	for ( ; ; ) {
		FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);    //select阻塞于rset描述字集中,分别为fd文件描述字和socket套接口描述字

		if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */         
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		/* all done */        //Fgets失败后直接返回,造成main函数直接返回,则进程终止,会带来问题1见下面
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

问题1:

如果是在一个满负荷的全双工管道上,由于分组从管道的一端发送到另一端存在延迟,

已连续的方式发送数据直到充满管道

理想条件下由于延迟应答数据应该在RTT时间后才会被收到

而对于上述函数当客户端发送完数据后,Fgets()返回一个NULL,则函数直接返回到main,而在main中直接调用exit终止了客户端进程,然此时,服务器的应答数据还在管道中没有到达客户端,这将造成接收的出错。

解决此问题的方式是:
使用shutdown,使得在函数从fgets结束后,不直接返回到main函数,而是关闭发送端口,等待服务端应答消息至套接口读为NULL后才返回main

改进代码

#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, stdineof;
	fd_set		rset;
	char		buf[MAXLINE];
	int		n;

	stdineof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);

/*以下使用一个标志位stdineof,标准read读文件fd到buf,完成后返回NULL,并置stdineof为1,然后关闭socket的写这一半(TCP套接口称之为半关闭),
                                       然后继续执行直到读套接口也返回NULL,则返回main后,客户端进程关闭,套接口关闭*/    
                
                     if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */
			if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
				if (stdineof == 1)
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");
			}

			Write(fileno(stdout), buf, n);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
				stdineof = 1;
				Shutdown(sockfd, SHUT_WR);	/* send FIN */
				FD_CLR(fileno(fp), &rset);
				continue;
			}

			Writen(sockfd, buf, n);
		}
	}
}


在服务器端的使用

通过使用select可以实现是服务器处理任意数目的客户的单进程程序,而不是为每个客户派生一个子进程

此情况下服务器值描述一个读描述字集,分别为:标准输入、标准输出和标准错误,及监听描述字

改进后的代码

/* include fig01 */
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					i, maxi, maxfd, listenfd, connfd, sockfd;
	int					nready, client[FD_SETSIZE];
	ssize_t				n;
	fd_set				rset, allset;
	char				buf[MAXLINE];
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	maxfd = listenfd;			/* initialize */    //设置待测描述字个数
	maxi = -1;					/* index into client[] array */
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* -1 indicates available entry */
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
/* end fig01 */

/* include fig02 */
	for ( ; ; ) {
		rset = allset;		/* structure assignment */
		nready = Select(maxfd+1, &rset, NULL, NULL, NULL);//阻塞于select,下包括事件为:监听描述字时间和链接描述字时间,细分为:新客户的连接建立
                                                                   //链接客户端,已连接客户发送来数据,FIN,RST等数据

                   if (FD_ISSET(listenfd, &rset)) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s, port %d\n",
					Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
					ntohs(cliaddr.sin_port));
#endif

			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd;	/* save descriptor */
					break;
				}
			if (i == FD_SETSIZE)
				err_quit("too many clients");

			FD_SET(connfd, &allset);	/* add new descriptor to set */
			if (connfd > maxfd)
				maxfd = connfd;			/* for select */
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

			if (--nready <= 0)
				continue;				/* no more readable descriptors */
		}

		for (i = 0; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {          //这里的read处理可能造成拒绝服务攻击,客户发送一个不含换行符的数据后
                                                                                       //sleep,则服务器阻塞于read等待用户数据
                                                                                      //解决方法:1.使用非阻塞I/O;2.让客户由单独的控制线程提供服务;
                                                                                       //3.对I/O操作设置timeout       
                              /*4connection closed by client */
					Close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else
					Writen(sockfd, buf, n);

				if (--nready <= 0)
					break;				/* no more readable descriptors */
			}
		}
	}
}
/* end fig02 */



你可能感兴趣的:(select函数总结)