.>应用程序中同时需要处理多路输入输出流时,若采用阻塞模式,将得不到预期的目的; .>若采用非阻塞模式,对多个输入进行轮训有太耗费时间; .>若设置多个进程分别处理一天数据通路,将产生新的进程同步通信问题,使程序更复杂; 比较好的方法就是采用多路复用,其基本思想就是: 》》》先创建一张有关描述符的的表,然后调用一个函数,当这些文件描述符中的一个或者多个已经准备好进行IO操作时函数才返回; 》》》函数返回时告诉进程哪个描述符已就绪,可以进行IO操作; 函数原型: int select(int nfds,fd_set *readset,fd_set *writeset,fd_set* exceptset,struct timeval *timeout); nfds: 第一个参数是:最大的文件描述符值+1; readset: 可读描述符集合; writeset: 可写描述符集合; exceptset: 异常描述符; timeout:select 的监听时长,如果这短时间内所监听的 socket 没有事件发生。 设置文件描述符的几个宏: FD_ZERO(fd_set *) 从fd_set中清除所有文件描述符; FD_SET(int fd, fd_set *) 将fd添加到fd_set中; FD_CLR(int fd, fd_set *) 将fd从fd_set中清除; FD_ISSET(int fd, fd_set *) 判断fd是否在fd_set中; 通俗点解释,就是将需要监听的文件描述符添加到fd_set这个文件描述符集合中,然后用宏函数FD_ISSET()判断该文件描述符是否存在,因为进行循环后只有有需要处理的文件描述符才会留在这个文件描述符集合中,否则就会被清除,在对相应描述符进行操作; //tcp 多路复用服务器端 #include "tcp.h" int main() { int socketID = 0; int maxFd = 0; int ret = 0; int newID = 0; fd_set readFds; int addrLength = 0; struct sockaddr_in addr; char buf[SIZE] = {0}; //init socket socketID = initSocket(); if (0 > socketID) { printf("socket init error\r\n"); return ERROR; } printf("init socket success\r\n"); addrLength = sizeof(addr); //多路复用 maxFd = socketID; FD_ZERO(&readFds); FD_SET(socketID, &readFds); while(1) { //设定:把所有要监听的描述符都放到监听集合中 fd_set tmp = readFds; ret = select(maxFd + 1, &tmp, NULL, NULL, NULL); if (0 > ret) { perror("select error"); return ERROR; } else if (0 == ret) { printf("select time out\r\n"); } else { //判断哪一个描述符可读 int i = 0; for (i = 0; i <= maxFd; i++) { if (FD_ISSET(i, &tmp)) { if (i == socketID) { memset(&addr, 0, addrLength); newID = accept(socketID, (struct sockaddr *)&addr, &addrLength); if (0 > newID) { perror("accept error"); return ERROR; } printf("client %d connected, IP=%s,port=%u\r\n", newID, (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); //把newID加入到监听描述符集合中 FD_SET(newID, &readFds); if (newID > maxFd) { maxFd = newID; } continue; }//if (i == socketID) //判断newID是否可读 memset(buf, 0, SIZE); ret = recv(i, buf, SIZE - 1, 0); if (0 > ret) { perror("recv error"); close(i); FD_CLR(i, &readFds); printf("client %d closed\r\n", i); } else if (0 == ret) { close(i); FD_CLR(i, &readFds); printf("client %d closed\r\n", i); } else printf("client %d said:%s\r\n", i, buf); }//if (FD_ISSET(i, &tmp)) }//for(i = 0; i <= maxFd; i++) }//else }//while(1) close(socketID); return 0; } int initSocket() { int socketID = 0; int addrLength = 0; int flag = 1; struct sockaddr_in addr; //创建socket socketID = socket(AF_INET, SOCK_STREAM, 0); if (socketID < 0) { perror("socket error"); return ERROR; } //设置地址可以重复绑定 if( setsockopt(socketID, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) { perror("setsockopt"); return ERROR; } //bind addrLength = sizeof (addr); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = INADDR_ANY; if (0 > bind(socketID, (struct sockaddr *)&addr, addrLength)) { perror("bind error"); return ERROR; } //listen if (0 > listen(socketID, LISTENNUM)) { perror("listen error"); return ERROR; } return socketID; } /*tcp 通信, 客户端*/ #include "tcp.h" int main() { //定义变量 int socketID = 0; int addrLength = 0; int ret = 0; struct sockaddr_in addr; char buf[SIZE] = {0}; //创建套接字 socketID = socket(AF_INET, SOCK_STREAM, 0); if (socketID < 0) { perror("socket error"); return -1; } //设定对方的IP/PORT addrLength = sizeof(addr); memset(&addr, 0, addrLength); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = inet_addr(IP); //发送连接请求 ret = connect(socketID, (const struct sockaddr *)(&addr), addrLength); if (ret < 0) { perror("connect error"); close(socketID); return -1; } printf("connect success\r\n"); //通信(发,收) while(1) { fgets(buf, SIZE - 1, stdin); ret = send(socketID, buf, strlen(buf), 0); if (ret < 0) { perror("send error"); close(socketID); return -1; } printf("send success %s,%d\r\n", buf, ret); if (strncmp(buf, "quit", 4) == 0) { break; } } //关闭套接字 close(socketID); return 0; } 程序可实现多客户端与服务器端进行连接,而服务器都可及时响应客户端,不会出现服务器 阻塞;
本文出自 “11303964” 博客,请务必保留此出处http://11313964.blog.51cto.com/11303964/1901141
原文地址:http://11313964.blog.51cto.com/11303964/1901141