IO多路转接之select

xiaoxiao2021-02-27  237

在完成I/O操作时,程序中完成真正I/O的时间可能只有少的一部分,而大部分时间都处于一个等的时间。比如,此时需要从一个套接字中读取数据read(socket, buf, BUFSIZE); 这个操作可能会一直阻塞,直到有数据从网络的另一端发送过来。等的时间过于长,这是I/O效率低下的真正原因。可能有人会提出让代码不要阻塞的等,可以进行非阻塞的等待,比如当read一个socket发现没有数据时,就不在等待,而去read其他的socket,进行一种轮询式的read。可是这种模式还是会进行read的这个操作,不过这时进行操作时不成功的话就去read其他socket,效率还是低下的。 为了解决上述问题,提出了I/O多路转接。它的做法是这样的,一次等多个文件描述符,当有一个或者多个文件描述符就绪,可以进行I/O操作时,便返回通知有哪些那些文件描述符可以I/O。 系统提供了select函数实现多路复用输入/输出模型,select系统调用可以监视多个文件描述符的状态变化。程序会停在select这里等待,直到被监视的文件描述符至少有一个的状态发生了变化。 函数的定义:

参数描述: nfds:要关心的文件描述符readfds:表示要监视文件描述符集中,所有文件描述符的读状态writefds:表示要监视文件描述符集中,所有文件描述符的写状态exceptfds:表示要监视文件描述符集中,所有文件描述符的异常状态timeout:监视多长时间, 当timeout被设置为0,表示以非阻塞方式等待;当timeout被设置为大于0的数字,则表示其等待时间,有秒和毫秒的区分                      struct timeval                     {                          long tv_sec;//秒数                          long tv_usec;//微秒数                     } 当timeout设置为NULL时,表示已阻塞方式等待。  返回值: 当监视的文件描述符集中有文件描述符就绪,则会返回一个大于0的数当监视的文件描述符没有任何一个就绪时,并且指定的时间已到,返回0当函数调用出错时,返回-1         fd_set: select()机制中提供一fd_set的 数据结构,可以理解为一个集合,实际上是一个位图,每一个特定位来标志相应大小文件描述符,这个集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成。 系统提供了四个宏函数对fd_set进行操作

FD_CLR用于将fd_set中fd对应的位关闭。 FD_ISSET判断fd是否在fd_set中 FD_SET将fd添加进fd_set中 FD_ZERO将fd_set清空 基于select实现的网络服务器和客户端 server:

#include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<sys/select.h> #include<error.h> #include<unistd.h> #include<sys/types.h> #define NUMS 1024 static void Usage(char* proc) { printf("Usage: %s [local_ip] [local_port]\n", proc); } int startup(char* ip, int port) { //创建文件特性 //AF_INET ipv4,SOCK_STREAM,基于字节流服务 int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 2; } printf("sock = %d\n", sock); struct sockaddr_in local; //确定地址协议类型 local.sin_family = AF_INET; //绑定端口 local.sin_port = htons(port); //绑定ip local.sin_addr.s_addr = inet_addr(ip); //绑定网络特性 if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) { perror("bind"); return 3; } //监听套接字 if (listen(sock, 10) < 0) { perror("listen"); return 4; } return sock; } //初始化文件描述符数组 void Init(int *fds) { int i = 0; for (; i < NUMS; i++) { fds[i] = -1; } } //将文件描述符数组所存储的文件描述符设置进文件描述符集 int Addfd(int *fds, fd_set *set) { int i = 0; int maxfd = fds[0]; for (; i < NUMS; i++) { if (fds[i] != -1) { FD_SET(fds[i], set); if (maxfd < fds[i]) { maxfd = fds[i]; } } } //返回最大的文件描述符 return maxfd; } int main(int argc, char* argv[]) { if (argc != 3) { Usage(argv[0]); return 5; } //获取监听到的套接字 int listen_sock = startup(argv[1], atoi(argv[2])); //创建一个辅助的空间(一个数组)用于保存 文件描述符信息 //fds中保存的文件描述符将会被监听读状态 int fds[NUMS]; //wfd中保存的文件描述符将会被监听写状态 int wfd[NUMS]; //创建文件描述符集 fd_set set; fd_set wset; //将数组初始化 Init(fds); Init(wfd); //监听listen_sock添加进数组 fds[0] = listen_sock; printf("listen_sock:%d\n", fds[0]); while (1) { //初始化文件描述符集 FD_ZERO(&set); FD_ZERO(&wset); int maxrfd = -1; int maxwfd = -1; //将存于数组中的文件描述符添加至文件描述符集 maxrfd = Addfd(fds, &set); maxwfd = Addfd(wfd, &wset); struct timeval timeread = { 2, 0 }; struct timeval timewrite = { 0, 0 }; if (maxrfd < maxwfd) maxrfd = maxwfd; //监视set中的读状态信息,监视wset中的写状态信息 int rres = select(maxrfd + 1, &set, &wset, NULL, &timeread); switch (rres) { case -1: { perror("selete"); break; } case 0: { printf("Timeout...\n"); break; } default: {//至少有一个文件描述符状态就绪 int i = 0; for (; i < NUMS; i++) { //此条件满足,说明客户端的连接已经就绪 if (i == 0 && fds[i] != -1 && \ FD_ISSET(fds[i], &set)) { struct sockaddr_in client; socklen_t len = sizeof(client); //接受监听到的套接字 int new_sock = accept(listen_sock, \ (struct sockaddr*)&client, &len); printf("client [%s] [%d]\n", \ inet_ntoa(client.sin_addr), \ ntohs(client.sin_port)); if (new_sock < 0) { perror("accept"); continue; } //连接套接字后,将其添加到数组中,他将被监听读状态就绪 for (i; i < NUMS; i++) { if (fds[i] == -1) { fds[i] = new_sock; break; } } } //监视到有文件描述符的读状态就绪,就可以执行读操作 else if (i != 0 && fds[i] != -1 && FD_ISSET(fds[i], &set)) { char buf[1024]; //从套接字读取信息到buf中 ssize_t s = read(fds[i], buf, sizeof(buf)-1); if (s > 0) { buf[s] = 0; printf("client# %s\n", buf); int j = 1; //从这个文件描述符中读取数据后,就可以监视它的写状态 for (; j < NUMS; j++) { if (wfd[j] == -1) { wfd[j] = fds[i]; fds[i] = -1; break; } } } else if (s == 0) { close(fds[i]); printf("客户端已经退出!\n"); fds[i] = -1; } else { perror("read"); close(fds[i]); fds[i] = -1; } } //监视到有文件描述符的写状态就绪,就可以进行写操作 if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset)) { char buf[1024]; printf("Please Enter# "); fflush(stdout); //从键盘输入信息到buf中 ssize_t _s = read(0, buf, sizeof(buf)-1); if (_s > 0) { buf[_s - 1] = 0; //发送信息到套接字 write(wfd[i], buf, strlen(buf)); //写完之后,就可等待其回复,监视其读状态 int j = 0; for (; j < NUMS; j++) { if (fds[j] == -1) { fds[j] = wfd[i]; wfd[i] = -1; break; } } } else { perror("read"); close(wfd[i]); wfd[i] = -1; break; } } } } } } return 0; } client:

#include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<unistd.h> #include<sys/types.h> #define NUMS 1024 static void Usage(char *proc) { printf("Usage %s [server_ip] [server_port]\n", proc); } void Init(int *fds) { int i = 0; for (; i < NUMS; i++) { fds[i] = -1; } } int Addfd(int *fds, fd_set *set) { int i = 0; int maxfd = fds[0]; for (; i < NUMS; i++) { if (fds[i] != -1) { FD_SET(fds[i], set); if (maxfd < fds[i]) { maxfd = fds[i]; } } } return maxfd; } int main(int argc, char* argv[]) { if (argc != 3) { Usage(argv[0]); return 1; } //创建套接字 int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 2; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); //将创建的套接字,连入指定的网络服务中,这里连到了服务器 if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) { perror("connect111"); return 3; } //创建一个辅助的空间(一个数组)用于保存 文件描述符信息 //wfd中保存的文件描述符将会被监听写状态 int wfd[NUMS]; //rfd中保存的文件描述符将会被监听读状态 int rfd[NUMS]; //初始化数组 Init(wfd); Init(rfd); //客户端先写,所以将sock添加进wfd中 wfd[0] = sock; //创建文件描述符集 fd_set rset; fd_set wset; char buf[1024]; while (1) { //清空文件文件描述符集 FD_ZERO(&rset); FD_ZERO(&wset); int maxrfd = -1; int maxwfd = -1; //将存于数组中的文件描述符添加至文件描述符集 maxrfd = Addfd(rfd, &rset); maxwfd = Addfd(wfd, &wset); struct timeval timeread = { 2, 0 }; struct timeval timewrite = { 0, 0 }; if (maxrfd < maxwfd) maxrfd = maxwfd; //监视rset中的读状态信息,监听wset中的写状态信息 int res = select(maxrfd + 1, &rset, &wset, NULL, &timeread); switch (res) { case -1: { perror("selete"); break; } case 0: { printf("Timeout...\n"); break; } default: { int i = 0; for (; i < NUMS; i++) { //写状态就绪 if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset)) { printf("Please Enter# "); fflush(stdout); //从键盘写入内容到缓冲区 ssize_t s = read(0, buf, sizeof(buf)-1); if (s >0) { buf[s - 1] = 0; printf("server# "); fflush(stdout); //将缓冲区内容通过套接字发送到服务器 write(wfd[i], buf, strlen(buf)); int j = 0; //写操作完成,应该进行读操作,监视其读操作 for (; j < NUMS; j++) { if (rfd[j] == -1) { rfd[j] = wfd[i]; wfd[i] = -1; break; } } } else { FD_CLR(wfd[i], &wset); close(wfd[i]); return 5; } } if (rfd[i] != -1 && FD_ISSET(rfd[i], &rset)) { //在从套接字中读取服务器的回应信息 ssize_t _s = read(sock, buf, sizeof(buf)-1); if (_s > 0) { buf[_s] = 0; printf("%s\n", buf); int j = 0; //读操作完成,应该进行写操作,监视其写操作 for (; j < NUMS; j++) { if (wfd[j] == -1) { wfd[j] = rfd[i]; rfd[i] = -1; break; } } } if (_s < 0) { FD_CLR(rfd[i], &rset); close(rfd[i]); perror("read"); return 4; } } } } } } return 0; }

转载请注明原文地址: https://www.6miu.com/read-8421.html

最新回复(0)