博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UNIX网络编程卷1:套接字联网-第16章:非阻塞式I/O
阅读量:4211 次
发布时间:2019-05-26

本文共 6662 字,大约阅读时间需要 22 分钟。

1.可能阻塞的4类套接字调用:

      1)输入操作,包括read,readv,recv,recvfrom,recvmsg。对于非阻塞的套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。

     2)输出操作,包括write、writev、send、sendto和sendmsg。对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个EWOULDBLOCK错误。如果其发送缓冲区中有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。

     3)接受外来连接,即accept函数。如果对于一个非阻塞的套接字调用accept函数,并且尚无新的连接到达,accept调用将立即返回一个EWOULDBLOCK错误。

     4)发起外来连接,即用于tcp的connect函数。对于一个非阻塞的tcp套接字调用connect,并且连接不能立即建立,那么连接的建立能照样发起,不过会返回一个EINPROGRESS错误。且,有些连接可以立即建立,通常发生在服务器和客户处于同一个主机

以下代码来自unix网络编程卷1

使用非阻塞式I/O版本,防止进程在可做任何有效工作期间发生阻塞

2.非阻塞式I/O的str_cli版本

非阻塞式I/O的加入,使得程序对缓冲区的管理明显复杂化

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

/* include nonb1 */#include	"unp.h"voidstr_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;	/* initialize buffer pointers */	friptr = froptr = fr;	stdineof = 0;	maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;	for ( ; ; ) {		FD_ZERO(&rset);		FD_ZERO(&wset);		if (stdineof == 0 && toiptr < &to[MAXLINE])			FD_SET(STDIN_FILENO, &rset);	/* read from stdin */		if (friptr < &fr[MAXLINE])			FD_SET(sockfd, &rset);			/* read from socket */		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);/* end nonb1 *//* include nonb2 */		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) {#ifdef	VOL2				fprintf(stderr, "%s: EOF on stdin\n", gf_time());#endif				stdineof = 1;			/* all done with stdin */				if (tooptr == toiptr)					Shutdown(sockfd, SHUT_WR);/* send FIN */			} else {#ifdef	VOL2				fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);#endif				toiptr += n;			/* # just read */				FD_SET(sockfd, &wset);	/* try and write to socket below */			}		}		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) {#ifdef	VOL2				fprintf(stderr, "%s: EOF on socket\n", gf_time());#endif				if (stdineof)					return;		/* normal termination */				else					err_quit("str_cli: server terminated prematurely");			} else {#ifdef	VOL2				fprintf(stderr, "%s: read %d bytes from socket\n",								gf_time(), n);#endif				friptr += n;		/* # just read */				FD_SET(STDOUT_FILENO, &wset);	/* try and write below */			}		}/* end nonb2 *//* include nonb3 */		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 {#ifdef	VOL2				fprintf(stderr, "%s: wrote %d bytes to stdout\n",								gf_time(), nwritten);#endif				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(sockfd, tooptr, n)) < 0) {				if (errno != EWOULDBLOCK)					err_sys("write error to socket");			} else {#ifdef	VOL2				fprintf(stderr, "%s: wrote %d bytes to socket\n",								gf_time(), nwritten);#endif				tooptr += nwritten;	/* # just written */				if (tooptr == toiptr) {					toiptr = tooptr = to;	/* back to beginning of buffer */					if (stdineof)						Shutdown(sockfd, SHUT_WR);	/* send FIN */				}			}		}	}}/* end nonb3 */

str_cli的较简单版本(把应用程序任务划分到多个进程或多个线程) #include    "unp.h"voidstr_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);    /* in case parent still running */        exit(0);    }        /* parent: stdin -> server */    while (Fgets(sendline, MAXLINE, fp) != NULL)        Writen(sockfd, sendline, strlen(sendline));    Shutdown(sockfd, SHUT_WR);    /* EOF on stdin, send FIN */    pause();    return;}

 我们首先要明确几点: 

  1. TCP连接是全双工的
  2. 父子进程共享一个套接字
  3. 我们fork子进程后,父进程往该套接字中写,子进程从该套接字中读(任务分开,进程各干各的)

3.str_cli执行时间对比

以下来自unix网络编程卷1的测试结果

测试环境:从一个solaris客户机向RTT为175毫秒的一个服务器主机复制2000行文本

非阻塞式I/O版本 6.9秒
线程化版本 8.5秒
fork版本 8.7秒
select加阻塞式I/O版本 12.3秒
停等版本 354.0秒

4.非阻塞connect

非阻塞connect的三个用途:

  1.  我们可以把三路握手叠加在其他处理上,完成一个connect要花一个RTT时间,这段时间内我们可以执行其他要处理的工作。
  2. 我们可以使用这个技术同时建立多个连接
  3. 使用select等待连接的建立,我们可以给select指定一个时间限制,使得我们能够缩短connect的超时

怎么使用connect:

先将套接字描述符设置为非阻塞,然后按照常规网络编程,直到调用connect时,connect将立即返回一个EINPROGRESS错误(ps: 已经发起的三路握手继续执行)。我们

接着使用I/O复用(比如 select)检测这个连接或成功或失败的已建立条件

需要注意的处理细节

  1. 尽管套接字是非阻塞的,如果连接的服务器在同一主机上,我们调用connect时,连接通常立刻建立。
  2. 关于select和非阻塞connect的以下两个规则:当连接成功建立时,描述符变为可写,当连接建立遇到失败时,描述符变为既可读又可写。

注:以下代码来自unix网络编程卷1

1.非阻塞connect:

#include	"unp.h"intconnect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec){	int				flags, n, error;	socklen_t		len;	fd_set			rset, wset;	struct timeval	tval;	flags = Fcntl(sockfd, F_GETFL, 0);	Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);	error = 0;	if ( (n = connect(sockfd, saptr, salen)) < 0)		if (errno != EINPROGRESS)			return(-1);	/* Do whatever we want while the connect is taking place. */	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) {		close(sockfd);		/* timeout */		errno = ETIMEDOUT;		return(-1);	}	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {		len = sizeof(error);		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)			return(-1);			/* Solaris pending error */	} 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);}

如何判断非阻塞connect调用后,连接是否成功建立:

  1. 上述程序使用的方法:先判断套接字可读或可写条件,然后调用getsockopt,检查套接字上是否存在待处理错误
  2. 调用getpeername代替getsockopt。
  3. 以值为0的长度参数调用read
  4. 再调用connect一次

以上具体参见unix网络编程卷1

5.非阻塞accept

 1.当使用select获悉某个监听套接字上何时有已完成连接准备被accept时,总是把这个监听套接字设置为非阻塞

2.在后续的accept调用中忽略以下错误:EWOULDBLOCK(源自bk,客户终止连接时),ECONNABORTED(POSIX实现,客户终止连接时,EPROTO(SVR4实现,客户中止连接时)和EINTR(如果有信号被捕获)

你可能感兴趣的文章
内核态与用户态
查看>>
使用mingw(fedora)移植virt-viewer
查看>>
趣链 BitXHub跨链平台 (4)跨链网关“初介绍”
查看>>
C++ 字符串string操作
查看>>
MySQL必知必会 -- 了解SQL和MySQL
查看>>
MySQL必知必会 -- 使用MySQL
查看>>
MySQL必知必会 -- 数据检索
查看>>
MySQL必知必会 -- 排序检索数据 ORDER BY
查看>>
MySQL必知必会 -- 数据过滤
查看>>
POJ 3087 解题报告
查看>>
POJ 2536 解题报告
查看>>
POJ 1154 解题报告
查看>>
POJ 1661 解题报告
查看>>
POJ 1101 解题报告
查看>>
ACM POJ catalogues[转载]
查看>>
ACM经历总结[转载]
查看>>
C/C++文件操作[转载]
查看>>
专业计划
查看>>
小米笔试:最大子数组乘积
查看>>
常见的排序算法
查看>>