基于C++的Linux高性能服务器3

7 篇文章 0 订阅

上次写到将Epoll,socket,address都封装了一下,实际上进度还是相当慢的。照着敲肯定快,主要是还是要自己理解,再加上智能指针的自动析构特性在部分场景有点反向优化的坑,bug一改就是好久。

这次新增了一个Channel类,该类与一个文件描述符相关,不同的Channel负责不同的文件描述符,可以对不同的服务或者不同的事件类型做出不同的处理。

epoll中有个epoll_event结构体

typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
struct epoll_event {
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

里面的data成员是一个联合类型,可以储存一个指针,通过指针理论上可以指向任何一个地址,所以可以指向任何一个类型,从而达到针对不同类型实现不同处理的效果。

Channel类:

class Channel{
private:
    Epoll *ep;
    int fd;
    uint32_t events;
    uint32_t revents;
    bool inEpoll;
};

显然每个文件描述符会被分发到一个Epoll类,用一个ep指针来指向。类中还有这个Channel负责的文件描述符。另外是两个事件变量,events表示希望监听这个文件描述符的哪些事件,因为不同事件的处理方式不一样。revents表示在epoll返回该Channel时文件描述符正在发生的事件。inEpoll表示当前Channel是否已经在epoll红黑树中,为了注册Channel的时候方便区分使用EPOLL_CTL_ADD还是EPOLL_CTL_MOD。

使用的话,也是用智能指针来管理,然后设置监听读事件,当然也可以设置监听其他事件

pChannel servChannel(new Channel(ep.get(), servSock->getFd()));

servChannel->enableReading();

现在server代码:

int main() {
    pSocket servSock(new Socket());
    
    pInetAddress servAddr(new InetAddress("127.0.0.1", 8888));
    servSock->bind(servAddr.get());
    servSock->listen();
    
    pEpoll ep(new Epoll());
    servSock->setNonBlocking();
    pChannel servChannel(new Channel(ep.get(), servSock->getFd()));
    servChannel->enableReading();
    
    while (true) {
        std::vector<pChannel> activeChannel = ep->poll(-1);
        int nfds = activeChannel.size();
        for (int i = 0; i < nfds; i++) {
            int chfd = activeChannel[i]->getFd();
            if (chfd == servSock->getFd()) {       //新客户端连接
                pInetAddress clntAddr(new InetAddress());
                Socket *clntSock = new Socket(servSock->accept(clntAddr.get()));
                printf("new client fd %d! IP: %s Port: %d\n", clntSock->getFd(), 
                    inet_ntoa(clntAddr->getSocketaddr().sin_addr), ntohs(clntAddr->getSocketaddr().sin_port));
                clntSock->setNonBlocking();
                pChannel clntChannel(new Channel(ep.get(), clntSock->getFd()));
                clntChannel->enableReading();
            } else if (activeChannel[i]->getFd() & EPOLLIN) {        //可读事件
                handleReadEvent(activeChannel[i]->getFd());
            } else {
                printf("something else happened");
            }
        }
    }
    
    return 0;
}

void handleReadEvent(int sockfd) {
    char buf[READ_BUFFER];
    while (true) {          //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
        bzero(buf, sizeof(buf));
        ssize_t bytesRead = read(sockfd, buf, sizeof(buf));

        if (bytesRead > 0) {
            printf("message from clinet fd %d: %s\n", sockfd, buf);
            write(sockfd, buf, sizeof(buf));
        } else if (bytesRead == -1 && errno == EINTR) {     //客户端正常中断、继续读取
            printf("continue reading\n");
            continue;
        } else if (bytesRead == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {      //非阻塞IO,这个条件表示数据全部读取完毕
            printf("reading finished, errno:%d\n", errno);
            break;
        } else if (bytesRead == 0) {         //EOF,客户端断开连接
            printf("client fd: %d disconnected\n", sockfd);
            close(sockfd);      //关闭socket会自动将文件描述符从epoll树上移除
            break;
        }
    }
}

p开头的全为typedef定义的智能指针,这次遇到的主要问题是Epoll类里获取活动事件的函数poll

std::vector<pChannel> Epoll::poll(int timeout) const{
    std::vector<pChannel> activeEvents;
    int nfds = epoll_wait(epFd, events.get(), MAX_EVENTS, timeout);
    errif(nfds == -1, "epoll wait error");
    auto ptr = events.get();
    for(int i = 0; i < nfds; ++i){
        auto t = reinterpret_cast<Channel*>((ptr + i)->data.ptr);
        pChannel ch(t, [](Channel *p){});
        ch->setRevents((ptr + i)->events);
        activeEvents.emplace_back(ch);
    }
    return activeEvents;
}

该函数通过epoll_wait等待io事件发生,如果发生比如客户端连接或者读事件时则唤醒该函数,并返回事件数量,上文说到epoll里data结构里可以储存一个void*指针,如果要使用的话需要转换,说实话auto确实省事很多,可以不用过多纠结类型问题,但不能产生太大依赖。

前不久才看到《Effective STL》里说到谨慎使用储存指针类型的vector,因为要自己释放指针或者直接使用储存智能指针的vector上,这里问题就出在pChannel类型上,智能指针会自动析构,从而导致返回的vector activeEvents里空无一物,结果就是接收到了请求却无法处理,客户端无法连接,解决方法也很简单,要么就用普通指针,要么在构造ch的时候传入自定义的析构函数,这里用的lambda表达式,函数体为空,也就是什么都不干,说实话感觉真不如用普通指针,也就当练练手了,看来任何一条建议都要结合实际情况来看,不能盲从。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值