网络编程–基于epoll的EchoServer编程示例

以下为采用Linux下epoll方式的EchoServer示例

1.每个连接应该包含一个epoll_event,用于此连接上读写事件的监听,同时此连接上面还包含读写缓存、用户数据等

struct socket_conn_t {
    int  fd;
    int  efd;
    struct epoll_event *event;
    char *buffer;
    void *data;
};

2. 客户端连接构造和析构函数

struct socket_conn_t *socket_conn_new(void)
{
    struct socket_conn_t *conn = malloc(sizeof(struct socket_conn_t));
    memset(conn, 0, sizeof(struct socket_conn_t));

    conn->event = (struct epoll_event *)malloc(sizeof(struct epoll_event));
    memset(conn->event, 0, sizeof(struct epoll_event));

    return conn;
}
void socket_conn_free(struct socket_conn_t *conn)
{
    if (conn == NULL) {
        return;
    }

    if (conn->fd != 0) {
        close(conn->fd);
        conn->fd = 0;
    }

    if (conn->event != NULL) {
        free(conn->event);
    }

    if (conn->buffer != NULL) {
        free(conn->buffer);
    }

    free(conn);
}

3. 异步编程,所有连接描述符都是非阻塞的

int socket_setnonblock(int fd)
{
    long flags = fcntl(fd, F_GETFL);
    if (flags < 0) {
        fprintf(stderr, "fcntl F_GETFL");
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0) {
        fprintf(stderr, "fcntl F_SETFL");
        return -1;
    }

    return 0;
}

4. 主程序结构

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/unistd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>

#define BUFFER_SIZE   1024

#define MAXEVENTS 64

int main(int argc, char *argv[])
{
    int sfd = 0, s = 0;
    int efd = 0;
    struct epoll_event event;
    struct epoll_event *events;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s [port]\n", argv[0]);
        return -1;
    }

    sfd = socket_create_and_bind(argv[1]);
    if (sfd == -1) {
        abort();
        return -1;
    }

    s = socket_setnonblock(sfd);
    if (s == -1) {
        abort();
        return -1;
    }

    s = listen(sfd, SOMAXCONN);
    if (s == -1) {
        fprintf(stderr, "listen error\n");
        abort();
        return -1;
    }

    efd = epoll_create(256);
    if (efd == -1) {
        fprintf(stderr, "epoll_create error\n");
        abort();
        return -1;
    }

    event.data.fd = sfd;
    event.events = EPOLLIN;
    s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1) {
        fprintf(stderr, "epoll_ctl error\n");
        abort();
        return -1;
    }

    /* the event loop */
    events = calloc(MAXEVENTS, sizeof(event));
    while (1) {
        int i = 0, nfds = 0;
        nfds = epoll_wait(efd, events, MAXEVENTS, 1000);
        for (i = 0; i < nfds; i++) {
            if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) {
                fprintf(stderr, "epoll_wait event error\n");
                close(events[i].data.fd);
                continue;
            }
            else if (events[i].events & EPOLLIN) {
                if (events[i].data.fd == sfd) {
                    /* listen socket, need accept */
                    socket_accept(events[i].data.fd, 0, &efd);
                }
                else {
                    /* client socket, need read */
                    socket_read(events[i].data.fd, 0, events[i].data.ptr);
                }
            }
            else if ((events[i].events & EPOLLOUT) && (events[i].data.fd != sfd)) {
                /* client socket, need write */
                socket_write(events[i].data.fd, 0, events[i].data.ptr);
            }
        }
    }

    free(events);
    close(efd);
    close(sfd);

    return 0;
}

5. 创建、并绑定本地端口

int socket_create_and_bind(const char *port)
{
    int s, sfd;
    struct addrinfo hints;
    struct addrinfo *result, *rp;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_PASSIVE;

    s = getaddrinfo(NULL, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr,"%s getaddrinfo: %s\n", __func__, gai_strerror(s));
        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;
        }

        s = socket_setopt(sfd);
        if (s == -1) {
            return -1;
        }

        s = bind(sfd, rp->ai_addr, rp->ai_addrlen);
        if (s == 0) {
            break;
        }
        close(sfd);
    }

    if (rp == NULL) {
        fprintf(stderr, "%s Could not bind\n", __func__);
        return -1;
    }

    freeaddrinfo(result);
    return sfd;
}

