IO复用之poll

主要用一个例程来讲解poll,包含客户端和服务器端。

   poll函数没有FD_SETSIZE的限制

 

 int poll(struct pollfd * fdarray, unsigned long nfds, int timeout)

  第一个参数:IO数组
  第二个:检测的IO个数
  struct pollfd
  {
      int fd;//文件描述符
      short events;//请求的事件(感兴趣的事件)
      short revents;//返回的事件
  };

 

客户端程序:

  1 /*
  2 #include <poll.h>
  3 int poll(struct pollfd * fdarray,unsigned long nfds,int timeout)
  4 第一个参数:IO数组
  5 第二个:检测的IO个数
  6 struct pollfd
  7 {
  8     int fd;//文件描述符
  9     short events;//请求的事件
 10     short revents;//返回的事件
 11 };
 12 
 13 */
 14 #include<unistd.h>
 15 #include<sys/types.h>
 16 #include<sys/socket.h>
 17 #include<string.h>
 18 #include<stdlib.h>
 19 #include<stdio.h>
 20 #include<errno.h>
 21 #include<netinet/in.h>
 22 #include<arpa/inet.h>
 23 #include<signal.h>
 24 #include <sys/time.h>
 25 
 26 #define ERR_EXIT(m)\
 27     do\
 28     {\
 29         perror(m);\
 30         exit(EXIT_FAILURE);\
 31     }while(0)
 32 ssize_t readn(int fd,void *buf,size_t count)
 33 {
 34     size_t nleft=count;
 35     ssize_t nread;
 36     char *bufp=(char*)buf;
 37     while(nleft>0)
 38     {
 39         if((nread=read(fd,bufp,nleft))<0)
 40         {
 41             if(errno==EINTR)
 42                 continue;
 43             else
 44                 return -1;
 45         }
 46         else if(nread==0)
 47             return (count-nleft);
 48         bufp+=nread;
 49         nleft-=nread;
 50     }
 51     return count;
 52 }
 53 ssize_t writen(int fd, const void *buf, size_t count)
 54 {
 55     size_t nleft=count;
 56     ssize_t nwritten;
 57     char *bufp=(char*)buf;
 58     while(nleft>0)
 59     {
 60         if((nwritten=write(fd,bufp,nleft))<=0)
 61         {
 62             if(errno==EINTR)
 63                 continue;
 64             return -1;
 65         }else if(nwritten==0)
 66             continue;
 67         bufp+=nwritten;
 68         nleft-=nwritten;
 69     }
 70     return count;
 71 
 72 }
 73 ssize_t recv_peek(int sockfd,void *buf,size_t len)
 74 {
 75     while(1)
 76     {
 77         int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
 78         if(ret==-1&&errno==EINTR)
 79             continue;
 80         return ret;
 81     }
 82 }
 83 //偷窥方案实现readline避免一次读取一个字符
 84 ssize_t readline(int sockfd,void * buf,size_t maxline)
 85 {
 86     int ret;
 87     int nread;
 88     size_t nleft=maxline;
 89     char *bufp=(char*)buf;
 90     while(1)
 91     {
 92         ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
 93         if(ret<0)
 94             return ret;
 95         else if(ret==0)
 96             return ret;
 97         nread=ret;
 98         int i;
 99         for(i=0;i<nread;i++)
100         {
101             if(bufp[i]=='\n')
102             {
103                 ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
104                 if(ret!=i+1)
105                     exit(EXIT_FAILURE);
106                 return ret;
107             }
108         }
109         if(nread>nleft)
110             exit(EXIT_FAILURE);
111         nleft-=nread;
112         ret=readn(sockfd,bufp,nread);
113         if(ret!=nread)
114             exit(EXIT_FAILURE);
115         bufp+=nread;//移动指针继续窥看
116     }
117     return -1;
118 }
119 void echo_cli(int sock)
120 {
121 /*
122     char sendbuf[1024]={0};
123     char recvbuf[1024]={0};    
124     while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符
125     {
126         
127         writen(sock,sendbuf,strlen(sendbuf));
128         int ret=readline(sock,recvbuf,1024);
129         if(ret==-1)
130             ERR_EXIT("readline");
131         else if(ret==0)
132         {
133             printf("service closed\n");
134             break;
135         }
136         fputs(recvbuf,stdout);
137         memset(sendbuf,0,sizeof(sendbuf));
138         memset(recvbuf,0,sizeof(recvbuf));
139     }
140 */
141     char sendbuf[1024]={0};
142     char recvbuf[1024]={0};    
143     fd_set rset;
144     FD_ZERO(&rset);//初始化
145     int nready;//准备好的个数
146     int maxfd;
147     int fd=fileno(stdin);//防止STDIN_FILLENO被重定向
148     if(fd>sock)
149         maxfd=fd;
150     else 
151         maxfd=sock;
152     while(1)
153     {
154         FD_SET(fd,&rset);//循环中
155         FD_SET(sock,&rset);
156         nready=select(maxfd+1,&rset,NULL,NULL,NULL);
157         if(nready==-1)
158             ERR_EXIT("select error");
159         if(nready==0)
160             continue;
161         if(FD_ISSET(sock,&rset))
162         {
163             int ret=readline(sock,recvbuf,sizeof(recvbuf));
164             if(ret==-1)
165                 ERR_EXIT("readline error");
166             else if(ret==0)
167             {
168                 ERR_EXIT("serve closed");
169                 break;
170             }
171             fputs(recvbuf,stdout);
172             memset(recvbuf,0,sizeof(recvbuf));
173         }
174         if(FD_ISSET(fd,&rset))
175         {
176             if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
177                 break;
178             writen(sock,sendbuf,strlen(sendbuf));
179             memset(sendbuf,0,sizeof(sendbuf));
180         }
181     }
182     close(sock);
183     
184 }
185 void handle_sigpipe(int sig)
186 {
187     printf("recive a signal=%d\n",sig);
188 
189 }
190 int main(void)
191 {
192         signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
193         int sock;
194         if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
195             ERR_EXIT("socket error");
196     
197         struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
198         memset(&servaddr,0,sizeof(servaddr));
199         servaddr.sin_family=AF_INET;
200         servaddr.sin_port=htons(5188);
201     
202         servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
203         //inet_aton("127.0.0.1",&servaddr.sin_addr);
204     
205         if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
206             ERR_EXIT("connect");
207 
208         //利用getsockname获取客户端本身地址和端口,即为对方accept中的对方套接口
209         struct sockaddr_in localaddr;
210         socklen_t addrlen=sizeof(localaddr);
211         if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0)
212             ERR_EXIT("getsockname error");
213         printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));    
214         //使用getpeername获取对方地址
215     echo_cli(sock);//选择一个与服务器通信
216     return 0;
217 }

 

