0731------Linux网络编程----------论封装的乐趣(select、poll、epoll 服务器模型的封装)

0.春阳语录,代码嵌套三层以上就是一坨垃圾。因此良好的编程风格从封装开始。

1.封装select服务器模型

  1.1 如何封装?将select需要的数据结构都封装成结构体,通过参数在函数之间传递,将固定的操作封装成相应的函数。

  1.2 封装后的程序:

    1.2.1 封装的头文件 select_t.h   

#ifndef __SELECT_T_H__
#define __SELECT_T_H__
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)

typedef struct{
    fd_set allset_;
    fd_set rset_;
    int clients_[FD_SETSIZE];
    int maxi_;
    int maxfd_;
    int nready_;
    int listenfd_;
    void (*handle_callback_)(int, char *buf); //有用户提供回调函数
}select_t;

void select_init(select_t *sel, int listenfd);
void select_set_callback(select_t *sel, void (*handler)(int, char *buf));
void select_do_wait(select_t *sel);
void select_handle_accept(select_t *sel);
void select_handle_data(select_t *sel);

#endif

    1.2.2 封装的select类函数 select_t.c

#include "select_t.h"
#include "network.h"
#include <assert.h>
//仅供本文件内部调用
void select_add_fd(select_t *sel, int fd); //将新连接的fd加入到监听集合 和 clients数组中
void select_del_fd(select_t *sel, int i); //将关闭的fd从监听集合和clients数组中基础,并关闭连接


void select_init(select_t *sel, int listenfd){
    sel->listenfd_ = listenfd;
    FD_ZERO(&sel->allset_);
    FD_ZERO(&sel->rset_);
    FD_SET(listenfd, &sel->allset_);
    int i;
    for(i = 0; i < FD_SETSIZE; i++){
        sel->clients_[i] = -1;
    }
    sel->maxi_ = -1;
    sel->maxfd_ = listenfd;
}

void select_set_callback(select_t *sel, void (*handler)(int, char*)){
    sel->handle_callback_ = handler;
}

void  select_do_wait(select_t *sel){
    sel->rset_ = sel->allset_;
    do{
        sel->nready_ = select(sel->maxfd_ + 1, &sel->rset_, NULL, NULL, NULL);
    }while(sel->nready_ == -1 && errno == EINTR);
}

void select_handle_accept(select_t *sel){
    if(FD_ISSET(sel->listenfd_, &sel->rset_)){
        int peerfd = accept(sel->listenfd_, NULL, NULL);
        if(peerfd == -1)
                ERR_EXIT("accept");
        select_add_fd(sel, peerfd);
    }
    sel->nready_--;
}

void select_handle_data(select_t *sel){
    if(sel->nready_ == 0)
        return;
    int i;
    char recvbuf[1024] = {0};
    for(i = 0; i < FD_SETSIZE; i++){
        if(sel->clients_[i] == -1)
           continue;
        int fd = sel->clients_[i];
        if(FD_ISSET(sel->clients_[i], &sel->rset_)){ //?这里用fd会出错
            int ret = readline(fd, recvbuf, 1024);
            if(ret == -1)
                ERR_EXIT("readline");
            else if(ret == 0){
                printf("client closed\n");
                select_del_fd(sel, i);
                continue;
            }
            sel->handle_callback_(fd, recvbuf);
        }
    }
}

void select_add_fd(select_t *sel, int fd){
    int i;
    for(i = 0; i < FD_SETSIZE; i++){
        if(sel->clients_[i] == -1){
            sel->clients_[i] = fd;
            if(i > sel->maxi_)
                sel->maxi_ = i;
            break;
        }
    }
    if(i == FD_SETSIZE){
        fprintf(stderr, "too many clients\n");
        close(fd);
        exit(EXIT_FAILURE);//!
    }
    FD_SET(fd, &sel->allset_);
    if(fd > sel->maxfd_)
        sel->maxfd_ = fd;
}

void select_del_fd(select_t *sel, int i){
    assert(i >= 0 && i < FD_SETSIZE); //!
    int fd = sel->clients_[i];
    sel->clients_[i] = -1;
    FD_CLR(fd, &sel->allset_);
    close(fd);
}

    1.2.3 封装后的mian函数 server.c

