select函数
/* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 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);//清空描述符集合
监视readfds查看读文件描述符集合会不会被堵塞(即使end-of-file,也是可读的)。
监视writefds查看写文件描述符集合会不会被堵塞。
监视exceptfd异常文件描述符集合是否出现了异常。主要用来读取OOB数据,异常并不是指出错。
注意当一个套接口出错时,它会变得既可读又可写。
如果有了状态改变,会将其他fd清零,只有那些发生改变了的fd保持置位,以用来指示set中的哪一个改变了状态。
参数n是所有set里所有fd里,具有最大值的那个fd的值加1
struct timeval {long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
timeval是从调用开始到select返回前,会经历的最大等待时间。
当timeval为 NULL时,会无限等待,直到被信号打断时返回1, errno 设置成 EINTR。
当timeval.tv_sec 为0并且 timeval.tv_usec为0,不等待立即返回。
当timeval.tv_sec非0 或者timeval.tv_usec非 0,等待特定时间长度, 超时返回0。
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> int main(void) { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(0,&rfds); tv.tv_sec=10; tv.tv_usec=0; int ret=select(1,&rfds,NULL,NULL,&tv); if(ret==-1) printf("error!\n"); else if(ret) { if(FD_ISSET(0,&rfds)) printf("fd0 data is avilable!\n"); } else printf("no data coming in 2 sec!\n"); return 0; }
用select对TCP客户端代码进行修改
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/select.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> #include<signal.h> #include <errno.h> ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*) buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*) buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while (1) { ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i = 0; i < nread; i++) { if (bufp[i] == '\n') { ret = readn(sockfd, bufp, i + 1); if (ret != i + 1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; } int main(int argc,char *argv[]) { signal(SIGPIPE,SIG_IGN); fd_set rset; FD_ZERO(&rset); struct sockaddr_in user; user.sin_family = AF_INET; user.sin_port = htons(8001); user.sin_addr.s_addr = inet_addr("127.0.0.1"); int sockfd,stdinfd,maxfd; char sendbuf[1024]= {0},revbuf[1024]= {0}; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd==-1) { printf("socket failed!\n"); exit(0); } stdinfd=fileno(stdin); maxfd = sockfd+1; if ( connect(sockfd, (struct sockaddr*) (&user), sizeof(user)) < 0) { perror("connect failed!\n"); exit(0); } for(;;) { FD_SET(stdinfd,&rset); FD_SET(sockfd,&rset); int ret=select(maxfd,&rset,NULL,NULL,NULL); if(ret==-1) printf("select failed!\n"); if(ret==0) continue; if(FD_ISSET(sockfd,&rset)) { int val=readline(sockfd,revbuf,sizeof(revbuf)); if(val==0) break; printf("client revmsg:"); fputs(revbuf,stdout); memset(revbuf,0,sizeof(revbuf)); } if(FD_ISSET(stdinfd,&rset)) { if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL) break; writen(sockfd,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } } close(sockfd); return 0; }
用select对服务器端代码进行修改
此时服务器端仅有一个监听套接字描述符
我们创建一个client数组用来表示连接池,并初始化为-1,观察读文件描述符的状态
当有第一个连接描述符申请连接时,监听套接字描述符状态变为可读
同时读文件描述符的状态更新,此时我们更改client数组对应描述符状态。
当第二个连接建立时,此时描述符的状态变化如下图
我们将第一个连接终止,此时客户端发送一个FIN,使得服务器描述符4变为可读,并把client数组对应的描述符4的内容更改为-1
将fork多进程并发服务器端代码改写单进程多连接池select模型
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while (1) { ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0; i<nread; i++) { if (bufp[i] == '\n') { ret = readn(sockfd, bufp, i+1); if (ret != i+1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; } void echo_srv(int conn) { char recvbuf[1024]; while (1) { memset(recvbuf, 0, sizeof(recvbuf)); int ret = readline(conn, recvbuf, 1024); if (ret == -1) ERR_EXIT("readline"); if (ret == 0) { printf("client close\n"); break; } fputs(recvbuf, stdout); writen(conn, recvbuf, strlen(recvbuf)); } } int main(int argc,char *argv[]) { char buf[1024]= {}; signal(SIGPIPE, SIG_IGN); int listenfd; if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in serv; bzero(&serv,sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(8001); serv.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&serv, sizeof(serv)) < 0) ERR_EXIT("bind"); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); struct sockaddr_in cli; socklen_t len=sizeof(cli); int connfd,i,maxi=0,maxfd=listenfd,client[FD_SETSIZE],nready; for(i=0; i<FD_SETSIZE; i++) { client[i]=-1; } printf("FD_SETSIZE max is %d\n",FD_SETSIZE); fd_set rset,allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(listenfd,&allset); for(;;) { rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) { printf("select error!\n"); } if(nready==0) continue; if(FD_ISSET(listenfd,&rset)) { connfd=accept(listenfd, (struct sockaddr*)&cli,&len); if(connfd==-1) { printf("connfd error!\n"); } for(i=0; i<FD_SETSIZE; i++) { if(client[i]==-1) { client[i]=connfd; if(i>maxi) maxi=i; break; } } if(i==FD_SETSIZE) printf("no more connect!\n"); FD_SET(connfd,&allset); if(connfd>maxfd) maxfd=connfd; if(--nready<=0) continue; } for(i=0; i<=maxi; i++) { if(client[i]==-1) continue; if(FD_ISSET(client[i],&rset)) { memset(buf,0,sizeof(buf)); int ret=readline(client[i],buf,sizeof(buf)); if(ret==-1) printf("read error!\n"); if(ret==0) { printf("client closed!\n"); client[i]=-1; FD_CLR(client[i],&allset); close(client[i]); } fputs(buf,stdout); writen(client[i],buf,strlen(buf)); if(--nready<=0) break; } } } return 0; }