转载:http://www.jb51.net/article/101057.htm

1、基本概念

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

2、select函数

该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

返回值:就绪描述符的数目,超时返回0,出错返回-1

函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。

因为文件描述符是从0开始的。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

void FD_ZERO(fd_set *fdset);           //清空集合

void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中

void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除

int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

?
1
2
3
4
5
6
7
struct timeval{
      long tv_sec;  //seconds
      long tv_usec; //microseconds
};

这个参数有三种可能:

(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。

(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

 原理图:

3、测试程序

写一个TCP回射程序,程序的功能是:客户端向服务器发送信息,服务器接收并原样发送给客户端,客户端显示出接收到的信息。

服务端程序如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#define IPADDR   "127.0.0.1"
#define PORT    8787
#define MAXLINE   1024
#define LISTENQ   5
#define SIZE    10
typedef struct server_context_st
{
  int cli_cnt;    /*客户端个数*/
  int clifds[SIZE];  /*客户端的个数*/
  fd_set allfds;   /*句柄集合*/
  int maxfd;     /*句柄最大值*/
} server_context_st;
static server_context_st *s_srv_ctx = NULL;
/*===========================================================================
 * ==========================================================================*/
static int create_server_proc(const char* ip,int port)
{
  int fd;
  struct sockaddr_in servaddr;
  fd = socket(AF_INET, SOCK_STREAM,0);
  if (fd == -1) {
    fprintf(stderr, "create socket fail,erron:%d,reason:%s\n",
        errno, strerror(errno));
    return -1;
  }
  /*一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/
  int reuse = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
    return -1;
  }
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET,ip,&servaddr.sin_addr);
  servaddr.sin_port = htons(port);
  if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) {
    perror("bind error: ");
    return -1;
  }
  listen(fd,LISTENQ);
  return fd;
}
static int accept_client_proc(int srvfd)
{
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  cliaddrlen = sizeof(cliaddr);
  int clifd = -1;
  printf("accpet clint proc is called.\n");
ACCEPT:
  clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
  if (clifd == -1) {
    if (errno == EINTR) {
      goto ACCEPT;
    } else {
      fprintf(stderr, "accept fail,error:%s\n", strerror(errno));
      return -1;
    }
  }
  fprintf(stdout, "accept a new client: %s:%d\n",
      inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
  //将新的连接描述符添加到数组中
  int i = 0;
  for (i = 0; i < SIZE; i++) {
    if (s_srv_ctx->clifds[i] < 0) {
      s_srv_ctx->clifds[i] = clifd;
      s_srv_ctx->cli_cnt++;
      break;
    }
  }
  if (i == SIZE) {
    fprintf(stderr,"too many clients.\n");
    return -1;
  }
101 }
static int handle_client_msg(int fd, char *buf)
{
  assert(buf);
  printf("recv buf is :%s\n", buf);
  write(fd, buf, strlen(buf) +1);
  return 0;
}
static void recv_client_msg(fd_set *readfds)
{
  int i = 0, n = 0;
  int clifd;
  char buf[MAXLINE] = {0};
  for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {
    clifd = s_srv_ctx->clifds[i];
    if (clifd < 0) {
      continue;
    }
    /*判断客户端套接字是否有数据*/
    if (FD_ISSET(clifd, readfds)) {
      //接收客户端发送的信息
      n = read(clifd, buf, MAXLINE);
      if (n <= 0) {
        /*n==0表示读取完成,客户都关闭套接字*/
        FD_CLR(clifd, &s_srv_ctx->allfds);
        close(clifd);
        s_srv_ctx->clifds[i] = -1;
        continue;
      }
      handle_client_msg(clifd, buf);
    }
  }
}
static void handle_client_proc(int srvfd)
{
  int clifd = -1;
  int retval = 0;
  fd_set *readfds = &s_srv_ctx->allfds;
  struct timeval tv;
  int i = 0;
  while (1) {
    /*每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦*/
    FD_ZERO(readfds);
    /*添加监听套接字*/
    FD_SET(srvfd, readfds);
    s_srv_ctx->maxfd = srvfd;
    tv.tv_sec = 30;
    tv.tv_usec = 0;
    /*添加客户端套接字*/
    for (i = 0; i < s_srv_ctx->cli_cnt; i++) {
      clifd = s_srv_ctx->clifds[i];
      /*去除无效的客户端句柄*/
      if (clifd != -1) {
        FD_SET(clifd, readfds);
      }
      s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
    }
    /*开始轮询接收处理服务端和客户端套接字*/
    retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv);
    if (retval == -1) {
      fprintf(stderr, "select error:%s.\n", strerror(errno));
      return;
    }
    if (retval == 0) {
      fprintf(stdout, "select is timeout.\n");
      continue;
    }
    if (FD_ISSET(srvfd, readfds)) {
      /*监听客户端请求*/
      accept_client_proc(srvfd);
    } else {
      /*接受处理客户端消息*/
      recv_client_msg(readfds);
    }
  }
}
static void server_uninit()
{
  if (s_srv_ctx) {
    free(s_srv_ctx);
    s_srv_ctx = NULL;
  }
}
static int server_init()
{
  s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
  if (s_srv_ctx == NULL) {
    return -1;
  }
  memset(s_srv_ctx, 0, sizeof(server_context_st));
  int i = 0;
  for (;i < SIZE; i++) {
    s_srv_ctx->clifds[i] = -1;
  }
  return 0;
}
int main(int argc,char *argv[])
{
  int srvfd;
  /*初始化服务端context*/
  if (server_init() < 0) {
    return -1;
  }
  /*创建服务,开始监听客户端请求*/
  srvfd = create_server_proc(IPADDR, PORT);
  if (srvfd < 0) {
    fprintf(stderr, "socket create or bind fail.\n");
    goto err;
  }
  /*开始接收并处理客户端请求*/
  handle_client_proc(srvfd);
  server_uninit();
  return 0;
err:
  server_uninit();
  return -1;
}

