阻塞与非阻塞IO -- 网络编程随想

阻塞和非阻塞IO

阻塞IO指当进行IO操作时, 如果IO操作无法立即完成,当前线程进入阻塞状态,直到IO操作完成,IO函数返回。

非阻塞IO指当进行IO操作时,如果IO操作无法立即完成,IO函数立即返回,线程不会阻塞。

写与读操作对阻塞与非阻塞IO的语义

写操作,只有完成所有指定数据的写入时,写操作才算完成。

读操作,只要能读取到数据,读操作就算完成。

阻塞IO

写操作。len 为指定写入的数据量。 如果只写入部分数据,IO函数会阻塞直至写入数据或发生错误才返回。

以soket的send()为例。ssize_t send(int sockfd, const void *buf, size_t len, int flags);

调用send()。设write_num为写入数据量,当0 < write_num < len,线程会阻塞直至写入所有数据。如果函数send()的返回值小于len则意味有错误发生了。

读操作。len 为一次读入的最大数据量,即buffer的大小。

同样以socket的recv()为例。 ssize_t recv(int sockfd, void *buf, size_t len, int flags);

如果sockfd无可读数据,则调用recv()会阻塞。如有数据可读,IO函数立即返回。设recv_num为返回值。一般0 <= recv_num <= len。

非阻塞IO

写操作。调用send(),函数立即返回,设write_num为返回值。正常状态下 0 < write_num <= len。如果 write_num 为负数,则说明有错误发生了。

读操作。调用recv(), 函数立即返回,设recv_num为为返回值。正常状态下 0 <= recv_num <= len。(recv_num = 0 表明接收到对方的FIN。即对方调用shutdown() )。如recv_num为负数,则说明有错误发生。

比较示例 echo_client_block.c
和 echo_client_nonblock.c, 可以验证上面的说法。

网络编程(仅考虑TCP)

客户端

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

阻塞IO。connect() 会阻塞直至连接完成建立,返回0。这时sockfd可写。

非阻塞IO。connect()不会等待连接完成建立,会立即返回-1,并将errno设置为EINPROGRESS。这时可忽略该错误,并使用poll()或select()。因为当连接完成建立时,sockfd这时处于writable的状态。

服务端

int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);

阻塞IO。accept() 会阻塞直至从待处理连接队列(the queue of pending connections)中获取到的新的连接,并为给连接创建socket并返回其描述符。

非阻塞IO。accept() 会返回-1,并将errno设置为EAGAIN或EWOULDBLOCK。这时可忽略该错误,并使用poll()或select()监听listen_sockfd。因为当待处理连接队列不为空时,listen_sockfd处于readable状态。

注:int listen(int sockfd, int backlog) 可通过backlog设置待处理连接队列的长度。有些资料表明这个设置对操作系统仅是一个建议。

获取阻塞和非阻塞socket

通过socket()获得

int socket(int domain, int type, int protocol);

sockfd = socket(AF_INET, SOCK_STREAM, 0) 这样默认获得阻塞的socket。

(sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0) 获得非阻塞socket。

通过accept()获得

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

默认得到的是一个阻塞的socket。如果要设置socket为非阻塞,可用 int fcntl(int fd, int cmd, ... /* arg */ );

// non-block

int flags = fcntl(sockfd, F_GETFL, 0);

flags |= O_NONBLOCK;

int ret = fcntl(sockfd, F_SETFL, flags);

利用新增的系统调用可以accept(2)一步得到非阻塞的socket。

int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

一点总结

阻塞IO最大的好处就是编程模型简单,但是线程容易阻塞在IO系统调用上,无法最大限度复用线程的控制流( thread-of-control)。反之,非阻塞IO能够更好的复用线程的控制流,但编程模型却变得复杂,比较示例 echo_server_block.c 和echo_server_nonblock.c可以看出。最终还是要依据应用的特点做合理的选择!

示例代码

/*
 * echo_client_block.c
 *
 *  Created on: Jun 7, 2014
 *      Author: damonhao
 */

#include <stdio.h> //perror()
#include <string.h> //strlen()
#include <strings.h>//bzero()
#include <errno.h>
#include <unistd.h> //close()
#include <stdlib.h> //exit()

//net
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXBUF	1024*1000
//#define MAXBUF	20

