IO多路复用并发服务器

服务器调用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);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值