6. 服务器端应设置端口重用、关闭连接数据处理方式等socket option

int socket_setopt(int sockfd)
{
    int ret = 0;

    int reuse = 1;
    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&reuse, sizeof(reuse));
    if (ret < 0) {
        fprintf(stderr, "%s setsockopt SO_REUSEPORT error\n", __func__);
        return -1;
    }

    struct linger so_linger;
    so_linger.l_onoff = 1;
    so_linger.l_linger = 1;
    ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
    if (ret < 0) {
        fprintf(stderr, "%s setsockopt SO_LINGER error\n", __func__);
        return -1;
    }

    return 0;
}

7. 主进程/主线程监听到客户端连接的处理

void socket_accept(int listen_fd, int events, void *arg)
{
    struct sockaddr_in sin;
    socklen_t len = sizeof(struct sockaddr_in);

    int s = 0;
    int epoll_fd = *(int *)arg;

    int nfd = 0;
    if ((nfd = accept(listen_fd, (struct sockaddr *)&sin, &len)) == -1) {
        if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
            return;
        }
        else {
            fprintf(stderr, "%s bad accept\n", __func__);
            return;
        }
    }

    fprintf(stderr, "%s new conn %d [%s:%d]\n",
            __func__, nfd, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));

    do {
        // new client
        struct socket_conn_t *conn = socket_conn_new();
        socket_setnonblock(nfd);
        conn->fd = nfd;
        conn->efd = epoll_fd;
        conn->event->data.fd = nfd;
        conn->event->data.ptr = conn;
        conn->event->events = EPOLLIN;
        s = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, nfd, conn->event);
        if (s == -1) {
            fprintf(stderr, "%s epoll_ctl error\n", __func__);
            break;
        }
    } while (0);

    return;
}

8. 对客户端读事件的处理,读完成后要向事件驱动器中加入写事件监测

void socket_read(int fd, int events, void *arg)
{
    struct socket_conn_t *conn = (struct socket_conn_t *)(arg);

    char buffer[BUFFER_SIZE] = {0};
    ssize_t read = recv(conn->fd, buffer, BUFFER_SIZE, 0);
    if (read < 0) {
         fprintf(stderr, "%s read error, fd:%d\n", __func__, conn->fd);
         return;
    }

    if (read == 0) {
        fprintf(stderr, "%s client disconnected, fd:%d\n", __func__, conn->fd);
        epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, conn->event);
        socket_conn_free(conn);
        return;
    }

    buffer[read] = '\0';
    fprintf(stderr, "%s receive from fd:%d message:%s", __func__, conn->fd, buffer);

    if (strncasecmp(buffer, "quit", 4) == 0) {
        fprintf(stderr, "%s client quit, fd:%d\n", __func__, conn->fd);
        epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, conn->event);
        socket_conn_free(conn);
        return;
    }

    int len = strlen(buffer);
    conn->buffer = (char *)malloc((len + 1) * sizeof(char));
    strncpy(conn->buffer, buffer, len);
    conn->buffer[len] = '\0';
    memset(buffer, 0, sizeof(buffer));

    conn->event->events = EPOLLOUT;
    epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, conn->event);
}

9. 对客户端写事件的处理,写事件完成后要从事件驱动器中停止写事件监测

void socket_write(int fd, int events, void *arg)
{
    struct socket_conn_t *conn = (struct socket_conn_t *)(arg);

    // write/clean data buffer
    if (conn->buffer == NULL) {
        socket_conn_free(conn);
        return;
    }
    send(conn->fd, conn->buffer, strlen(conn->buffer), 0);
    free(conn->buffer);
    conn->buffer = NULL;

    conn->event->events = EPOLLIN;
    epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, conn->event);
    return;
}

备注:对于客户端的读写事件处理函数,比较简单,更复杂的比如状态比如:可读、正在读、读完成,可写、正在写、写完成并没有展示,这个只是更细化读写事件处理的过程,就不再展示,对于客户端正常结束和异常结束只是个简单处理。

10. 编译和测试

备注:将以上函数统一放在 test.c 中,然后进行编译

gcc -Wall -o test test.c
原创声明:除非注明,本站文章均为原创!转载请注明来自 嗨!大佟! www.qmailer.net
本文链接:http://www.qmailer.net/archives/276.html

发表评论