一、
int select(int fds,fd_set *readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);select 监管多个I/O,检测遍历[0,fds)的描述符,select实现的服务器称为并发(非并行)服务器,多核cpu才有并行可同时检测标准出入和网络端口事件,不会因为阻塞在标准输入而无法处理网络数据 二、 可读:可读事件产生的4种情况(前三种) 套接口缓冲区有数据可读; 连接的读一半关闭,即接收到,读操作将返回0,通知select 如果是监听套接口(服务器),已完成连接队列不为空时; 套接口上发生一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取 可写:(第一种) 套接口发送缓冲区有空间容纳数据(不断产生) 连接的写一半关闭。对方关闭,不会发送数据过来,可以发送数据。第一次发送write,收到RST段。再次调用会产生SIGPIPE信号 套接口上发生一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取 异常: 套接口存在带外数据。
利用select改进回射服务器程序:
如图所示:fd最多FD_SETSIZE个,fd=3(listenfd)放进rset。接下来若监听套接口产生事件,那么建立连接,将conn放入rset中。同时更新maxfd。
服务器端改进:一个进程
#include#include #include #include #include #include #include #include #include #include #include #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; else 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);//从sockfd读取内容到buf,但不去清空sockfd,偷窥 if(ret==-1&&errno==EINTR) continue; return ret; }}//偷窥方案实现readline避免一次读取一个字符ssize_t readline(int sockfd,void * buf,size_t maxline){ int ret; int nread; size_t nleft=maxline; char *bufp=(char*)buf; while(1) { ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看 if(ret<0) return ret; else if(ret==0) return ret; nread=ret; int i; for(i=0;i nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//移动指针继续窥看 } return -1;}void handle_sigchld(int sig){ while(waitpid(-1,NULL, WNOHANG)>0) ; }int main(void){ signal(SIGCHLD,handle_sigchld); int listenfd; if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) ERR_EXIT("socket error"); //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) //本地协议地址赋给一个套接字 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) ERR_EXIT("setsockopt error"); //绑定本地套接字 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("bind error"); if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字) ERR_EXIT("listen error"); struct sockaddr_in peeraddr;//对方套接字地址 socklen_t peerlen; /* pid_t pid; while(1){ if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) ERR_EXIT("accept error"); //连接好之后就构成连接,端口是客户端的。peeraddr是对端 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) ERR_EXIT("fork"); if(pid==0){ close(listenfd); echo_srv(conn); //某个客户端关闭,结束该子进程,否则子进程也去接受连接 //虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。 exit(EXIT_SUCCESS); }else close(conn); }*/ int client[FD_SETSIZE];//select最大文件描述符,用来保存已连接文件描述符。单进程时conn只有一个。 int i=0; for(i=0;i maxi) maxi=i;//更新最大不空闲位置 break; } } if(i==FD_SETSIZE) { fprintf(stderr,"too many clents\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); FD_SET(conn,&allset);//将已连接套接字描述符放入allset,用于监测已连接套接口是否有客户端数据到来 if(conn>maxfd) maxfd=conn; if(--nready<=0) continue;//如果事件已经处理完,就继续循环监听,不再执行以下代码 } for(i=0;i<=maxi;i++)//小于等于 { conn=client[i]; if(conn==-1) continue; if(FD_ISSET(conn,&rset))//已经连接套接字是否有事件,不用while(1)循环处理客户端发送,有select监听。 { int ret; char recvbuf[1024]; memset(&recvbuf,0,sizeof(recvbuf)); ret=readline(conn,recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); else if(ret==0) { printf("client close\n"); FD_CLR(conn,&allset);//客户端关闭,select就不用去监听 client[i]=-1;//将已连接套接口数组重置为-1 } fputs(recvbuf,stdout); writen(conn,recvbuf,strlen(recvbuf)); if(--nready==0) break; } } } return 0;}