#include "network.h"
#include "select_t.h"
/*
 * 服务器端使用 select 模型
 *
 * 这里为服务器提供处理客户请求的方法
 * 服务器回调本函数
 */
void handler(int fd, char *buf){
    printf("recv data:%s", buf);
    writen(fd, buf, strlen(buf));
}

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
       ERR_EXIT("signal");
    int listenfd = listenfd_init();
    select_t sel;
    select_init(&sel, listenfd);
    select_set_callback(&sel, handler);
    while(1){
        select_do_wait(&sel);
        select_handle_accept(&sel);
        select_handle_data(&sel);
    }
    close(listenfd);
    return 0;
}

2.封装poll服务器模型

  2.1 封装后的头文件 poll_t.h

#ifndef __POLL_T_H__
#define __POLL_T_H__
#include <poll.h>
#include <assert.h>
#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)

typedef struct{
    struct pollfd clients_[2048];
    int maxi_;
    int nready_;
    int listenfd_;
    void (*handle_callback_)(int, char*);
}poll_t;

void poll_init(poll_t *pol, int listenfd, void (*handler)(int, char*));
void poll_do_wait(poll_t *pol);
void poll_handle_accept(poll_t *pol);
void poll_handle_data(poll_t *pol);

#endif

  2.2 封装的函数 poll_t.c

#include "poll_t.h"
#include "network.h"

void poll_add_fd(poll_t *pol, int fd);
void poll_del_fd(poll_t *pol, int i);

void poll_init(poll_t *pol, int listenfd, void(*handler)(int, char*)){
    pol->listenfd_ = listenfd;
    int i;
    for(i = 0; i < 2048; i++){
       pol->clients_[i].fd = -1;
    }
    pol->clients_[0].fd = listenfd;
    pol->clients_[0].events = POLLIN;
    pol->maxi_ = 0;
    pol->nready_ = 0;
    pol->handle_callback_ = handler;
}

void poll_do_wait(poll_t *pol){
    int nready;
    do{
        nready = poll(pol->clients_, pol->maxi_ + 1, -1);
    }while(nready == -1 && errno == EINTR);
    if(nready == -1)
        ERR_EXIT("poll");
    pol->nready_ = nready;
}

void poll_handle_accept(poll_t *pol){
    if(pol->clients_[0].revents & POLLIN){
        int peerfd = accept(pol->listenfd_, NULL, NULL);
        if(peerfd == -1)
            ERR_EXIT("accept");
        poll_add_fd(pol, peerfd);
        --pol->nready_;
    }
}

void poll_handle_data(poll_t *pol){
    if(pol->nready_ == 0)
        return;
    int i;
    char recvbuf[1024] = {0};
    for(i = 1; i <= pol->maxi_; i++){
        int peerfd = pol->clients_[i].fd;
        if(peerfd == -1)
            continue;
        if(pol->clients_[i].revents & POLLIN){
            int ret = readline(peerfd, recvbuf, 1024);
            if(ret == -1)
                ERR_EXIT("readline");
            else if(ret == 0){
                printf("client closed\n");
                poll_del_fd(pol, i);
                continue;
            }
            pol->handle_callback_(peerfd, recvbuf);
        }
    }
}

