github代码:
https://github.com/NICK-DUAN/Three-U/tree/master/poll_server
代码编写
poll服务器的编写上,就不能直接在代码上做文章了,需要先了解一下poll函数中的几个API和参数。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
先说返回值,返回值以及timeout这个参数和select的几乎一样,timeout使用单位的是毫秒,表示当前服务器对一个请求最长等待多长时间,nfds表示监听事件集合的大小,定义如下:
typedef unsigned long int nfds_t
至于struct pollfd *fds,这个才是poll的核心所在,系统的定义为:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fd表示的当然就是每个文件描述符,events和revents是一系列的位表示法,events表示的是,我们需要监听同一结构体的fd的什么事件,revents由内核修改,表示的是当前结构体中的fd上究竟发生了事件。其中,events和revents可以表示的事件如下所示:
POLLIN There is data to read.
POLLPRI There is urgent data to read (e.g., out-of-band data on TCP socket; pseudoterminal master in packet
mode has seen state change in slave).
POLLOUT Writing is now possible, though a write larger that the available space in a socket or pipe will
still block (unless O_NONBLOCK is set).
POLLRDHUP tream socket peer closed connection, or shut down writing half of connection. The _GNU_SOURCE fea‐ture test macro must be defined
POLLERR Error condition (only returned in revents; ignored in events).
POLLHUP Hang up (only returned in revents; ignored in events). Note that when reading from a channel such
as a pipe or a stream socket, this event merely indicates that the peer closed its end of the chan‐el. Subsequent reads from the channel will return 0 (end of file) only after all outstanding data in the channel has been consumed.
POLLNVAL Invalid request: fd not open (only returned in revents; ignored in events).
POLLRDNORM Equivalent to POLLIN.
POLLRDBAND Priority band data can be read (generally unused on Linux).
POLLWRNORM Equivalent to POLLOUT.
POLLWRBAND Priority data may be written.
对应到中文就是:
所以我们在编写代码时只需要设置一个struct pollfd的数组即可,这个数组可以告诉系统我们需要监听那个套接字,我们需要监听这个套接字的什么事件,而这个套接字上又具体发生了什么事件,而不用像select那样,每次发生事件之后,设定一个文件描述符到全部数据中,等待下一次的遍历发现并处理,并且在使用完之后重置这个文件描述符,这都是比较耗费时间的。
#include <stdio.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SIZE 128
void usage(char* str)
{
printf("%s [local_ip] [local_port]\n",str);
}
int startup(const char* ip,const char* port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("sockek");
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(atoi(port));
local.sin_addr.s_addr=inet_addr(ip);
if(bind (sock,(struct sockaddr*)&local,sizeof(local))<0 )
{
perror("bind");
exit(3);
}
if(listen(sock,10)<0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(1);
}
int listen_sock=startup(argv[1],argv[2]);
int i=0;
int max=0;
int timeout=3000;
struct pollfd readfds[SIZE];
for(i=0;i<SIZE;i++){
readfds[i].fd=-1;
readfds[i].events=0;
readfds[i].revents=0;
}
依旧是初始化,需要将readfds的套接字,监听与发生事件均初始化,否则在之后的代码编程中会有不必要的错误发生。
readfds[0].fd=listen_sock;
readfds[0].events = POLLIN;
并且将监听套接字放在第一位,保证监听套接字的存在,这样才能在进入后判断是否一个已经到来。
while(1){
int ret = poll(readfds,SIZE,timeout);
switch(ret){
case -1:
perror("poll");
break;
case 0:
printf("timeout...\n");
break;
default:{
for(i=0;i<SIZE;i++){
if(i==0 && ( (readfds[i].revents) & POLLIN)){//listen_sock ready
此处使用revents的原因时,在poll函数时使用的时events表示需要监听当前套接字的事件,当poll被成功触发,就将当前套接字上实际发生了什么事件写在了revents上,所以此处的判定使用revents。表示监听套接字上发生可读事件,意味着有连接到来。
struct sockaddr_in client;
socklen_t len=sizeof(client);
int new_sock=accept(readfds[i].fd,(struct sockaddr*)&client,&len);
if(new_sock<0){
perror("accept");
break;
}
printf("get a client:[%s__%d]\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
int j=1;
for(;j<SIZE;j++){
if(readfds[j].fd==-1){
readfds[j].fd=new_sock;
readfds[j].events=POLLIN;
break;
}
}if(j==SIZE){
printf("connect full...\n");
close(new_sock);
}
这些代码同select相似,也是在找到一个合适的位置来表示当前的文件描述符。
}else if(i!=0 && ( (readfds[i].revents) & POLLIN)){//client ready
char buff[1024];
ssize_t s=read(readfds[i].fd,buff,sizeof(buff)-1);
if(s>0){
buff[s]=0;
printf("client say# %s",buff);
write(readfds[i].fd,buff,sizeof(buff)-1);
}else if(s==0){
printf("client quit...\n");
close(readfds[i].fd);
readfds[i].fd=-1;
}
else{
perror("read");
}
}//end of elif
}//end of for
}//end of default
}//end of switch
}//end of while
return 0;
}
其实,完完全全的写一次select代码,再将poll中的struct pollfd搞明白,以及为什么使用的是revents,那么poll服务器你已经懂了70%了。