客户端程序如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
#define max(a,b) (a > b) ? a : b
static void handle_recv_msg(int sockfd, char *buf)
{
printf("client recv msg is:%s\n", buf);
sleep(5);
write(sockfd, buf, strlen(buf) +1);
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
fd_set readfds;
int n;
struct timeval tv;
int retval = 0;
while (1) {
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
maxfdp = sockfd;
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(maxfdp+1,&readfds,NULL,NULL,&tv);
if (retval == -1) {
return ;
}
if (retval == 0) {
printf("client timeout.\n");
continue;
}
if (FD_ISSET(sockfd, &readfds)) {
n = read(sockfd,recvline,MAXLINE);
if (n <= 0) {
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
FD_CLR(sockfd,&readfds);
return;
}
handle_recv_msg(sockfd, recvline);
}
}
}
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
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,IPADDRESS,&servaddr.sin_addr);
int retval = 0;
retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if (retval < 0) {
fprintf(stderr, "connect fail,error:%s\n", strerror(errno));
return -1;
}
printf("client send to server .\n");
write(sockfd, "hello server", 32);
handle_connection(sockfd);
return 0;
}

4、程序结果

启动服务程序,执行三个个客户程序进行测试,结果如下图所示:

以上就是小编为大家带来的IO多路复用之select全面总结(必看篇)全部内容了,希望大家多多支持脚本之家~