服务器端程序:

  1 /*
  2 #include <poll.h>
  3 int poll(struct pollfd * fdarray,unsigned long nfds,int timeout)
  4 第一个参数:IO数组
  5 第二个:检测的IO个数
  6 struct pollfd
  7 {
  8     int fd;//文件描述符
  9     short events;//请求的事件
 10     short revents;//返回的事件
 11 };
 12 
 13 */
 14 #include<unistd.h>
 15 #include<sys/types.h>
 16 #include<sys/socket.h>
 17 #include<string.h>
 18 #include<stdlib.h>
 19 #include<stdio.h>
 20 #include<errno.h>
 21 #include<netinet/in.h>
 22 #include<arpa/inet.h>
 23 #include<signal.h>
 24 #include<sys/wait.h>
 25 #include<poll.h>
 26 #define ERR_EXIT(m)\
 27     do\
 28     {\
 29         perror(m);\
 30         exit(EXIT_FAILURE);\
 31     }while(0)
 32 ssize_t readn(int fd,void *buf,size_t count)
 33 {
 34     size_t nleft=count;
 35     ssize_t nread;
 36     char *bufp=(char*)buf;
 37     while(nleft>0)
 38     {
 39         if((nread=read(fd,bufp,nleft))<0)
 40         {
 41             if(errno==EINTR)
 42                 continue;
 43             else
 44                 return -1;
 45         }
 46         else if(nread==0)
 47             return (count-nleft);
 48         bufp+=nread;
 49         nleft-=nread;
 50     }
 51     return count;
 52 }
 53 ssize_t writen(int fd, const void *buf, size_t count)
 54 {
 55     size_t nleft=count;
 56     ssize_t nwritten;
 57     char *bufp=(char*)buf;
 58     while(nleft>0)
 59     {
 60         if((nwritten=write(fd,bufp,nleft))<=0)
 61         {
 62             if(errno==EINTR)
 63                 continue;
 64             return -1;
 65         }else if(nwritten==0)
 66             continue;
 67         bufp+=nwritten;
 68         nleft-=nwritten;
 69     }
 70     return count;
 71 
 72 }
 73 ssize_t recv_peek(int sockfd,void *buf,size_t len)
 74 {
 75     while(1)
 76     {
 77         int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
 78         if(ret==-1&&errno==EINTR)
 79             continue;
 80         return ret;
 81     }
 82 }
 83 //偷窥方案实现readline避免一次读取一个字符
 84 ssize_t readline(int sockfd,void * buf,size_t maxline)
 85 {
 86     int ret;
 87     int nread;
 88     size_t nleft=maxline;
 89     char *bufp=(char*)buf;
 90     while(1)
 91     {
 92         ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
 93         if(ret<0)
 94             return ret;
 95         else if(ret==0)
 96             return ret;
 97         nread=ret;
 98         int i;
 99         for(i=0;i<nread;i++)
100         {
101             if(bufp[i]=='\n')
102             {
103                 ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
104                 if(ret!=i+1)
105                     exit(EXIT_FAILURE);
106                 return ret;
107             }
108         }
109         if(nread>nleft)
110             exit(EXIT_FAILURE);
111         nleft-=nread;
112         ret=readn(sockfd,bufp,nread);
113         if(ret!=nread)
114             exit(EXIT_FAILURE);
115         bufp+=nread;//移动指针继续窥看
116     }
117     return -1;
118 }
119 /*
120 signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);
121 
122 signal(SIGCHLD, SIG_IGN);
123 
124 因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。(Linux Only)
125 
126 对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
127 
128 signal(SIGPIPE, SIG_IGN);
129 
130 TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道,
131 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制,
132 一个端点无法获知对端的socket是调用了close还是shutdown.
133 
134 对一个已经收到FIN包的socket调用read方法,
135 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送).
136 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以,
137 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
138 
139 为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:
140 
141 signal(SIGPIPE, SIG_IGN);
142 
143 这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE. 程序便能知道对端已经关闭.
144 
145 */
146 void handle_sigchld(int sig)
147 {
148     
149     while(waitpid(-1,NULL, WNOHANG)>0)
150         ;
151         
152 }
153 void handle_sigpipe(int sig)
154 {
155     printf("recevie a sig=%d\n",sig);//打印,不退出服务器进程
156 }
157 int main(void)
158 {    
159     int count=0;//测试描述符限制
160 signal(SIGCHLD,handle_sigchld);//避免僵死进程
161 signal(SIGPIPE,handle_sigpipe);//忽略pipe信号。如果客户端关闭套接字close,而服务器端又调用一次write,服务器会接受到RST.如果服务器再调用write产生SIGPIPE
162     int listenfd;
163     if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
164         ERR_EXIT("socket error");
165     //本地协议地址赋给一个套接字
166     struct sockaddr_in servaddr;
167     memset(&servaddr,0,sizeof(servaddr));
168     servaddr.sin_family=AF_INET;
169     servaddr.sin_port=htons(5188);
170     servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址
171 
172     //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
173     int on=1;
174     if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
175         ERR_EXIT("setsockopt error");
176     //绑定本地套接字
177     if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
178         ERR_EXIT("bind error");
179     if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
180         ERR_EXIT("listen error");
181     
182     struct sockaddr_in peeraddr;//对方套接字地址
183     socklen_t peerlen=sizeof(peeraddr);
184     
185     //poll没有fd_set的限制。若进程中最多打开2048个文件描述符
186     struct pollfd client[2048];
187     int i=0;    
188     for(i=0;i<2048;i++)
189     {
190         client[i].fd=-1;//保存已连接套接字fd.
191     }
192     int conn;//已连接套接字(主动套接字)
193     int nready;//准备好的事件数,返回值
194     int maxi=0;//当前client数组中正在使用的最大下标值,关心的事件个数
195     client[0].fd=listenfd;
196     client[0].events=POLLIN;//只对监听套接口的可读事件感兴趣
197     while(1)
198     {    
199         nready=poll(client,maxi+1,-1);//[0,maxi]
200         if(nready==-1)
201         {
202             if(errno==EINTR)
203                 continue;
204             ERR_EXIT("select error");
205         }
206         if(nready==0)
207             continue;
208         //判断是不是监听套接口产生了事件
209         //返回的事件与上 POLLIN,如果等于1 表明监听套接字产生了可读事件
210         if(client[0].revents & POLLIN)
211         {
212             conn=accept(listenfd,(struct sockaddr *)&peeraddr,&peerlen);
213             if(conn==-1)
214                 ERR_EXIT("accept");
215             for(i=0;i<2018;i++)
216             {
217                 if(client[i].fd<0)
218                 {
219                     client[i].fd=conn;
220                     client[i].events=POLLIN;
221                     if(i>maxi)
222                         maxi=i;
223                     break;
224                 }
225             }
226             if(i==2048)
227             {    
228                 fprintf(stderr,"too many clients\n");
229                 exit(EXIT_FAILURE);
230             }
231             printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
232             printf("count=%d\n",++count);
233             
234             if(--nready<=0)
235                 continue;
236         }
237         //判断是不是已连接套接口产生了事件
238         for(i=1;i<=maxi;i++)
239         {
240             conn=client[i].fd;
241             if(conn==-1) continue;
242             if(client[i].revents & POLLIN)
243             {
244                 char recvbuf[1024]={0};
245                 int ret=readline(conn,recvbuf,1024);
246                 if(ret==-1)
247                     ERR_EXIT("readline");
248                 if(ret==0)
249                 {
250                     printf("client close\n");
251                     client[i].fd=-1;
252                     close(conn);
253                 }
254                 fputs(recvbuf,stdout);
255                 writen(conn,recvbuf,strlen(recvbuf));
256                 if(--nready<=0)
257                     break;
258             }
259         }
260     }
261     return 0;
262 }

Makefile文件

.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN= poll_cli  poll_ser
all:$(BIN)
%.o:%.c
    $(CC) $(CFLAGS)  -c $^ -o $@
clean:
    rm -f *.o $(BIN)

posted on 2018-01-04 21:24  wsw_seu  阅读(456)  评论(0编辑  收藏  举报

导航