int main(int agrc, char *argv[])
{
	if (agrc != 3)
	{
		puts("usage:<program> <server ip> <port>");
		exit(0);
	}
	puts("block echo client up!");
	const char *server_ip = argv[1];
	int server_port = atoi(argv[2]);
	int sockfd;
	//Create streaming socket
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(errno);
	}
	struct sockaddr_in server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(server_port);
	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

	if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr))
			!= 0)
	{
		perror("connect");
		exit(errno);
	}

	char input_buff[MAXBUF];
	char message[MAXBUF];
	memset(message, 'a', MAXBUF - 1);
	message[MAXBUF - 1] = '\0';
	int send_num = send(sockfd, message, strlen(message) + 1, 0);
	printf("send num:%d\n", send_num);
	int recv_num = recv(sockfd, input_buff, sizeof(input_buff), 0);
	printf("recv num:%d\n", recv_num);
	close(sockfd);
	return 0;
}
/*
 ./echo_client_block 127.0.0.1 9999
 block echo client up!
 send num:1024000
 recv num:65664
 */
/*
 * echo_client_nonblock.c
 *
 *  Created on: Jun 7, 2014
 *      Author: damonhao
 */

#include <stdio.h> //perror()
#include <string.h> //strlen()
#include <strings.h>//bzero()
#include <errno.h>
#include <unistd.h> //close()
#include <stdlib.h> //exit()

//net
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/poll.h>

#define MAXBUF	1024*1000

int main(int agrc, char *argv[])
{
	if (agrc != 3)
	{
		puts("usage:<program> <server ip> <port>");
		exit(0);
	}
	puts("nonblock echo client up!");
	const char *server_ip = argv[1];
	int server_port = atoi(argv[2]);
	int sockfd;
	//Create a nonblocking streaming socket
	if ((sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
	{
		perror("socket");
		exit(errno);
	}
	struct sockaddr_in server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(server_port);
	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
	//For nonblocking sockfd, connect will return -1 immediately with errno set to EINPROGRESS.
	int ret = connect(sockfd, (struct sockaddr *) &server_addr,
			sizeof(server_addr));
	int saved_errno = (ret == 0) ? 0 : errno;
	if (saved_errno == EINPROGRESS || saved_errno == 0)
	{
		struct pollfd pfds[1];
		pfds[0].fd = sockfd;
		pfds[0].events = POLLOUT;
		while (1)
		{
			poll(pfds, sizeof(pfds), -1);

			if (pfds[0].revents & POLLOUT)
			{
				char input_buff[MAXBUF];
				char message[MAXBUF];
				memset(message, 'a', MAXBUF - 1);
				message[MAXBUF - 1] = '\0';
				int send_num = send(sockfd, message, strlen(message) + 1, 0);
				printf("send num:%d\n", send_num);
				int recv_num = recv(sockfd, input_buff, sizeof(input_buff), 0);
				printf("recv num:%d\n", recv_num);
				close(sockfd);
				break;
			}
		}
	}
	else
	{
		perror("connect");
	}
	return 0;
}

/*
 ./echo_client_nonblock 127.0.0.1 9999
 nonblock echo client up!
 send num:180224
 recv num:1024
 **/
/*
 * echo_server_block.c
 *
 *  Created on: Jun 8, 2014
 *      Author: damonhao
 */

#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

//net
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXBUF	1024

int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		puts("usage:<program> <port>");
		return 0;
	}
	puts("block echo server up!");
	//set server address;
	int port = atoi(argv[1]);

	struct sockaddr_in server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = INADDR_ANY;
	server_addr.sin_port = htons(port);

	int listen_sockfd = -1;

	//Create streaming socket
	if ((listen_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(errno);
	}

	//bind server address to listen_sockfd
	if (bind(listen_sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr))
			!= 0)
	{
		perror("bind error");
		exit(errno);
	}
	//make listen_sockfd a "listening socket"
	if (listen(listen_sockfd, 20) != 0)
	{
		perror("listen error");
		exit(errno);
	}

	while (1)
	{
		struct sockaddr_in client_addr;
		socklen_t addr_len = sizeof(client_addr);
		int clientfd = accept(listen_sockfd, (struct sockaddr*) &client_addr,
				&addr_len);
		pid_t pid;
		if ((pid = fork()) < 0)
		{
			perror("fork");
			exit(errno);
		}
		else if (pid == 0) //in child progress
		{
			char buffer[MAXBUF];
			printf("connection from %s:%d up\n", inet_ntoa(client_addr.sin_addr),
					ntohs(client_addr.sin_port));
			//Echo back anything received
			int recv_num = 0;
			while ((recv_num = recv(clientfd, buffer, MAXBUF, 0)) > 0)
			{
				printf("recevied data size: %d\n", recv_num);
				send(clientfd, buffer, recv_num, 0);
			}
			if (recv_num < 0)
			{
				puts("received data error");
			}
			close(clientfd);
			printf("connection from %s:%d down\n", inet_ntoa(client_addr.sin_addr),
					ntohs(client_addr.sin_port));
			break;
		}
		else // in parent progress
		{
			close(clientfd);
		}
	}
	return 0;
}
/*
 * echo_server_nonblock.c
 *
 *  Created on: Jun 8, 2014
 *      Author: damonhao
 */

#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

//net
//define _GNU_SOURCE for accept4()
//#define _GNU_SOURCE
//man page says use _GNU_SOURCE, but in socket.h I find it should be __USE_GNU;
//define __USE_GNU for accept4()
#define __USE_GNU
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/poll.h>

#define MAXBUF	1024

int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		puts("usage:<program> <port>");
		return 0;
	}
	puts("nonblock echo server up!");
	//set server address;
	int port = atoi(argv[1]);

	struct sockaddr_in server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = INADDR_ANY;
	server_addr.sin_port = htons(port);

	int listen_sockfd = -1;
	//Create streaming socket
	if ((listen_sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
	{
		perror("socket");
		exit(errno);
	}
	//bind server address to listen_sockfd
	if (bind(listen_sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr))
			!= 0)
	{
		perror("bind error");
		exit(errno);
	}
	//make listen_sockfd a "listening socket"
	if (listen(listen_sockfd, 20) != 0)
	{
		perror("listen error");
		exit(errno);
	}

	struct pollfd pfds[1];
	pfds[0].fd = listen_sockfd;
	pfds[0].events = POLLIN;
	while (1)
	{
		struct sockaddr_in client_addr;
		socklen_t addr_len = sizeof(client_addr);
		poll(pfds, sizeof(pfds), -1);
		if (pfds[0].revents & POLLIN)
		{
			int clientfd = accept4(listen_sockfd, (struct sockaddr*) &client_addr,
					&addr_len, SOCK_NONBLOCK);
			pid_t pid;
			if ((pid = fork()) < 0)
			{
				perror("fork");
				exit(errno);
			}
			else if (pid == 0) // in child progress;
			{
				printf("connection from %s:%d up\n", inet_ntoa(client_addr.sin_addr),
						ntohs(client_addr.sin_port));
				struct pollfd client_pfds[1];
				client_pfds[0].fd = clientfd;
				client_pfds[0].events = POLLIN;
				while (1)
				{
					poll(client_pfds, sizeof(client_pfds), -1);
					if (client_pfds[0].revents & POLLIN)
					{
						char buffer[MAXBUF];
						int recv_num = recv(clientfd, buffer, MAXBUF, 0);
						if (recv_num > 0)
						{
							printf("recevied data size: %d\n", recv_num);
							//FIXME: may send less than require;
							int send_num = send(clientfd, buffer, recv_num, 0);
							if (send_num < 0)
							{
								perror("send data error");
							}
						}
						else if (recv_num == 0)
						{
							close(clientfd);
							printf("connection from %s:%d down\n",
									inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
							break;
						}
						else
						{
							perror("received data error");
							exit(errno);
						}
					}
				}
			}
			else // in parent progress
			{
				close(clientfd);
			}
		}
	}
	return 0;
}

阻塞与非阻塞IO -- 网络编程随想,布布扣,bubuko.com

时间: 2024-10-09 12:27:11

阻塞与非阻塞IO -- 网络编程随想的相关文章

网络IO之阻塞、非阻塞、同步、异步总结

1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一遍时候,似懂非懂,没有深入理解.网上有详细的分析:http://blog.csdn.net/historyasamirror/article/details/5778378.我结合网上博客和书总结一下,加以区别,加深理解. 2.数据流向 网络IO操作实际过程涉及到内核和调用这个IO操作的进程.以read为例,read的具体操作分为以下两个

网络编程释疑之:同步,异步,阻塞,非阻塞

一讲到网络编程的I/O模型,总会涉及到这几个概念.问了很多人,没几个能清晰地讲出他们之间的区别联系,甚至在网络上也有很多不同的观点,也不知是中国文字释义的博大精深,还是本来这几个概念就是绕人不倦.今天我也来给大家讲解一下我对这几个概念的理解. 既然网络上众说纷纭,不如找个权威参考一下,这个权威就是<UNIX网络编程:卷一>第六章——I/O复用.书中向我们提及了5种类UNIX下可用的I/O模型: 阻塞式I/O: 非阻塞式I/O: I/O复用(select,poll,epoll...): 信号驱动

(转载)网络编程释疑之:同步,异步,阻塞,非阻塞

一讲到网络编程的I/O模型,总会涉及到这几个概念.问了很多人,没几个能清晰地讲出他们之间的区别联系,甚至在网络上也有很多不同的观点,也不知是中国文字释义的博大精深,还是本来这几个概念就是绕人不倦.今天我也来给大家讲解一下我对这几个概念的理解. 既然网络上众说纷纭,不如找个权威参考一下,这个权威就是<UNIX网络编程:卷一>第六章——I/O复用.书中向我们提及了5种类UNIX下可用的I/O模型: 阻塞式I/O: 非阻塞式I/O: I/O复用(select,poll,epoll...): 信号驱动

linux网络IO模型——阻塞、非阻塞和同步、异步

最近几天在学习nginx的时候了解了一下linux网络IO模型,在此谈谈我自己的理解,如有错误请多多指教.本文参考书籍Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”. Linux网络IO请求数据分为两段: 1.数据准备 2.将数据从内核拷贝到进程空间 其实,阻塞.非阻塞和同步.异步的不同就在于这两个阶段的不同. 同步和异步关

socket编程的同步、异步与阻塞、非阻塞示例详解

socket编程的同步.异步与阻塞.非阻塞示例详解之一 分类: 架构设计与优化 简介图 1. 基本 Linux I/O 模型的简单矩阵 每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点.本节将简要对其一一进行介绍. 一.同步阻塞模式在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误). /* * \brief * tcp client */ #include <stdio.h> #include <stdlib