IO多路复用之select全面总结(必看篇)相关推荐

  1. 聊聊IO多路复用之select、poll、epoll详解

    聊聊IO多路复用之select.poll.epoll详解 2016/04/22 · IT技术 · 1 评论 · epoll, IO多路复用, poll, select 分享到:0 本文作者: 伯乐在线 ...

  2. 【python】-- IO多路复用(select、poll、epoll)介绍及实现

    IO多路复用(select.poll.epoll)介绍及select.epoll的实现 IO多路复用中包括 select.pool.epoll,这些都属于同步,还不属于异步 一.IO多路复用介绍 1. ...

  3. IO多路复用中select、poll、epoll之间的区别

    本文来说下IO多路复用中select.poll.epoll之间的区别 文章目录 什么是IO多路复用 为什么有IO多路复用机制 同步阻塞(BIO) 同步非阻塞(NIO) IO多路复用(现在的做法) 3种 ...

  4. Python之进程+线程+协程(事件驱动模型、IO多路复用、select与epoll)

    文章目录 一.事件驱动模型 二.IO多路复用 本篇文章是关于涉及网络编程与协程.进程之间结合的内容,其中事件驱动模型.IO多路复用.select与epoll的使用等方面的知识 一.事件驱动模型 1.事 ...

  5. java 中传输文件的代码_java文件上传Demo(必看篇)

    说到文件上传我们要做到: 1.引入两个包:commons-fileupload-1.2.1.jar和commons-io-1.3.2.jar 2.将form改为上传文件模式:enctype=" ...

  6. Java基础通信_Java网络通信基础编程(必看篇)

    方式一:同步阻塞方式(BIO): 服务器端(Server): package com.ietree.basicskill.socket.mode1; import java.io.IOExceptio ...

  7. servers split sql_SQL中实现SPLIT函数几种方法总结(必看篇)

    例1 代码如下 create function f_split(@SourceSql varchar(8000),@StrSeprate varchar(10)) returns @temp tabl ...

  8. 一个牛人给Java初学者的建议(必看篇)

    给初学者之一:浅谈Java及应用学java 从不知java为何物到现在一个小小的j2ee项目经理虽说不上此道高手,大概也算有点斤两了吧每次上网,泡bbs逛论坛,没少去java相关的版 面总体感觉初学者 ...

  9. 初学者必看篇之国产数据库---达梦

    初学者必看篇之国产数据库-达梦 需要了解的可以点进去看看----达梦官网 首先,写这篇文章的目的是为了把学到的新知识做个总结梳理,同时我也希望在学习的过程中把一些需要注意的细节和大家分享,正所谓,共同 ...

最新文章

  1. c++中基本的语法问题
  2. Linux系统文件属性,什么是Linux系统的文件属性?
  3. 通过WiFi控制智能小车机器人制作过程详解
  4. 【Flink】Flink 1.9 升级 到 flink 1.12.4 报错 shaded netty4 AbstractChannel AnnotatedConnectException
  5. AX2012 学习自动生成编码
  6. 使用Ant定义生成文件
  7. Atitit.提升软件稳定性---基于数据库实现的持久化 循环队列 环形队列
  8. 泰迪杯A题通讯产品销售和盈利能力分析一等奖作品
  9. 第三天 引用类型选择结构循环结构【悟空教程】
  10. 程序人生 - 猫咪冷知识!猫咪“呼噜呼噜”声究竟代表什么?
  11. 使用navicat导入SQL语句的教程
  12. centos php 开启libgdgd_linux gd
  13. 对话线性资本郑灿:新一代AI公司的魅力与使命
  14. 【神经网络】正向传播和反向传播(结合具体例子)
  15. E01 GBase 8a MPP Cluster V95 安装和卸载
  16. 国内 Android 手机典型勒索软件详情分析(附解锁方法)
  17. 地铁调色,打造金属质感
  18. 计算机服务里打印设置在哪,电脑打印页面设置在哪
  19. Windows server 2012 R2服务器上的SqlServer数据库无法用公网IP远程访问
  20. c语言中的Null语句作用,null什么意思(c语言中的NULL是什么意思)

热门文章

  1. NOIP2017年11月9日赛前模拟
  2. Spring MVC-集成(Integration)-集成LOG4J示例(转载实践)
  3. apache camel 相关配置_使用apache camel从表中选择数据-问答-阿里云开发者社区-阿里云...
  4. 小米手机升级Android6,小米3能升级miui 6?小米3升级miui v6教程
  5. python自动补全库_这个库厉害了,自动补全Python代码,节省50%敲码时间
  6. android 自定义switch控件,Android中switch自定义样式
  7. 后面的参数_英特尔I系列CPU大家都知道,后面的参数你有没有了解过
  8. MongoDB 数组类型查询 —— $elemMatch 操作符
  9. 10位IT领袖给应届毕业生的10条忠告
  10. 8086标志操作指令