阻塞和非阻塞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;
}