第一节
IO描述符的复用
-
select / poll /epoll 都能实现IO描述符的复用,select是最老旧的版本,描述符集合的描述符个数<1024,需要自己自己一个一个的找就绪的描述符,并且是不安全的;poll相比于select描述符个数不限制;epoll在poll基础上,增加了安全性
-
UDP发送数据没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,永远都不会阻塞。
-
linux中每个进程默认情况下,最多可以打开2*10=1024个文件,最多有1024个文件描述符
1.1 IO模型(了解)
UNⅨ/Linux下的4种I/O模型 | 阻塞IO 非阻塞IO 复用IO 信号驱动IO |
---|---|
非阻塞模式I/O | 1: 非阻塞模式是比较老旧的方案,请求的IO操作不能够立即完成,请马上返回一个错误给我。 2:使用了非阻塞模式,需要使用循环不停地测试文件描述符是否可以操作,我们称之为polling,是极其浪费资源的,这也是为什么非阻塞模式的使用是不普遍的,而阻塞式IO普遍受欢迎的原因 |
阻塞IO模式(一直等待) | 阻塞IO模式是最普遍使用的IO模式 例:读操作中的read、 recvfrom 例:写操作中的 write、send 例:socket中的:accept、 connect IO读阻塞的原理==当没有从缓冲区读到数据,当前进程就休眠,当缓冲区内有数据后,进程被唤醒,读取缓冲区的数据 IO写阻塞的原理 == 当缓冲区满了后,会阻塞,并进入休眠,当有空间可以写入时,再唤醒写入 |
复用IO | 1:构造能装多个描述符的盒子,当所有的描述符都在阻塞时,盒子关闭,当有一个描述符准备就绪时,盒子打开.可以进行IO操作;这个盒子我们称之为描述符集合 |
信号驱动IO | 1:进程要定义一个信号处理程序,系统可以自动捕获特定信号的到来,并启动I/O描述符。这是由内核通知用户何时可以启动一个I/O操作决定的 2:它是非阻塞的。当有就绪的数据时,内核就向该进程发送SIGIO信号。 无论我们如何处理SIGIO信号,这种模型的好处是当等待数据到达时,可以不阻塞。主程序继续执行,只有收到SIGIO信号时才去处理数据即可 |
文件描述符特点 | 1.非负整数 2.从最小可用的数字来分配 3.每个进程启动时默认打开0,1,2三个文件描述符 |
---|
第二节
2.1 select函数
- select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
- select函数判断描述符集合这个盒子里面有任意一个描述符准备就绪select函数结束,使用FD_ISSET( )来判断描述符是否准备就绪
|
---|
功能:调用后select函数会阻塞,直到有任意一个描述符就绪,或者超时,函数返回 我们在select函数返回后,我们可以通过FD_ISSET()遍历描述符集合,来找到就绪的描述符 |
select()函数对文件描述符进行了分类处理,主要涉及4个宏函数 注意:一般来说,在每次使用select()函数之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符 在select()函数返回后,可循环使用FD_ISSET()来测试描述符集,在执行完对相关文件描述符的操作后,使用FD_CLR()来清除描述符集。 |
使用select函数与FD_宏函数的基本流程 注意:在accept后需要再次将服务器描述符使用FD_SET加入到描述符队列 |
使用select函数通过对描述符的管理,来控制多个客户端与服务器的通信 1 =============================================================== 2 * Copyright (C) 2020 All rights reserved. 3 * 4 * 文件名称:ser.c 5 * 创 建 者:liujing 6 * 创建日期:2020年07月26日 7 * 描 述: 8 * 9 * 1:select函数可以实现IO描述服的复用,不采用进程或者线程,使用IO描述服复用的方法实现多个客户端接入服务器 10 * 11 * 更新日志: 12 * 13 ================================================================*/ 14 #include <stdio.h> 15 #include <string.h> 16 #include <stdlib.h> 17 #include <strings.h> 18 #include <netinet/in.h> 19 #include <netinet/ip.h> /* superset of previous */ 20 #include <sys/socket.h> 21 #include <sys/select.h> 22 /* According to earlier standards */ 23 #include <sys/time.h> 24 #include <sys/types.h> 25 #include <unistd.h> 26 27 void main() 28 { 29 //socket();接受网络描述符 30 int socketid = socket(AF_INET,SOCK_STREAM,0); 31 if(socketid == -1) 32 { 33 perror("socket error:"); 34 } 35 else 36 { 37 printf("\nsocket>>socketid == %d\n",socketid); 38 } 39 40 //bind(),绑定网络地址 41 struct sockaddr_in socketaddr; 42 socketaddr.sin_family = AF_INET; 43 socketaddr.sin_port = 5001; 44 if(inet_pton(AF_INET,"192.168.43.70",&socketaddr.sin_addr) == 1) 45 { 46 printf("\ninet_pton success\n"); 47 } 48 else 49 { 50 perror("\ninet_pton error\n"); 51 } 52 53 54 socklen_t socket_len = sizeof(socketaddr); 55 56 if(0 == bind(socketid, (struct sockaddr *)&socketaddr,socket_len)) 57 { 58 printf("bind success"); 59 } 60 else 61 { 62 perror("bind error"); 63 } 64 65 //listen();监听 66 if(0 == listen(socketid,5)) 67 { 68 printf("\nlisten success\n"); 69 } 70 else 71 { 72 perror("listen error"); 73 } 74 75 //描述符初始化 76 77 //建立读。写,错误的描述符集合 78 fd_set readids,writeids,exeptids; 79 80 struct timeval sockettime; 81 sockettime.tv_sec = 10; 82 sockettime.tv_usec = 0; 83 84 85 //描述符集合清空 86 FD_ZERO(&readids); 87 FD_ZERO(&writeids); 88 FD_ZERO(&exeptids); 89 90 //向描述符集合加入待监听的描述符 91 //FD_SET(0,&readids); 92 93 FD_SET(socketid,&readids); 94 95 int newfd; 96 int cli[5] = { 0}; //一个数组,专门存放客户端请求的socket描述符 97 int cli_sub = 0; //cli数组的下标/记录客户端连接个数 98 int ret; 99 char buf[32]; //服务器接受缓存区 100 char sendbuf[32] = "HELLO WORD"; //服务器发送的数据 101 102 int i; 103 while(1) 104 { 105 sleep(2); 106 //slect函数的作用是判断文件描述服是否空闲 107 switch(select(6,&readids,&writeids,NULL,NULL)) 108 { 109 case -1: 110 { 111 perror("select error"); 112 } 113 case 0: 114 { 115 printf("select outtime"); 116 } 117 default: 118 { 119 printf("join default"); 120 121 //判断是否客户端socket描述符为空闲状态 122 if(FD_ISSET(socketid,&readids) !=0) 123 { 124 printf("\n客户端准备ok\n"); 125 //网络描述符准备好了 126 newfd = accept(socketid,(struct sockaddr*)&socketaddr, &socket_len); 127 if(newfd == -1) 128 { 129 perror("accept error"); 130 } 131 else 132 { 133 printf("\n连接成功:newfd = %d\n",newfd); 134 135 //将服务器的描述符存储在cli数组 136 cli[cli_sub] = newfd; 137 138 //将描述服加入到描述符集合中 139 FD_SET(cli[cli_sub],&readids); 140 141 //客户端数+1.下标加1 142 cli_sub = cli_sub+1; 143 144 } 145 146 } 147 148 //重新装填客户端的描述符 149 FD_SET(socketid,&readids); 150 151 152 153 //轮训查找是否是客户端的描述符号准备就绪 154 for(i = 0;i<5;i++) 155 { 156 //查询是否是客户端i准备就绪 157 if(FD_ISSET(cli[i],&readids)!=0) 158 { 159 //客户端i的描述符准备就绪 160 printf("\n客户端%d准备就绪\n",i); 161 162 //读写操作 163 ret = read(cli[i],buf,32); 164 write(cli[i],sendbuf,32); 165 166 //打印 167 puts(buf); 168 } 169 } 170 171 break; //推出break 172 173 } 174 175 176 177 } 178 179 180 } 181 } 1 =============================================================== 2 * Copyright (C) 2020 All rights reserved. 3 * 4 * 文件名称:cli.c 5 * 创 建 者:liujing 6 * 创建日期:2020年07月25日 7 * 描 述: 8 * 9 * 更新日志: 10 * 11 ================================================================*/ 12 #include <stdio.h> 13 #include <string.h> 14 #include <stdlib.h> 15 #include <strings.h> 16 #include <sys/types.h> /* See NOTES */ 17 #include <sys/socket.h> 18 #include <netinet/in.h> 19 #include <netinet/ip.h> /* superset of previous */ 20 void main() 21 { 22 //1.获取网络套结字描述符 23 int socketid; 24 socketid = socket(AF_INET,SOCK_STREAM,0); 25 26 //AF_INET=IPv4 27 //SOCK_STREAM=socket流 28 //0=协议默认 29 if(socketid!=EOF) 30 { 31 printf("\n获取ID成功\n"); 32 } 33 else 34 { 35 perror("\n获取ID失败\n"); 36 } 37 38 //2.连接服务器 39 //socket网络地址赋值 --> For AF_INET see ip(7) 40 struct sockaddr_in sockaddr; //接受客户端的网络地址结构体 41 sockaddr.sin_family = AF_INET;//接受客户端的网络地址结构体的大小 42 sockaddr.sin_port = 5001; //5000~65535是用户段口号 43 //将点分十进制的IP地址转化为32位地址,存储在sockaddr.sin_addr 44 45 if(1 == inet_pton(AF_INET,"192.168.43.70",(void*)&sockaddr.sin_addr)) 46 { 47 printf("\n转换IP-->32位成功\n"); 48 } 49 else 50 { 51 perror("\n转化IP-->位失败\n"); 52 } 53 54 if(0 == connect(socketid,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) 55 { 56 printf("\nconnet function success\n"); 57 } 58 else 59 { 60 perror("connect funtion error"); 61 } 62 //3.通信 63 char buf[32] = "hello word"; 64 char getbuf[32] = { 0}; 65 int ret; 66 while(1) 67 { 68 write(socketid,buf,32); //注意:服务器的收发的字节大小应该保持一致 69 ret = read(socketid,getbuf,32); 70 puts(getbuf); 71 sleep(1); 72 73 if(ret == 0) 74 { 75 printf("\n退出循环\n"); 76 break; 77 } 78 79 } 80 close(socketid); 81 printf("\nend\n"); 82 } 83 =============================================================== 客户端1运行如下: farsight@ubuntu:~/Desktop$ ./cli 获取ID成功 转换IP-->32位成功 connet function success HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD =============================================================== 客户端2运行如下: farsight@ubuntu:~/Desktop$ ./cli 获取ID成功 转换IP-->32位成功 connet function success HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD HELLO WORD =============================================================== 服务器端运行结果如下: farsight@ubuntu:~/Desktop$ ./ser socket>>socketid == 3 inet_pton success bind success listen success 客户端准备ok 连接成功:newfd = 4 客户端0准备就绪 hello word 客户端0准备就绪 hello word 客户端0准备就绪 hello word 客户端0准备就绪 hello word 客户端准备ok 连接成功:newfd = 5 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪 hello word 客户端1准备就绪 hello word 客户端0准备就绪