从epoll构建muduo-2 最简单的epoll

mini-muduo版本传送门
version 0.00 从epoll构建muduo-1 mini-muduo介绍
version 0.01 从epoll构建muduo-2 最简单的epoll
version 0.02 从epoll构建muduo-3 加入第一个类,顺便介绍Reactor
version 0.03 从epoll构建muduo-4 加入Channel
version 0.04 从epoll构建muduo-5 加入Acceptor和TcpConnection
version 0.05 从epoll构建muduo-6 加入EventLoop和Epoll
version 0.06 从epoll构建muduo-7 加入IMuduoUser
version 0.07 从epoll构建muduo-8 加入发送缓冲区和接收缓冲区
version 0.08 从epoll构建muduo-9 加入onWriteComplate回调和Buffer
version 0.09 从epoll构建muduo-10 Timer定时器
version 0.11 从epoll构建muduo-11 单线程Reactor网络模型成型
version 0.12 从epoll构建muduo-12 多线程代码入场
version 0.13 从epoll构建muduo-13 Reactor + ThreadPool 成型

mini-muduo v 0.01版本,这是mini-muduo的第一个版本,整个程序是一个100行的epoll示例

下面粘贴的代码省略了头文件引用,完整可运行的示例可从github下载,使用命令git checkout v0.01可切换到此版本,在线浏览到这里

#define MAX_LINE 100
#define MAX_EVENTS 500
#define MAX_LISTENFD 5

int createAndListen()
{
    int on = 1;
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    fcntl(listenfd, F_SETFL, O_NONBLOCK); //no-block io
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(11111);
    
    if(-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
    {
        cout << "bind error, errno:" << errno << endl; 
    }

    if(-1 == listen(listenfd, MAX_LISTENFD))
    {
        cout << "listen error, errno:" << errno << endl; 
    }
    return listenfd;
}

int main(int args, char** argv)
{
    struct epoll_event ev, events[MAX_EVENTS];
    int listenfd,connfd,sockfd;
    int readlength;
    char line[MAX_LINE];
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(struct sockaddr_in);
    int epollfd = epoll_create(1);
    if (epollfd < 0)
        cout << "epoll_create error, error:" << epollfd << endl;
    listenfd = createAndListen();
    ev.data.fd = listenfd;
    ev.events = EPOLLIN;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);

    for(;;)
    {
        int fds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if(fds == -1)
        {
            cout << "epoll_wait error, errno:" << errno << endl; 
            break;
        }
        for(int i = 0; i < fds; i++)
        {
            if(events[i].data.fd == listenfd)
            {
                connfd = accept(listenfd, (sockaddr*)&cliaddr, (socklen_t*)&clilen);
                if(connfd > 0)
                {
                    cout << "new connection from " 
                         << "[" << inet_ntoa(cliaddr.sin_addr) 
                         << ":" << ntohs(cliaddr.sin_port) << "]" 
                         << " accept socket fd:" << connfd 
                         << endl;
                }
                else
                {
                    cout << "accept error, connfd:" << connfd 
                         << " errno:" << errno << endl; 
                }
                fcntl(connfd, F_SETFL, O_NONBLOCK); //no-block io
                ev.data.fd = connfd;
                ev.events = EPOLLIN;
                if( -1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev))
                    cout << "epoll_ctrl error, errno:" << errno << endl;
            }
            else if(events[i].events & EPOLLIN)
            {
                if((sockfd = events[i].data.fd) < 0)
                {
                    cout << "EPOLLIN sockfd < 0 error " << endl;
                    continue;
                }
                bzero(line, MAX_LINE);
                if((readlength = read(sockfd, line, MAX_LINE)) < 0)
                {
                    if(errno == ECONNRESET)
                    {
                        cout << "ECONNREST closed socket fd:" << events[i].data.fd << endl;
                        close(sockfd);
                    } 
                }
                else if( readlength == 0)
                {
                    cout << "read 0 closed socket fd:" << events[i].data.fd << endl;
                    close(sockfd);
                }
                else
                {
                    if(write(sockfd, line, readlength) != readlength)
                        cout << "error: not finished one time" << endl;
                }
            }
        }
    }
    return 0;
}

程序说明:

1 使用epoll的三个函数

int epoll_create(int size) 创建一个epoll文件描述符

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 将socket描述符加入/移出epoll监听,修改注册事件

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 在epoll描述符上等待事件的发生,并获得事件

2 epoll编程最基本模型

伪代码

epollfd = epoll_create(...)                     //创建epoll描述符
listenfd = socket(...)                          //创建用于端口监听的socket
bind(listenfd ...)                              //绑定
listen(listenfd ...)                            //开始在端口监听
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd...)  //将listenfd加入epoll描述符监听
for(;;)                                         //无限循环
{
    n = epoll_wait(...)                         //等待epoll描述符的事件发生
    for(0 ~ n)                                  //可能有多个读写事件发生,遍历所有事件
    {
        if(events[i].data.fd == listenfd)       //通过发生事件的socket描述符确认有新链接,
        {                                       //而非已经打开的socket的读写事件
            connfd = accpet(...)                //accept新连接为connfd
            epoll_ctl(...)                      //connfd加入epoll监听,像上面listenfd一样
        }
        else if(events[i].events & EPOLLIN)     //发生事件的socket不是listenfd而是connfd
        {                                       //且事件集里有EPOLLIN表明有数据要读取
            n = read(...)                       //读取数据
            if(n == 0)                          //读到0字节,需要关闭socket
                close(connfd)                   //close会自动将connfd从epoll监听中删除
        }                                       //无需调用epoll_ctl(..EPOLL_CTL_DEL..)
        else if(...)                            //其他各种事件处理依次处理
        ...    }                                            
} 
3 程序非常简陋,虽然能运行,但是问题多多,只处理了读事件,没有输入输出缓冲区,整个代码写在一个巨大的for循环中,这些在后续版本逐一改进。

4 83行的bzero是从UNP(第一卷P6)里学来的,因为memset的三个参数总是容易搞混。

5 41行和72行虽然只注册了EPOLLIN事件,但是有另外两个事件会自动被注册:EPOLLERR和EPOLLHUP, man epoll_ctl可以看到相关说明。

6 54行和76行根据发生事件的socket描述符结合events里的事件判断应该如何进行下一步处理,如果socket描述符为listenfd,就要accpet新连接,否则发生事件的socket就是connfd,需要判断发生了哪些事件,后续调用read还是write,本例只调用了read

7 89行和95行两处调用了close,后续会详细解释原因

    1 read返回-1,且错误码为ECONNRESET。

    2 read返回0。 

8 10行和70行将socket设置为no-blocking模式,加入epoll的socket要先设置为no-blocking

9 11行中用于监听端口的listenfd被设置为SO_REUSEADDR

10 cout如果链式链调用有可能发生线程切换导致输出被分割,不过目前例子是单线程,暂时忽略这个问题。

你可能感兴趣的:(linux,socket,epoll,网络编程,muduo)