服务器调用select函数检测两种不同类型的输入事件
1、新的客户端请求到达,此时监听描述符准备好可以读了
服务器打开连接并将该客户添加到池里
2、一个已存在的客户端的已连接描述符准备好可以读了
服务器把来自每个已经准备好的已连接描述符的一个文本行回送回去
(当且仅当一个从该描述符读取一个字节的请求不会阻塞,描述符k就表示准备好可以读了)
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/select.h>
//robust IO
ssize_t rio_writen(int fd, void *userbuf, size_t n)
{
size_t nleft = n;
ssize_t nwrite;
char *bufp = (char*)userbuf;
while(nleft > 0)
{
if((nwrite = write(fd, bufp, nleft))<0)
{
if(errno == EINTR)
nwrite = 0;
else
return -1;
}
nleft -= nwrite;
bufp += nwrite;
}
return n;
}
//带缓存版本
static const int RIO_BUFSIZE = 8192;
struct rio_t
{
int rio_fd; //文件描述符
int rio_cnt; //未读字节数
char *rio_bufptr; //下一个未读字节
char rio_buf[RIO_BUFSIZE];//内部缓冲
};
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
ssize_t rio_read(rio_t *rp, void *userbuf, size_t n)
{
while(rp->rio_cnt <= 0)
{
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
if(rp->rio_cnt < 0)
{
if(errno != EINTR)
return -1;
}
else if (rp->rio_cnt == 0)
return 0;
else
rp->rio_bufptr = rp->rio_buf;
}
size_t cnt = n > rp->rio_cnt ? rp->rio_cnt : n;
memcpy(userbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
}
//读文本行,复制到内存位置userbuf,最后在结尾添NULL(0)
ssize_t rio_readlineb(rio_t *rp, void *userbuf, size_t maxlen)
{
ssize_t readn;
char c;
char *buf = (char*)userbuf;
int i;
for(i = 1; i < maxlen; ++i)
{
readn = rio_read(rp, &c, 1);
if(readn < 0)
return -1;
else if (readn == 0)
{
if(i == 1)
return 0;
else
break;
}
else
{
*buf++ = c;
if(c == '\n')
{
++i;
break;
}
}
}
*buf = 0;
return i-1;
}
//打开监听描述符
int open_listenfd(const char *port)
{
addrinfo hints, *listp, *p;
int listenfd, optval = 1;
memset(&hints, 0, sizeof(addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
hints.ai_flags |= AI_NUMERICSERV;
getaddrinfo(NULL, port, &hints, &listp);
for(p = listp; p; p = p->ai_next)
{
if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break;
close(listenfd);
}
freeaddrinfo(listp);
if(!p)
return -1;
if(listen(listenfd, 3) < 0)
{
close(listenfd);
return -1;
}
return listenfd;
}
int MAXLINE = 100;
struct Pool
{
int maxfd; //最大描述符
fd_set read_set; //读集合的描述符集合
fd_set ready_set; //准备好集合
int nready; //准备好描述符个数
int maxi; //客户端描述符数组最大下标
int clientfd[FD_SETSIZE]; //客户端描述符数组
rio_t clientrio[FD_SETSIZE];//客户端对应的读缓冲
};
void init_pool(int listenfd, Pool *p)
{
int i;
p->maxi = -1;
for(i = 0; i < FD_SETSIZE; ++i)
{
p->clientfd[i] = -1;//-1代表空槽位,刚开始没有请求客户端
}
//刚开始只有监听描述符
p->maxfd = listenfd;
FD_ZERO(&(p->read_set));
FD_SET(listenfd,&(p->read_set));
}
void add_client(int connfd, Pool *p)
{
int i;
p->nready--;
//找到空位,添加新客户端
for(i = 0; i < FD_SETSIZE; ++i)
{
if(p->clientfd[i]<0)
{
p->clientfd[i] = connfd;
rio_readinitb(&p->clientrio[i], connfd);
FD_SET(connfd, &p->read_set);
if(connfd > p->maxfd)
p->maxfd = connfd;
if(i > p->maxi)
p->maxi = i;
break;
}
//数组已经满了,FD_SETSIZE为1024
if(i == FD_SETSIZE)
perror("add_client error: Too many clients");
}
}
//服务器接收的总字节数
int byte_cnt = 0;
void check_clients(Pool * p)
{
int i, connfd, n;
char buf[MAXLINE];
rio_t rio;
for(i = 0; (i <= p->maxi) && (p->nready > 0); ++i)
{
connfd = p->clientfd[i];
rio = p->clientrio[i];
if((connfd > 0) && (FD_ISSET(connfd, &p->ready_set)))
{
p->nready--;
if((n = rio_readlineb(&rio, buf, MAXLINE)) != 0)
{
//回送文本行
byte_cnt += n;
printf("Server received %d (%d total) bytes on fd %D\n", n, byte_cnt, connfd);
rio_writen(connfd, buf, n);
}
else//已经从客户端读完文本行,检测到EOF,关闭连接
{
close(connfd);
FD_CLR(connfd, &p->read_set);
p->clientfd[i] = -1;
}
}
}
}
int main(int argc, const char * argv[])
{
// insert code here...
int listenfd, connfd;
socklen_t clientlen;
sockaddr_storage clientaddr;
static Pool pool;
if(argc!=2) //两个命令行参数,可执行文件名和端口号
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = open_listenfd(argv[1]); //打开监听描述符,argv[1]为端口号,这里选择8000
init_pool(listenfd, &pool);
while(1)
{
//select函数读入读集合,输出准备好集合,所以每次都要更新pool.ready_set,作为读集合输入
pool.ready_set = pool.read_set;
pool.nready = select(pool.maxfd+1, &pool.ready_set, NULL, NULL, NULL);
if(FD_ISSET(listenfd,&pool.ready_set))
{
clientlen = sizeof(sockaddr_storage);
connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen);
add_client(connfd, &pool);
}
check_clients(&pool);
}
}