我们先来看一下select的接口。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 12345 12345从上面的这些接口我们应该能有写认识,首先我么来看select系统调用的参数的含义。
参数 功能 nfds 被监听的文件描述符的总数。通常是文件描述符最大值加1 readfds 可读事件的文件描述符集合 writefds 可写事件的文件描述符集合 exceptfds 异常事件的文件描述符集合 timeout 设定超时时间值得注意的是,后面的三个参数即是输入型参数,又是输出型参数,输入表示关心那些文件描述符对应的特定事件发生。输出表示的是那些文件描述符对应的事件就绪。当就绪后,内核将会去修改这些文件描述符的集合。这三个参数都是fd_set结构体类型
typedef struct { /*XPG4.2requiresthismembername.Otherwiseavoidthename fromtheglobalnamespace.*/ #ifdef__USE_XOPEN __fd_maskfds_bits[__FD_SETSIZE/__NFDBITS]; #define__FDS_BITS(set)((set)->fds_bits) #else __fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS]; #define__FDS_BITS(set)((set)->__fds_bits) #endif }fd_set; 12345678910111213 12345678910111213可以看出fd_set就是一个结构体数组,这个结构体数组的每一位就是一个文件描述符的标记,配套提供了一些对于fd_set操作的宏。
void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); 1234 1234 宏 功能 FD_CLR 进行对应位fd FD_ISSET 进行判断对应位fd FD_SET 设置fd的对应位置 FD_ZERO 进行清空fd_set最后要说一下的就是timeout参数,这个参数用来设置超时时间,它也是一个结构体,它用来告诉应用程序select等待多久,这里的单位是微秒级别的。
timeout参数 说明 0 立即返回,即轮询 NULL 阻塞监视文件描述符,当有时间就绪才返回 大于0的时间 超时时间设置select调用时内核级别的,select的轮询方式是和非阻塞轮询方式是不同的,select的轮询方式是同时可以对多个I/O端口进行监听,任何一个端口数据好了,这个时候就可以读了。然后通过系统调用,就可以把数据从内核拷贝到用户进程。
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看,有宏FD_SETSIZE进行限制fd的数量。32位机默认是1024个。64位机默认是2048.2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
select代码示例:
#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<stdlib.h> #include<netinet/in.h> #include<string.h> #include<sys/time.h> static void Usage(const char *str) { printf("usage: %s [server_ip][server_port]\n",str); } static int startup(const char *ip,int port) { int new_socket = socket(AF_INET,SOCK_STREAM,0); if(new_socket < 0) { perror("socket"); exit(2); } int op = 1; int ret = setsockopt(new_socket,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(op)); if(ret < 0) { perror("setsockopt"); exit(3); } struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); serv_addr.sin_addr.s_addr = inet_addr(ip); ret = bind(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); if(ret < 0) { perror("bind"); exit(4); } ret = listen(new_socket,128); if(ret < 0) { perror("listen"); exit(5); } return new_socket; } int array_fds[1024]; //定义一个全局数组。这个全局变量存放的是文件描述符。 int max_fd; //最大的文件描述符。 int main(int argc ,char *argv[]) { if(argc != 3) { Usage(argv[0]); exit(1); } int i = 0; for(; i < 1024; ++i) array_fds[i] = -1; //将数组中每一个元素都置为-1. int listen_sock = startup(argv[1],atoi(argv[2]) ); array_fds[0] = listen_sock; //将监听套件字放在数组中0号位置。 fd_set reads; //创建只读集合。 fd_set writes; //创建只写集合。 struct timeval timeout; while(1) //服务器一直处于服务状态。 { FD_ZERO(&reads); //因为select是输入输出参数,当select返回时,已经改变了read这个集合了,下次还需要监听read这个集合中的可读事件的话就需要重新赋值。 FD_ZERO(&writes); //初始化。 max_fd = -1; //每次这个得重新初始化 timeout.tv_sec = 10; //定时10秒,这个参数也是输入输出参数 timeout.tv_usec = 0; for(i = 0; i < 1024; ++i) { if(array_fds[i] >= 0) { FD_SET(array_fds[i], &reads); //将监听套接字加入到可读事件中。 FD_SET(array_fds[i],&writes);//将监听套接字加入到可写事件中。 if(array_fds[i] > max_fd) max_fd = array_fds[i]; } } //准备工作做好后,开始真正的监听了。 int j = 0; switch(select(max_fd+1 ,&reads,&writes,NULL,&timeout)) { case 0: printf("time out....\n"); break; case -1: perror("select"); exit(6); default: //有可读事件发生,但是不知道是那一个可读事件,需要遍历数组,查看数组中存放的描述符那一个可读了。 for(; j < 1024; ++j) { if(array_fds[j] < 0) //-1表示这个文件描述符没有可读事件发生。 continue; char buf[BUFSIZ]; //接收数据缓冲区。 if(j== 0 && FD_ISSET(array_fds[0],&reads)) //监听套接字有可读事件发生,表示有客户连接了。 { struct sockaddr_in clie_addr; socklen_t len = sizeof(clie_addr); int connect_fd = accept(array_fds[0],(struct sockaddr*)&(clie_addr),&len); if(connect_fd < 0) { perror("accept"); continue; //这次连接失败,让它下次连接。 } printf("get a new client :(%s:%d)\n",inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port)); //连接套接字后继续监听,看这个套接字是不是有数据发送。 int k = 0; for(; k < 1024; ++k) { if(array_fds[k] == -1) { array_fds[k] = connect_fd; break; } } } else if(j != 0 && FD_ISSET(array_fds[j],&reads)) { printf("======================read start==========\n"); ssize_t s = read(array_fds[j],buf,sizeof(buf) - 1); if(s < 0) { perror("read"); close(array_fds[j]); array_fds[j] = -1; //数组重新利用 break; } else if(s == 0) { printf("clinet quit\n"); close(array_fds[j]); array_fds[j] = -1; //数组重新利用 break; } else { buf[s] = 0; printf("clinet say:%s\n",buf); // if(FD_ISSET(array_fds[j],&writes)) // { // write(array_fds[j],buf,strlen(buf)); // } } } // if(j != 0 && FD_ISSET(array_fds[j],&writes)) //套接字可写事件满足,说明可以将套接字中的数据发送出去了。 // { // printf("-------------------write j = %d ----------------start\n",j); // sleep(1); // write(array_fds[j],buf,strlen(buf)); // } }//for结束 } //switch结束 } //while(1)死循环 return 0; }
客户端用dup2重定向
#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<stdlib.h> #include<netinet/in.h> #include<string.h> #include<sys/time.h> #include<fcntl.h> #include<sys/stat.h> static void Usage(const char *str) { printf("usage: %s [server_ip][server_port]\n",str); } int main(int argc, char*argv[] ) { if(argc != 3) { Usage(argv[0]); exit(1); } int new_socket = socket(AF_INET,SOCK_STREAM,0); if(new_socket < 0) { perror("socket"); exit(2); } struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(atoi (argv[2]) ); serv_addr.sin_addr.s_addr = inet_addr(argv[1]); int ret = connect(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); if(ret < 0) { perror("connect"); exit(3); } char buf[BUFSIZ]; while(1) { printf("please enter#:"); fflush(stdout); ssize_t s = read(0,buf,sizeof(buf) - 1); if(s > 0) { int outfd = dup(1); //outfd 指向标志输出(保存标准输出好恢复)。 buf[s-1] = 0; //去掉换行符。 // write(new_socket,buf,strlen(buf)); dup2(new_socket, 1); //1号文件描述符去指向new_socket指向的内容了。 printf("%s",buf); //本来是将buf中的东西写入到标准输出中,但是现在1号描述符已经重新定向了,指向套接字的缓冲区,所以现在就是讲buf中的东西写入到套接字缓冲区了。 fflush(stdout); dup2(outfd, 1); //让1重新指向标准输出。 } else break; // ssize_t s2 = read(new_socket,buf,sizeof(buf) - 1); // buf[s2] = 0; // printf("sever echo # %s\n",buf); } close(new_socket); return 0; }