非阻塞式I/O

时间:2020-12-07 22:49:39

套接字的默认状态是阻塞的。阻塞的套接字调用可分为以下四类:

1.输入操作:包括read,readv,recv,recvfrom和recvmsg共5个函数。

2.输出操作:包括write,writev,send,sendto和sendmsg共5个函数。

3.接收外来连接,即accept函数。调用accept函数时尚无新的连接到达,调用进程将进入睡眠。

4.发起外出连接,即connect函数。connect函数引起三路握手过程,要一直等到客户收到对于自己的SYN的ACK为止才返回。

 

非阻塞读和写:str_cli函数

两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出的数据。

非阻塞式I/O

 

 

 

str_cli函数: 

#include "unp.h"

void str_cli(FILE *fp, int sockfd)
{
	int maxfdp1, val, stdineof;
	ssize_t n, nwritten;
	fd_set rset, wset;
	char to[MAXLINE], fr[MAXLINE];
	char *toiptr, *tooptr, *friptr, *froptr;

	/* 把描述符设置为非阻塞 */
	val = fcntl(sockfd, F_GETFL, 0);
	fcntl(sockfd, F_SETFL, val | O_NONBLOCK);

	val = fcntl(STDIN_FILENO, F_GETFL, 0 );
	fcntl(STDIN_FILENO,F_SETFL, val | O_NONBLOCK);

	val = fcntl(STDOUT_FILENO, F_GETFL, 0);
	fcntl(STDOUT_FILENO,F_SETFL, val | O_NONBLOCK);

	toiptr = tooptr = to;
	friptr = froptr = fr;
	stdineof = 0;

	maxfdp1 = max(max(STDIN_FILENO,STDOUT_FILENO),sockfd) + 1;
	while (1) {
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		/* 指定所关注的描述符 */
		if (stdineof == 0 && toiptr < &to[MAXLINE])
			FD_SET(STDIN_FILENO, &rset);			//read from stdin 
		if (friptr < &fd[MAXLINE])
			FD_SET(sockfd,&rset);					//read from sockfd 
		if (tooptr != toiptr)
			FD_SET(sockfd, &wset);					//data to write to socket
		if (froptr != friptr)
			FD_SET(STDOUT_FILENO,&wset);			//data to write to stdout 
		
		select(maxfdp1,&rset,&wset,NULL,NULL);
		
		/* 从标准输入或者从套接字读入*/
		if(FD_ISSET(STDIN_FILENO, &rset)) {
			if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0){
				if (errno != EWOULDBLOCK)
					err_sys("read error on stdin");
			}
			else if (n == 0) {
				fprintf(stderr, "EOF on stdin\n");
				stdineof = 1;
				if (tooptr = toiptr)
					shutdown(sockfd, SHUT_WR);		//send FIN 
			} else {
				fprintf(stderr, "read %d bytes from stdin\n",n);
				toiptr += n;						//jusy read 
				FD_SET(sockfd,&wset);				//try to write to socket 
			}
		}

		if(FD_ISSET(sockfd, &rset)) {
			if ( (n = read(sockfd,friptr, &fr[MAXLINE] - friptr)) < 0) {
				if (errno != EWOULDBLOCK);
				err_sys("read error on socket");
			}
			else if (n == 0) {
				fprintf(stderr, "EOF on socket\n",);
				if (stdineof)
					return;							//normal termination
				else	
					err_quit("str_cli: server terminated prematurely");
			} else {
				fprintf(stderr,"read %d bytes from socket",n);
				friptr += n;
				FD_SET(STDOUT_FILENO,&wset);		//try to write to stdout 
			}
		}

		/* 写到标准输出或者套接字 */
		if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n= friptr - froptr) > 0)) {
			if ( (nwritten = write(STDOUT_FILENO,froptr,n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to stdout");
			}else {
				fprintf(stderr, "wrote %d bytes to stdout \n",nwritten);
				froptr += nwritten;					//just written 
				if (froptr == friptr)
					froptr = friptr = fr;			//back to beginning of buffer
			}
		}

		if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) {
			if ( (nwritten = write(socket,tooptr,n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to socket");
			} else {
				fprintf(stderr, "wrote %d bytes to socket \n",nwritten);
				tooptr += nwritten;					//just written 
				if (tooptr == toiptr) {
					toiptr = tooptr = to;			//back to beginning of buffer 
					if (stdineof)
						shutdown(sockfd, SHUT_WR);	//send FIN 
				}
			}
		}
	}
}

 

非阻塞式I/O例子的时间线:

                非阻塞式I/O

 

str_cli的较简单版本

str_cli函数的另一个版本,该函数使用fork把当前进程划分成两个进程。子进程把来自服务器的文本行复制到标准输出,父进程把来自标准输入的文本行复制到服务器。

                非阻塞式I/O

 

str_cli_fork.h

#include "unp.h"

void str_cli(FILE *fp, int sockfd)
{
	pid_t pid;
	char sendline[MAXLINE],recvline[MAXLINE];

	if ( (pid = fork()) == 0)	{		//child: server -> stdout
		while (readline(sockfd,recvline,MAXLINE) > 0)
			fputs(recvline, stdout);
	
		kill(getppid(),SIGTERM);		//防止父进程一直运行
		exit(0);
	}

	/* parent: stdin -> server */
	while (fgets(sendline,MAXLINE,fp) != NULL)
		writen(sockfd, sendline, strlen(sendline));

	shutdown(sockfd,SHUT_WR);			//EOF on stdin, sen FIN
	pause();
	return;
}

 

非阻塞connect

当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的TCP三路握手继续进行。使用select检测这个连接或成功或失败的已建立条件。非阻塞的connect有三个用途。

1.把三路握手叠加在其它处理上。

2.可以使用这个技术同时建立多个连接。

3.使用select等待连接的建立,可以给select指定一个时间限制,使得我们能够缩短connect的超时。

 

非阻塞connect:时间获取客户程序

#include "unp.h"

int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
	int flags, n, error;
	socklen_t len;
	fd_set rset, wset;
	struct timaval tval;

	flags = fcntl(sockfd, F_GETFL, 0);
	fcntl(sockfd,F_SETFL, flags | O_NONBLOCK);		//把套接字设置为非阻塞

	error = 0;
	if ( (n = connect(sockfd,saptr,salen)) < 0)		//发起非阻塞connect,期望错误是EINPROGRESS,表示连接建立已启动但是尚未完成
		if (errno != EINPROGRESS)
			return -1;
	
	//在connect完成期间可以做其它事
	if (n == 0)
		goto done;			//connect completed immediately;
	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);
	wset = rset;
	tval.tv_sec  = nsec;
	tval.tv_usec = 0;

	if ( (n=select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {	//调用select等待套接字可读或者可写
		close(sockfd);		//timeout
		errno = ETIMEDOUT;
		return -1;			//超时则关闭套接字,防止三路握手继续下去,并返回TIMEOUT错误给调用者。
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
		len = sizeof(error);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
			return (-1);
	} else 
		err_quit("select error: sockfd not set");

done:
	fcntl(sockfd,F_SETFL, flags);		//restore file status flags 

	if (error) {
		close(sockfd);					//just in case 
		errno = error;
		return (-1);
	}
	return 0;
}

 

非阻塞accept

 ......................