使用epoll实现简单的服务器

1. 头文件

#ifndef __TCP_SERVER_H__
#define __TCP_SERVER_H__
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <iostream>
#include <memory>
#include <thread>
#include <string>
#include <atomic>
using namespace std;

class tcp_server
{
public:
    class tcp_notify
    {
    public:
        virtual size_t on_recv_data(const unsigned int clientid, const char* buf, const size_t len) = 0;
     };

public:
    tcp_server(tcp_notify& notify);
    virtual ~tcp_server();
public:
    void start(const string& port);
    void stop();

private:
    int create_and_bind (const char *port);
    int make_socket_non_blocking (int sfd);

    void thread_func();
private:
    tcp_notify& m_notify;

    int sfd;
    int efd;
    static const int MAXEVENTS = 64;
    struct epoll_event *events;

    atomic<bool> m_thread_state_;
    shared_ptr<thread> m_thread_func_;
};

#endif /* TCP_SERVER_H_ */

2. 定义文件

#include "tcp_server.h"

tcp_server::tcp_server(tcp_notify& notify)
        : m_notify(notify),
          efd(::epoll_create1(EPOLL_CLOEXEC)),
          m_thread_state_(true),
          m_thread_func_(nullptr)
{
}

tcp_server::~tcp_server()
{
    stop();
}

void tcp_server::start(const string& port)
{
    int ret;
    struct epoll_event event;

    sfd = create_and_bind(port.c_str());
    if (sfd == -1) abort();

    ret = make_socket_non_blocking(sfd);
    if (ret == -1) abort();

    ret = listen(sfd, SOMAXCONN);
    if (ret == -1) {
        perror("listen");
        abort();
    }

    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;
    ret = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (ret == -1) {
        perror("epoll_ctl");
        abort();
    }

    /* Buffer where events are returned */
    events = (struct epoll_event*)calloc(MAXEVENTS, sizeof(event));

    m_thread_func_ = make_shared < thread > (
            bind(&tcp_server::thread_func, this));
    m_thread_func_->join();
}

void tcp_server::stop()
{
    m_thread_state_ = false;
    m_thread_func_->join();
    free(events);
    close(sfd);
}

int tcp_server::create_and_bind(const char *port)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int ret, sfd;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = AI_PASSIVE; /* All interfaces */

    ret = getaddrinfo(NULL, port, &hints, &result);
    if (ret != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
        return -1;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1) continue;
        ret = bind(sfd, rp->ai_addr, rp->ai_addrlen);
        if (ret == 0) {
            /* We managed to bind successfully! */
            break;
        }

        close(sfd);
    }
    if (rp == NULL) {
        fprintf(stderr, "Could not bind\n");
        return -1;
    }
    freeaddrinfo(result);
    return sfd;
}

int tcp_server::make_socket_non_blocking(int sfd)
{
    int flags, ret;

    flags = fcntl(sfd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    ret = fcntl(sfd, F_SETFL, flags);
    if (ret == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}

void tcp_server::thread_func()
{
    int ret;
    struct epoll_event event;
    /* The event loop */
    while (m_thread_state_) {
        int n, i;

        n = epoll_wait(efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++) {
            if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)
                    || (!(events[i].events & EPOLLIN))) {
                /* An error has occured on this fd, or the socket is not
                 ready for reading (why were we notified then?) */
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
                }
            else if (sfd == events[i].data.fd) {
                /* We have a notification on the listening socket, which
                 means one or more incoming connections. */
                while (1) {
                    struct sockaddr in_addr;
                    socklen_t in_len;
                    int infd;
                    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                    in_len = sizeof in_addr;
                    infd = accept(sfd, &in_addr, &in_len);
                    if (infd == -1) {
                        if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                            /* We have processed all incoming
                             connections. */
                            break;
                                }
                        else {
                            perror("accept");
                            break;
                                }
                            }
                    ret = getnameinfo(&in_addr, in_len, hbuf, sizeof hbuf, sbuf,
                            sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);
                    if (ret == 0) {
                        printf("Accepted connection on descriptor %d "
                                "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                            }

                    /* Make the incoming socket non-blocking and add it to the
                     list of fds to monitor. */
                    ret = make_socket_non_blocking(infd);
                    if (ret == -1) abort();

                    event.data.fd = infd;
                    event.events = EPOLLIN | EPOLLET;
                    ret = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
                    if (ret == -1) {
                        perror("epoll_ctl");
                        abort();
                            }
                    }
                continue;
                }
            else {
                /* We have data on the fd waiting to be read. Read and
                 display it. We must read whatever data is available
                 completely, as we are running in edge-triggered mode
                 and won't get a notification again for the same
                 data. */
                int done = 0;

                while (1) {
                    ssize_t count;
                    char buf[512];

                    count = read(events[i].data.fd, buf, sizeof buf);
                    if (count == -1) {
                        /* If errno == EAGAIN, that means we have read all
                         data. So go back to the main loop. */
                        if (errno != EAGAIN) {
                            perror("read");
                            done = 1;
                                }
                        break;
                            }
                    else if (count == 0) {
                        /* End of file. The remote has closed the
                         connection. */
                        done = 1;
                        break;
                            }

                    /* Write the buffer to standard output */
//                    ret = write(1, buf, count);
//                    if (ret == -1) {
//                        perror("write");
//                        abort();
//                            }
                    m_notify.on_recv_data(events[i].data.fd, buf, count);
                        }

                if (done) {
                    printf("Closed connection on descriptor %d\n",
                            events[i].data.fd);

                    /* Closing the descriptor will make epoll remove it
                     from the set of descriptors which are monitored. */
                    close(events[i].data.fd);
                     }
            } // rec end
        }// for
    }//while(1)
}

3.main.cpp

#include <iostream>
#include "tcp_server.h"
using namespace std;

class notify
        : public tcp_server::tcp_notify
{
    virtual size_t on_recv_data(const unsigned int clientid, const char* buf, const size_t len)
    {
        string str(buf, len);
        cout << "on_recv_data:" << str << endl;
        return len;
    }
};

int main(int argc,char *argv[])
{
    notify notify_inst;
    tcp_server test(notify_inst);
    test.start("9587");

    cout << "Helloworld!" << endl;
    return 0;
}

 

posted @ 2017-06-12 00:08  略加思索的河马  阅读(690)  评论(0编辑  收藏  举报