Epoll TCP Server and Client Simple Example

Introduction

epoll is a Linux-specific API that provides efficient file descriptor management. It’s commonly used to monitor the readability of multiple file descriptors in batch and is one of the effective methods for high-concurrency servers. Below is a simple example of an epoll server and client.

Server Side

#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>

using namespace std;

const int MAX_EPOLL_EVENTS = 1000;
const int MAX_MSG_LEN = 1024;

void setFdNonblock(int fd)
{
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

void err_exit(const char *s){
    printf("error: %s\n",s);
    exit(0);
}

int create_socket(const char *ip, const int port_number)
{
    struct sockaddr_in server_addr = {0};
    /* Set IPv4 mode */
    server_addr.sin_family = AF_INET;           /* ipv4 */
    /* Set port number */
    server_addr.sin_port = htons(port_number);
    /* Set host address */
    if(inet_pton(server_addr.sin_family, ip, &server_addr.sin_addr) == -1){
        err_exit("inet_pton");
    }
    /* Create socket */
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        err_exit("socket");
    }
    /* Set reuse mode */
    int reuse = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        err_exit("setsockopt");
    }
    /* Bind to port */
    if(bind(sockfd, (sockaddr *)&server_addr, sizeof(server_addr)) == -1){
        err_exit("bind");
    }
    /* Set passive open */
    if(listen(sockfd, 5) == -1){
        err_exit("listen");
    }
    return sockfd;
}

int main(int argc, const char *argv[])
{
    /* Help message */
    if(argc < 3){
        printf("usage:%s ip port\n", argv[0]);
        exit(0);
    }
    /* Get server parameters */
    const char * ip = argv[1];
    const int port = atoi(argv[2]);
    /* Create socket */
    int sockfd = create_socket(ip, port);
    printf("success create sockfd %d\n", sockfd);
    setFdNonblock(sockfd);
    /* Create epoll */
    int epollfd = epoll_create1(0);
    if(epollfd == -1) err_exit("epoll_create1");
    /* Add sockfd to epollfd interest list */
    struct epoll_event ev;
    ev.data.fd = sockfd;
    ev.events = EPOLLIN ;
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1){
        err_exit("epoll_ctl1");
    }
    /* Create a list to store events returned by wait */
    struct epoll_event events[MAX_EPOLL_EVENTS] = {0};
    /* Start waiting for all events registered on epoll */

    while(1){
        /* Wait for events */
        printf("begin wait\n");
        int number = epoll_wait(epollfd, events, MAX_EPOLL_EVENTS, -1);
        printf("end wait\n");
        sleep(1);
        if(number > 0){
            /* Traverse all events */
            for (int i = 0; i < number; i++)
            {
                int eventfd = events[i].data.fd;
                /* If the fd triggering the event is sockfd, someone has connected, and we need to accept them */
                if(eventfd == sockfd){
                    printf("accept new client...\n");
                    struct sockaddr_in client_addr;
                    socklen_t client_addr_len = sizeof(client_addr);
                    int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
                    setFdNonblock(connfd);
                    /* After accept, we need to add the file descriptor to the monitoring list */
                    struct epoll_event ev;
                    ev.data.fd = connfd;
                    ev.events = EPOLLIN;
                    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) == -1){
                        err_exit("epoll_ctl2");
                    }
                    printf("accept new client end.\n");
                }
                /* If the triggering fd is not sockfd, it's the newly added connfd */
                else{
                    /* Read content until encountering a newline, then display the content */
                    printf("read start...\n");
                    while(1){
                        char buff = -1;
                        int ret = read(eventfd, &buff, 1);
                        if(ret > 0){
                            printf("%c", buff);
                        }
                        if(buff == '\n'){
                            break;
                        }
                        else if (ret == 0){
                            printf("client close.\n");
                            close(eventfd);
                            epoll_ctl(epollfd, EPOLL_CTL_DEL, eventfd, NULL);
                            break;
                        }
                        else if (ret < 0){
                            printf("read error.\n");
                            break;
                        }
                    }
                    printf("read end.\n");
                }
            }
        }
    }
}

Client Side

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string>
#include <iostream>

using namespace std;

void err_exit(const char *s){
    printf("error: %s\n",s);
    exit(0);
}

int create_socket(const char *ip, const int port_number)
{
    struct sockaddr_in server_addr = {0};
    /* Set IPv4 mode */
    server_addr.sin_family = AF_INET;           /* ipv4 */
    /* Set port number */
    server_addr.sin_port = htons(port_number);
    /* Set host address */
    if(inet_pton(PF_INET, ip, &server_addr.sin_addr) == -1){
        err_exit("inet_pton");
    }

    /* Create socket */
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        err_exit("socket");
    }

    if(connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1){
        err_exit("connect");
    }

    return sockfd;
}

int main(int argc, const char *argv[]){
    if(argc < 3){
        printf("usage:%s ip port\n", argv[0]);
        exit(0);
    }
    /* Get server parameters */
    const char * ip = argv[1];
    const int port = atoi(argv[2]);
    //Create socket
    int sock = create_socket(ip, port);
    //Initiate request to server (specific IP and port)
    
    while(1){
        string buff;
        getline(cin, buff);
        if(buff == "exit") break;
        write(sock, buff.c_str(), buff.size());
        char end = '\n';
        write(sock, &end, 1);
    }
    close(sock);
    return 0;
}

Compilation

Save the above text as socket_server.cpp and socket_client.cpp, then compile and link the programs.

g++ -Wall socket_server.cpp -o server && g++ -Wall socket_client.cpp -o client

Execution

./server localhost 1234
./client localhost 1234

Entering text on the client side and pressing Enter will display it on the server side. Press Ctrl+C or type “exit” to close the client.

Execution Flow

The server first creates a passive open socket file descriptor, then adds this file descriptor to the epoll interest list. It then enters a loop. Whenever the interest list’s wait ends, it means the corresponding file descriptor can be operated on. When a client connects to the passive open socket file descriptor, it indicates a client has connected, and the passive open file descriptor can be accepted. The new file descriptor created after accept is the file descriptor for communicating with the client, which is also added to the interest list. When the client sends data, this file descriptor will also generate a readable signal, causing the wait to end. At this point, it enters processing mode, reading and displaying the data sent by the client.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy