【UNIX网络编程】第16章非阻塞式I/O

1. 概述

套接字的默认状态是阻塞的,即当发出一个不能立即完成套接字调用时,其进程被置于休眠状态。可能阻塞的套接字调用可分为如下四类:

  • 输入操作:read、readv、recv、recvfrom和recvmsg等。对一个阻塞的TCP套接字调用这些函数时,而该套接字的接收缓冲区中没有数据可读,该进程将被置于休眠状态,直到有数据到达。既然TCP是字节流协议,该进程的唤醒就是只要有一些数据到达。
  • 输出操作:write、writev、send、sendto、sendmsg等。对于一个TCP套接字,内核将从应用进程的缓冲区该套接字的发送缓冲区复制数据。若发送缓冲区无空间,进程将被置于休眠状态,直到有空间为止。【若发送缓冲区空间为0,则返回EWOULDBLOCK错误;若有小部分缓冲空间,则返回剩余空间的字节数(称为不足计数)】。
  • 接受外来连接,即accept函数。若尚无外来连接,则调用进程将被置于休眠状态。
  • 发起外出连接,即TCP的connect函数。TCP连接的建立涉及三路握手过程,而且connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回, 即connect总会阻塞其调用进程至少一个服务器的RTT时间。【图2-2】

1.1 非阻塞读和写:str_cli函数

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;

	// 使用Fcntl把所用3个描述符都设置为非阻塞
	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 = toiptr = to;  // 初始化指针
	froptr = friptr = 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);  // 从标准输入流读取数据
		if (friptr < &fr[MAXLINE])
			FD_SET(sockfd, &rset);    // 从socket读取数据
		if (tooptr != toiptr)
			FD_SET(sockfd, &wset);
		if (froptr != friptr)
			FD_SET(STDOUT_FILENO, &wset);
		// 等待4个可能条件中任何一个变为真
		Select(maxfdp1, &rset, &wset, NULL, NULL);

		if (FD_ISSET)
		{
			/* code */
		}
		/* 省略..... */
	}
}

1.2 非阻塞connect函数

int connect_nonb(int sockfd, 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函数把套接字设置为非阻塞
	Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

	error = 0;
	if ( (n = connect(sockfd, saptr, salen)) < 0)
		if (error != EINPROGRESS)
			return(-1);

	// Do whatever we want while the connect is taking place
	if (n == 0)   // connect completed immediately
		goto done;
	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)  // TIMEOUT
	{
		close(sockfd);
		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);
	} 
	else 
		err_quit("select error: sockfd not set");

done:
	Fcntl(sockfd, F_SETFL, flags);   // restore file status flags
	if (error)
	{
		close(sockfd);
		errno = error;
		return(-1);
	}
	return(0);
}

1.3 非阻塞accept


int main(int argc, char const *argv[])
{
	int sockfd;
	struct linger ling;
	struct sockaddr_in servaddr;

	if (argc != 2)
		err_quit("usage: tcpcli <IPaddress>");
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	// 设置
	ling.l_onoff = 1;
	ling.l_linger = 0;
	Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
	Close(sockfd);
	exit(0);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值