Linux下阻塞与非阻塞IO

阻塞:顾名思义,就是指在执行设备操作时若不能获得资源则挂起操作,直到满足可操作的条件后再进行操作,被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件满足. 非阻塞:就是反过来,进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直到可以进行位置. Socket编程中,阻塞与非阻塞的区别: 阻塞:一般的I/O操作可以在新建的流中运用.在服务器回应前它等待客户端发送一个空白的行.当会话结束时,服务器关闭流和客户端socket. 如果在队列中没有请示将会出现什么情况呢?那个方

IO多路复用,同步,异步,阻塞和非阻塞 区别(转)

转自:http://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral 一.什么是socket?什么是I/O操作? 我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO.管道.终端,对我们来说,一切都是文件,一切都是流.在信息 交换的过程中,我们都是对这些流进行数据的收发操作,简称为I/O操作(input and outp

关于IO的同步,异步,阻塞,非阻塞

关于网络IO的同步.异步.阻塞.非阻塞的文章网上有很多,搜索了对比了一下,观点也各不相同,即使是wiki也把异步和非阻塞区分得不是很清楚.下面我就结合<Unix网络编程 卷1>中的介绍,来说一说自己的理解.   IO模型     首先我们要先知道目前unix存在的五种IO模型,分别是: 阻塞型IO(blocking I/O) 非阻塞型IO(noblocking I/O) IO多路复用(I/O multiplexing) 信号驱动(signal driven I/O) 异步IO(asynchro

Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: a -- 阻塞I/Ob -- 非阻塞I/Oc -- I/O复用(select和poll)d -- 信号驱动I/O(SIGIO)e -- 异步I/O(Posix.1的aio_系列函数) 下面我们先学习阻塞I/O.非阻塞I/O .I/O复用(select和poll),先学习一下基础概念 a -- 阻塞