poll函数与select函数的功能基本一样,其定义如下:
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明:
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因 此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;
timeout:是poll函数调用阻塞的时间,单位:毫秒;
poll() 函数不会受到socket描述符上的O_NDELAY标记和O_NONBLOCK标记的影响和制约,也就是说,不管socket是阻塞的还是非阻塞 的,poll()函数都不会收到影响;而select()函数则不同,select()函数会受到O_NDELAY标记和O_NONBLOCK标记的影 响,如果socket是阻塞的socket,则调用select()跟不调用select()时的效果是一样的,socket仍然是阻塞式TCP通讯,相 反,如果socket是非阻塞的socket,那么调用select()时就可以实现非阻塞式TCP通讯;
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<sys/wait.h> //*进程用的头文件*/ #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<poll.h> //包含poll的头文件 #define MAXLINE 1024 //通信内容的最大长度 #ifndef FD_SETSIZE #define FD_SETSIZE 25 //select最多能处理的文件描述符 #endif ssize_t readn(int fd, void *buf, size_t count) { ssize_t nleft=count; ssize_t nread; char *charbuf=(char*) buf; while(nleft>0) { nread=read(fd,charbuf,nleft); if(nread<0) { if(errno==EINTR) continue; return -1; } else if(nread==0) return count-nleft; charbuf +=nread; nleft=count-nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { ssize_t nleft=count; ssize_t nwrite; char *charbuf=(char*) buf; while(nleft>0) { nwrite=write(fd,charbuf,nleft); if(nwrite<0) { if(errno==EINTR) continue; return -1; } else if(nwrite==0) return count-nleft; charbuf +=nwrite; nleft=count-nwrite; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { int ret; while(1) { ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1&& errno==EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t len) { ssize_t nleft=len,nread; int ret; char* bufchar=buf; while(1) { ret=recv_peek(sockfd,bufchar,len); if(ret<0||ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufchar[i]==' ') { ret=readn(sockfd,bufchar,i+1); if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufchar,nread); if(ret!=nread) exit(EXIT_FAILURE); bufchar+=nread; } return -1; } int main() { int sock_fd,new_fd,fd;//sock_fd用于监听,new_fd用于连接 int maxi; struct pollfd client[FD_SETSIZE];//用于存放客户端描述符 int nready;//检测到的事件数 struct sockaddr_in srv_addr;//服务器的地址信息 struct sockaddr_in client_addr;//客户机的地址信息 int i,size; //地址结构数据的长度 fd_set rset,allset; char sendbuf[1024],recvbuf[1024]; memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); /*创建套接字*/ sock_fd=socket(AF_INET,SOCK_STREAM,0);//采用IPv4协议 if(sock_fd==-1) { perror("creat socket failed"); exit(1); } /*服务器地址参数*/ srv_addr.sin_family=AF_INET; srv_addr.sin_port=htons(3490); srv_addr.sin_addr.s_addr=htonl(INADDR_ANY); bzero(&srv_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 int on=1; //表示开启reuseaddr if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) //打开地址、端口重用 perror("setsockopt"); /*绑定地址和端口*/ if(bind(sock_fd,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))==-1) { perror("bind failed"); exit(1); } /*设置监听模式,等待客户机的监听*/ if((listen(sock_fd,5))==-1) { perror("listen failed"); exit(1); } int maxi=0; client[0].fd=sock_fd; //将监听套接字放在数组中 client[0].events=POOLIN; //感兴趣的事件为数据可读 for(i=1;i<FD_SETSIZE;i++) client[i].fd=-1; //描述符为-1表示空闲 //使用poll实现并发服务器 while(1) { nready=poll(client,maxi+1,-1); //超时时间为-1,表示阻塞直到检测到事件 if(nready==-1) { if(errno==EINTR) //因为信号中断退出 continue; perror("select "); } else if(nready==0) //超时 { continue; } if(client[0].revents & POLLIN) //监听套接口产生可读 { size=sizeof(struct sockaddr_in); new_fd=accept(sock_fd,(struct sockaddr*)&client_addr,&size); /*接受连接,采用非阻塞是的模式调用accep*/ if(new_fd==-1) { perror("accept failed"); //continue;//restart accept when EINTR } printf("server:got connection from IP= %s prot= %d ",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//连接成功,打印客户机IP地址和端口号 /*char *inet_nota(struct sockaddr_in in); 头文件: arpa/inet.h Winsock2.h 参数: 一个网络上的IP地址 返回值: 如果正确,返回一个字符指针,指向一块存储着点分格式IP地址的静态缓冲区(同一线程内共享此内存);错误,返回NULL。 uint31_t ntohs(uint32_t net32bitvalue); 头文件: #include<netinet/in.h> 把net32bitvalue有网络字节序转换为主机字节序。 */ if(send(new_fd,"Hello client,I am 192.168.229.125! ",50,0)==-1) //192.168.229.125为子进程IP,可更改 perror("send failed"); for(i=1;i<FD_SETSIZE;i++) //检测已连接套接字中是否有套接字产生
{
if(client[i].fd<0)
{ client[i].fd=new_fd; //将描述符保存在某一个空闲的位置 break; } if(i==FD_SETSIZE) //没有找到空闲的位置,即描述符个数达到上限 perror("too many client"); if(i>maxi) maxi=i; client[i].events=POLLIN; if(--nready<=0) //若检测到的套接口已经处理完,则继续用poll监听 continue; } for(i=1;i<=maxi;i++) { if((fd=client[i].fd)<0) continue; if(client[i].revents & POLLIN) //连接套接口产生事件 { memset(recvbuf,0,sizeof(recvbuf)); if((n=readline(fd,recvbuf,MAXLINE))==0) { printf("client closed "); close(fd); client[i].fd=-1; } else if(n==-1) perror("readline "); else { writen(fd,recvbuf,n); fputs(recvbuf,stdout); } if(--nready<=0) break; } } } }