void poll_add_fd(poll_t *pol, int fd){
    int i;
    for(i = 0; i < 2048; i++){
        if(pol->clients_[i].fd == -1){
            pol->clients_[i].fd = fd;
            pol->clients_[i].events= POLLIN;
            if(i > pol->maxi_)
                pol->maxi_ = i;
            break;
        }
    }
    if(i == 2048){
        fprintf(stderr, "too many clients\n");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

void poll_del_fd(poll_t *pol, int i){
    assert(i >= 1 && i < 2048);//
    close(pol->clients_[i].fd);
    pol->clients_[i].fd = -1;
}

  2.3 封装后的main 函数 server.c

#include "network.h"
#include "poll_t.h"

void handler(int fd, char *buf){
    printf("recv data:%s", buf);
    writen(fd, buf, strlen(buf));
}

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
       ERR_EXIT("signal");
    int listenfd = listenfd_init();
    poll_t pol;
    poll_init(&pol, listenfd, handler);
    while(1){
        poll_do_wait(&pol);
        poll_handle_accept(&pol);
        poll_handle_data(&pol);
    }
    close(listenfd);
    return 0;
}

3.封装epoll服务器模型

  3.1 封装后的头文件 epoll_t.c

#ifndef __EPOLL_T_H__
#define __EPOLL_T_H__
#include <sys/epoll.h>
#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)

typedef struct{
    int epollfd_;
    struct epoll_event events_[2048];
    int listenfd_;
    int nready_;
    void (*handle_callback_)(int, char*);
}epoll_t;

void epoll_init(epoll_t *epo, int listenfd, void (*handler)(int, char*));
void epoll_do_wait(epoll_t *epo);
void epoll_handle(epoll_t *epo);
void epoll_close(epoll_t *epo);

#endif  

  3.2 封装后的epoll类函数 epoll_t.c

#include "epoll_t.h"
#include "network.h"

void epoll_handle_accept(epoll_t *epo);
void epoll_handle_data(epoll_t *epo, int peerfd);
void epoll_add_fd(epoll_t *epo, int fd);
void epoll_del_fd(epoll_t *epo, int fd);

void epoll_init(epoll_t *epo, int listenfd, void(*handler)(int, char*)){
    if((epo->epollfd_ = epoll_create(2048)) == -1)
        ERR_EXIT("epoll_create");
    epo->listenfd_ = listenfd;
    epoll_add_fd(epo, listenfd);
    epo->nready_ = 0;
    epo->handle_callback_ = handler;
}
void epoll_do_wait(epoll_t *epo){
    int nready;
    do{
        nready = epoll_wait(epo->epollfd_, epo->events_, 2048, -1);
    }while(nready == -1 && errno ==  EINTR);
    if(nready == -1)
        ERR_EXIT("epoll_wait");
    epo->nready_ = nready;
}

void epoll_handle(epoll_t *epo){
    int i;
    for(i = 0 ; i < epo->nready_; i++){
        int fd = epo->events_[i].data.fd;
        if(fd == epo->listenfd_){
            epoll_handle_accept(epo);
        }
        else
            epoll_handle_data(epo, fd);
    }
}

void epoll_handle_accept(epoll_t *epo){
    int peerfd = accept(epo->listenfd_, NULL, NULL);
    if(peerfd == -1)
        ERR_EXIT("accept");
    epoll_add_fd(epo, peerfd);
}

void epoll_handle_data(epoll_t *epo, int peerfd){
    char recvbuf[1024] = {0};
    int ret = readline(peerfd, recvbuf, 1024);
    if(ret == -1)
        ERR_EXIT("readline");
    else if(ret == 0){
        printf("client closed\n");
        epoll_del_fd(epo, peerfd);
        return;
    }
    epo->handle_callback_(peerfd, recvbuf);
}

void epoll_close(epoll_t *epo){
    close(epo->epollfd_);
    close(epo->listenfd_);
}

void epoll_add_fd(epoll_t *epo, int fd){
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN;
    if(epoll_ctl(epo->epollfd_, EPOLL_CTL_ADD, fd, &ev) == -1)
        ERR_EXIT("epoll_ctl");
}

void epoll_del_fd(epoll_t *epo, int fd){
    struct epoll_event ev;
    ev.data.fd = fd;
    if(epoll_ctl(epo->epollfd_, EPOLL_CTL_DEL, fd, &ev) == -1)
        ERR_EXIT("epoll_ctl");
}

  3.3 封装后的main函数 server.c

#include "network.h"
#include "epoll_t.h"

void handler(int fd, char *buf){
    printf("recv data:%s", buf);
    writen(fd, buf, strlen(buf));
}

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
       ERR_EXIT("signal");
    int listenfd = listenfd_init();
    epoll_t epo;
    epoll_init(&epo, listenfd, handler);
    while(1){
        epoll_do_wait(&epo);
        epoll_handle(&epo);
    }
    epoll_close(&epo);
    return 0;
}

 

 4.总结

  封装对一个程序的编写,调试来说都是至关重要的,封装后的代码可读性明显大大提高,因此,以后在写程序时,要注意这一点。此外,从封装这三种模型来看,epoll显然更简单方便,因此,以后在写服务器的时候要改select为epoll。

0731------Linux网络编程----------论封装的乐趣(select、poll、epoll 服务器模型的封装),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。