什么是IO多路复用?
举一个简单地网络服务器的例子,如果你的服务器需要和多个客户端保持连接,处理客户端的请求,属于多进程的并发问题,如果创建很多个进程来处理这些IO流,会导致CPU占有率很高。所以人们提出了I/O多路复用模型:一个线程,通过记录I/O流的状态来同时管理多个I/O,需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。
对select的理解
-
select 可以实现多路IO复用,是需要的线程数大大减少,减少了内存的开销和上下文切换CPU的开销
-
在一段指定的时间内,阻塞监听用户感兴趣的文件描述符上可读、可写和异常等事件。
-
select 只能得到有事件发生,但是不会得到具体事件对应的文件描述符
-
select 支持的文件描述符数量默认是1024。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds : 最大的文件描述符加1。
readfds: 用于检查可读的。
writefds:用于检查可写性。
exceptfds:用于检查异常的数据。一般情况下为NULL
timeout:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。
timeval结构的定义:
struct timeval{
long tv_sec; // seconds
long tv_usec; // microseconds
}
返回值: >0 是已就绪的文件句柄的总数, =0 超时, <0 表示出错,错误: errno
int FD_ZERO(fd_set *fdset); 一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); 清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set); 设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); 测试某个位是否被置位
经典案例:
这里只是对select的一个简单的运用
服务器端 server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 建立服务器端socket
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9000);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5); // 监听队列最多容纳5个
FD_ZERO(&readfds);// 把文件描述符清0
FD_SET(server_sockfd, &readfds); // 将服务器端socket加入到集合中
/* 输出
server waiting
adding client on fd 4
server waiting
serving client on fd 4
server waiting
serving client on fd 4
server waiting
removing client on fd 4
server waiting
^C 此时这里select又再阻塞等待事件的发生
*/
while (1){
char ch;
int fd;
int nread;
testfds = readfds; // 将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量
printf("server waiting\n");
// 无限期阻塞,并测试文件描述符变动
// result 是发生事件的文件描述符
// 参数:最大文件描述符+1 , 监听读事件,写事件,异常事件, FD_SETSIZE:系统默认的最大文件描述符
result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
if (result < 1){
perror("server5");
exit(1);
}
/*select和poll都只会告诉你,有事件发生,但是不会告诉你具体的文件描述符,所以需要扫描所有的文件描述符*/
for (fd = 0; fd < FD_SETSIZE; fd++){
// 判断是不是读套接字里面的
if (FD_ISSET(fd, &testfds))
{
/*判断是否为服务器套接字,若是则表示为客户请求连接。*/
if (fd == server_sockfd)
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
FD_SET(client_sockfd, &readfds); // 将客户端socket加入到集合中
printf("adding client on fd %d\n", client_sockfd);
}
else
{
// 客户端socket中有数据请求时
// 这里是fd 表示的是 client_sockfd
ioctl(fd, FIONREAD, &nread); // 取得数据量交给nread
/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
if (nread == 0)
{
// 客户端关闭也是一个读事件
close(fd);
FD_CLR(fd, &readfds); // 去掉关闭的fd
printf("removing client on fd %d\n", fd);
}
/*处理客户数据请求*/
else
{
read(fd, &ch, 1); // 读取字符
sleep(5);
printf("serving client on fd %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
return 0;
}
客户端
client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
int main()
{
int client_sockfd;
int len;
struct sockaddr_in address;//服务器端网络地址结构体
int result;
char ch = 'A';
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(9000);
result = connect(client_sockfd, (struct sockaddr*)&address, sizeof(address));
if (result == -1){
perror("oops: client2");
exit(1);
}
/* 输出
the first time: char from server = B
the second time: char from server = C
*/
//第一次读写
write(client_sockfd, &ch, 1);// A
read(client_sockfd, &ch, 1); // B
printf("the first time: char from server = %c\n", ch);
sleep(5);
//第二次读写
write(client_sockfd, &ch, 1);// A
read(client_sockfd, &ch, 1); // C
printf("the second time: char from server = %c\n", ch);
close(client_sockfd);
return 0;
}