accept的阻塞与非阻塞
C网络编程:Server处理多个Client(多进程server方法 和 non-blocking与select结合)
参看基于TCP/UDP的socket代码,同一时间Server只能处理一个Client请求:在使用当前连接的socket和client进行交互的时候,不能够accept新的连接请求。为了使Server能够处理多个Client请求,常见的方法:
多进程方法(每个子进程单独处理一个client连接)
在每个accept成功之后,使用fork创建一个子进程专门处理该client的connection,父进程(server)本身可以继续accept其他新的client的连接请求。具体如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <signal.h>#include <sys/wait.h>#define DEFAULT_PORT 1984 //默认端口
#define BUFFER_SIZE 1024 //buffer大小void sigCatcher(int n) {//printf("a child process dies\n");while(waitpid(-1, NULL, WNOHANG) > 0);}int clientProcess(int new_sock);int main(int argc, char *argv[]) {unsigned short int port;//get port, use default if not setif (argc == 2) {port = atoi(argv[1]);} else if (argc < 2) {port = DEFAULT_PORT;} else {fprintf(stderr, "USAGE: %s [port]\n", argv[0]);return 1;}//create socketint sock;if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {perror("socket failed, ");return 1;}printf("socket done\n");//create socket address and initializestruct sockaddr_in bind_addr;memset(&bind_addr, 0, sizeof(bind_addr));bind_addr.sin_family = AF_INET;bind_addr.sin_addr.s_addr = htonl(INADDR_ANY); //设置接受任意地址bind_addr.sin_port = htons(port); //将host byte order转换为network byte order//bind (bind socket to the created socket address)if ( bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1 ) {perror("bind failed, ");return 1;}printf("bind done\n");//listenif ( listen(sock, 5) == -1) {perror("listen failed.");return 1;}printf("listen done\n");//handler to clear zombie processsignal(SIGCHLD, sigCatcher);//loop and respond to clientint new_sock;int pid;while (1) {//wait for a connection, then accept itif ( (new_sock = accept(sock, NULL, NULL)) == -1 ) {perror("accept failed.");return 1;}printf("accept done\n");pid = fork();if (pid < 0) {perror("fork failed");return 1;} else if (pid == 0) {//这里是子进程close(sock); //子进程中不需要server的sockclientProcess(new_sock); //使用新的new_sock和client进行交互close(new_sock); //关闭client的连接exit(EXIT_SUCCESS); //子进程退出} else {//这里是父进程close(new_sock); //由于new_sock已经交给子进程处理,这里可以关闭了}}return 0;}int clientProcess(int new_sock) {int recv_size;char buffer[BUFFER_SIZE];memset(buffer, 0, BUFFER_SIZE);if ( (recv_size = recv(new_sock, buffer, sizeof(buffer), 0)) == -1) {perror("recv failed");return 1;}printf("%s\n", buffer);char *response = "This is the response";if ( send(new_sock, response, strlen(response) + 1, 0) == -1 ) {perror("send failed");return 1;}return 0;}
其中的signal(SIGCHLD, sigCatcher)代码为了处理zombie process问题:当server进程运行时间较长,且产生越来越多的子进程,当这些子进程运行结束都会成为zombie process,占据系统的process table。解决方法是在父进程(server进程)中显式地处理子进程结束之后发出的SIGCHLD信号:调用wait/waitpid清理子进程的zombie信息。
测试:运行server程序,然后同时运行2个client(telnet localhost 1984),可看到该server能够很好地处理2个client。
- 多进程方法的优点:
每个独立进程处理一个独立的client,对server进程来说只需要accept新的连接,对每个子进程来说只需要处理自己的client即可。
- 多进程方法的缺点:
子进程的创建需要独立的父进程资源副本,开销较大,对高并发的请求不太适合;且一个进程仅处理一个client不能有效发挥作用。另外有些情况下还需要进程间进行通信以协调各进程要完成的任务。
使用select实现non-blocking socket(single process concurrent server)
blocking socket VS non-blocking socket
默认情况下socket是blocking的,即函数accept(), recv/recvfrom, send/sendto,connect等,需等待函数执行结束之后才能够返回(此时操作系统切换到其他进程执行)。accpet()等待到有client连接请求并接受成功之后,recv/recvfrom需要读取完client发送的数据之后才能够返回。
可设置socket为non-blocking模式,即调用函数立即返回,而不是必须等待满足一定条件才返回。参看http://www.scottklement.com/rpg/socktut/nonblocking.html
non-blocking: by default, sockets are blocking - this means that they stop the function from returning until all data has been transfered. With multiple connections which may or may not be transmitting data to a server, this would not be very good as connections may have to wait to transmit their data.
设置socket为非阻塞non-blocking
使用socket()创建的socket(file descriptor),默认是阻塞的(blocking);使用函数fcntl()(file control)可设置创建的socket为非阻塞的non-blocking。
#include <unistd.h>#include <fcntl.h>sock = socket(PF_INET, SOCK_STREAM, 0);int flags = fcntl(sock, F_GETFL, 0);fcntl(sock, F_SETFL, flags | O_NONBLOCK);
这样使用原本blocking的各种函数,可以立即获得返回结果。通过判断返回的errno了解状态:
- accept():
在non-blocking模式下,如果返回值为-1,且errno == EAGAIN或errno == EWOULDBLOCK表示no connections没有新连接请求;
- recv()/recvfrom():
在non-blocking模式下,如果返回值为-1,且errno == EAGAIN表示没有可接受的数据或很在接受尚未完成;
- send()/sendto():
在non-blocking模式下,如果返回值为-1,且errno == EAGAIN或errno == EWOULDBLOCK表示没有可发送数据或数据发送正在进行没有完成。
- read/write:
在non-blocking模式下,如果返回-1,且errno == EAGAIN表示没有可读写数据或可读写正在进行尚未完成。
- connect():
在non-bloking模式下,如果返回-1,且errno = EINPROGRESS表示正在连接。
使用如上方法,可以创建一个non-blocking的server的程序,类似如下代码:
int main(int argc, char *argv[]) {int sock;if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {perror("socket failed");return 1;}//set socket to be non-blockingint flags = fcntl(sock, F_GETFL, 0);fcntl(sock, F_SETFL, flags | O_NONBLOCK);//create socket address to bindstruct sockaddr_in bind_addr...//bindbind(...)...//listenlisten(...)...//loop int new_sock;while (1) {new_sock = accept(sock, NULL, NULL);if (new_sock == -1 && errno == EAGAIN) {fprintf(stderr, "no client connections yet\n");continue;} else if (new_sock == -1) {perror("accept failed");return 1;}//read and write...} ...}
纯non-blocking程序缺点:如果运行如上程序会发现调用accept可以理解返回,但这样会耗费大量的CPU time,实际中并不会这样使用。实际中将non-blocking和select结合使用。
non-blocking和select结合使用
select通过轮询,监视指定file descriptor(包括socket)的变化,知道:哪些ready for reading, 哪些ready for writing,哪些发生了错误等。select和non-blocking结合使用可很好地实现socket的多client同步通信。
select函数:
#include <sys/time.h>#include <sys/types.h>#include <unistd.h>int select(int maxfd, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);//maxfd: 所有set中最大的file descriptor + 1//readfds: 指定要侦听ready to read的file descriptor,可以为NULL//writefds: 指定要侦听ready to write的file descriptor,可以为NULL//errorfds: 指定要侦听errors的file descriptor,可以为NULL//timeout: 指定侦听到期的时间长度,如果该struct timeval的各个域都为0,则相当于完全的non-blocking模式;如果该参数为NULL,相当于block模式;//select返回total number of bits set in readfds, writefds and errorfds,当timeout的时候返回0,发生错误返回-1。//另外select会更新readfds(保存ready to read的file descriptor), writefds(保存read to write的fd), errorfds(保存error的fd),且更新timeout为距离超时时刻的剩余时间。
另外,fd_set类型需要使用如下4个宏进行赋值:
FD_ZERO(fd_set *set); //Clear all entries from the set.FD_SET(int fd, fd_set *set); //Add fd to the set.FD_CLR(int fd, fd_set *set); //Remove fd from the set.FD_ISSET(int fd, fd_set *set); //Return true if fd is in the set.
因此通过如下代码可以将要侦听的file descriptor/socket添加到响应的fd_set中,例如:
fd_set readfds;FD_ZERO(&readfds);int sock;sock = socket(PF_INET, SOCK_STREAM, 0);FD_SET(sock, &readfds); //将新创建的socket添加到readfds中FD_SET(stdin, &readfds); //将stdin添加到readfds中
struct timeval类型:
struct timeval {int tv_sec; //secondsint tv_usec; //microseconds,注意这里是微秒不是毫秒,1秒 = 1000, 000微秒};
因此,使用select函数可以添加希望侦听的file descriptor/socket到read, write或error中(如果对某一项不感兴趣,可以设置为NULL),并设置每次侦听的timeout时间。
注意如果设置timeout为:
struct timeval timeout;timeout.tv_sec = 0;timeout.tv_usec = 0;
相当于每次select立即返回相当于纯non-blocking模式;
如果设置timeout参数为NULL,则每次select持续等待到有变化则相当于blocking模式。
使用select和non-blocking实现server处理多client实例:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <fcntl.h>#include <errno.h>#include <sys/time.h>#define DEFAULT_PORT 1984 //默认端口#define BUFF_SIZE 1024 //buffer大小#define SELECT_TIMEOUT 5 //select的timeout seconds//函数:设置sock为non-blocking modevoid setSockNonBlock(int sock) {int flags;flags = fcntl(sock, F_GETFL, 0);if (flags < 0) {perror("fcntl(F_GETFL) failed");exit(EXIT_FAILURE);}if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {perror("fcntl(F_SETFL) failed");exit(EXIT_FAILURE);}}//函数:更新maxfdint updateMaxfd(fd_set fds, int maxfd) {int i;int new_maxfd = 0;for (i = 0; i <= maxfd; i++) {if (FD_ISSET(i, &fds) && i > new_maxfd) {new_maxfd = i;}}return new_maxfd;}int main(int argc, char *argv[]) {unsigned short int port;//获取自定义端口if (argc == 2) {port = atoi(argv[1]);} else if (argc < 2) {port = DEFAULT_PORT;} else {fprintf(stderr, "USAGE: %s [port]\n", argv[0]);exit(EXIT_FAILURE);}//创建socketint sock;if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {perror("socket failed, ");exit(EXIT_FAILURE);}printf("socket done\n");//in case of 'address already in use' error messageint yes = 1;if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) {perror("setsockopt failed");exit(EXIT_FAILURE);}//设置sock为non-blockingsetSockNonBlock(sock);//创建要bind的socket addressstruct sockaddr_in bind_addr;memset(&bind_addr, 0, sizeof(bind_addr));bind_addr.sin_family = AF_INET;bind_addr.sin_addr.s_addr = htonl(INADDR_ANY); //设置接受任意地址bind_addr.sin_port = htons(port); //将host byte order转换为network byte order//bind sock到创建的socket address上if ( bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1 ) {perror("bind failed, ");exit(EXIT_FAILURE);}printf("bind done\n");//listenif ( listen(sock, 5) == -1) {perror("listen failed.");exit(EXIT_FAILURE);}printf("listen done\n");//创建并初始化select需要的参数(这里仅监视read),并把sock添加到fd_set中fd_set readfds;fd_set readfds_bak; //backup for readfds(由于每次select之后会更新readfds,因此需要backup)struct timeval timeout;int maxfd;maxfd = sock;FD_ZERO(&readfds);FD_ZERO(&readfds_bak);FD_SET(sock, &readfds_bak);//循环接受client请求int new_sock;struct sockaddr_in client_addr;socklen_t client_addr_len;char client_ip_str[INET_ADDRSTRLEN];int res;int i;char buffer[BUFF_SIZE];int recv_size;while (1) {//注意select之后readfds和timeout的值都会被修改,因此每次都进行重置readfds = readfds_bak;maxfd = updateMaxfd(readfds, maxfd); //更新maxfdtimeout.tv_sec = SELECT_TIMEOUT;timeout.tv_usec = 0;printf("selecting maxfd=%d\n", maxfd);//select(这里没有设置writefds和errorfds,如有需要可以设置)res = select(maxfd + 1, &readfds, NULL, NULL, &timeout);if (res == -1) {perror("select failed");exit(EXIT_FAILURE);} else if (res == 0) {fprintf(stderr, "no socket ready for read within %d secs\n", SELECT_TIMEOUT);continue;}//检查每个socket,并进行读(如果是sock则accept)for (i = 0; i <= maxfd; i++) {if (!FD_ISSET(i, &readfds)) {continue;}//可读的socketif ( i == sock) {//当前是server的socket,不进行读写而是accept新连接client_addr_len = sizeof(client_addr);new_sock = accept(sock, (struct sockaddr *) &client_addr, &client_addr_len);if (new_sock == -1) {perror("accept failed");exit(EXIT_FAILURE);}if (!inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip_str, sizeof(client_ip_str))) {perror("inet_ntop failed");exit(EXIT_FAILURE);}printf("accept a client from: %s\n", client_ip_str);//设置new_sock为non-blockingsetSockNonBlock(new_sock);//把new_sock添加到select的侦听中if (new_sock > maxfd) {maxfd = new_sock;}FD_SET(new_sock, &readfds_bak);} else {//当前是client连接的socket,可以写(read from client)memset(buffer, 0, sizeof(buffer));if ( (recv_size = recv(i, buffer, sizeof(buffer), 0)) == -1 ) {perror("recv failed");exit(EXIT_FAILURE);}printf("recved from new_sock=%d : %s(%d length string)\n", i, buffer, recv_size);//立即将收到的内容写回去,并关闭连接if ( send(i, buffer, recv_size, 0) == -1 ) {perror("send failed");exit(EXIT_FAILURE);}printf("send to new_sock=%d done\n", i);if ( close(i) == -1 ) {perror("close failed");exit(EXIT_FAILURE);}printf("close new_sock=%d done\n", i);//将当前的socket从select的侦听中移除FD_CLR(i, &readfds_bak);}}}return 0;}
编译并运行如上程序,然后尝试使用多个telnet localhost 1984连接该server。可以发现各个connection很好地独立工作。因此,使用select可实现一个进程尽最大所能地处理尽可能多的client。
accept的阻塞与非阻塞相关推荐
- 阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端
承接上文的操作系统,关于IO会涉及到阻塞.非阻塞.多路复用.同步.异步.BIO.NIO.AIO等几个知识点.知识点虽然不难但平常经常容易搞混,特此Mark下,与君共勉. 1 阻塞跟非阻塞 1.1 阻塞 ...
- Socket阻塞,非阻塞,同步,异步
1.socket 阻塞,非阻塞(select) http://blog.csdn.net/piaojun_pj/article/details/5991968/ http://blog.chinaun ...
- python gevent模块 下载_Python协程阻塞IO非阻塞IO同步IO异步IO
Python-协程-阻塞IO-非阻塞IO-同步IO-异步IO 一.协程 协程又称为微线程 CPU 是无法识别协程的,只能识别是线程,协程是由开发人员自己控制的.协程可以在单线程下实现并发的效果(实际计 ...
- python epoll 并发_Python语言之python并发原理(阻塞、非阻塞、epoll)
本文主要向大家介绍了Python语言之python并发原理(阻塞.非阻塞.epoll),通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 在Linux系统中 01 阻塞服务端 特征: ...
- Socket阻塞与非阻塞,同步与异步、I/O模型
[原文链接] 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步: 所谓同步,就是在发出一 ...
- linux网络编程--阻塞与非阻塞
linux网络编程--阻塞与非阻塞 建立连接 接受连接 无阻塞的设置方式 read() write() 读操作 写操作 Linux fcntl函数详解 功能描述 函数原型 fcntl()函数五种功能 ...
- 从 Linux 源码看 Socket 的阻塞和非阻塞
转载自 从 Linux 源码看 Socket 的阻塞和非阻塞 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 大部分高性能网络框架采用的是非阻塞模式.笔者这 ...
- C++网络编程快速入门(三):阻塞与非阻塞式调用网络通信函数
目录 阻塞与非阻塞定义 send与recv connect 一些问题 为什么要将监听socket设置为非阻塞 阻塞与非阻塞定义 阻塞模式指的是当前某个函数执行效果未达预期,该函数会阻塞当前的执行线程, ...
- 【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型
文章目录 1. 基本概念 1.1 阻塞与非阻塞 1.2 同步与异步 1.3 为什么没有"异步阻塞" 2. 五种IO模型 2.1 阻塞 blocking 2.2 非阻塞 non-bl ...
- socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)
socket的阻塞模式和非阻塞模式 无论是Windows还是Linux,默认创建socket都是阻塞模式的 在Linux中,可以再创建socket是直接将它设置为非阻塞模式 int socket (i ...
最新文章
- 36岁自学python_Python语言基础
- Java文件下载详解
- Realtek 8192cu 支持 Android Hotspot
- Spring Boot 集成数据库
- axure 组件_技巧分享 | Axure后台组件制作的全过程
- html鼠标响应事件吗,学习JavaScript鼠标响应事件
- python如何操作excel 基础代码
- AJAX无刷新搜索,即输即得(未完善…)
- 双向链表list.h升序排序
- Python字符串格式化--formate()的应用
- ORACLE自增字段创建方法
- 操作系统服务:time时间模块+datetime模块
- linux开发之uboot移植 -- uboot简介
- Java基础---集合框架---迭代器、ListIterator、Vector中枚举、LinkedList、ArrayList、HashSet、TreeSet、二叉树、Comparator
- python条形码,Python中的远距离条形码
- attachEvent和addEventListener
- 【笔记1-4】陈丹琦毕业论文 NEURAL READING COMPREHENSION AND BEYOND
- 高盛最新调查:Python超过汉语成为未来最重要技能,你准备学哪种编程语言?...
- Go语言核心之美 2.4-布尔值
- 如何引入iconfont字体图片和网页标题logo
热门文章
- LVS的Tun模式(隧道模式)的实现
- 数据仓库搭建DWT层
- iframe是什么?html中iframe标签的用法详解
- 总结几点无线Mesh网络的优点
- Revit建模助手独门绝技,一阳指给构件“ 元素上色 ”
- 闲置电脑搭建一台linux服务器,在局域网内访问
- 以太网交换机可以家用吗_工业交换机的作用是什么?工业交换机可以家用吗?...
- Aspose.Slides for .NET V23 Crack
- 基于AT89S52单片机的蘑菇大棚环境监测系统论文(附录代码)
- Centos7 lvm