c++linuxserverclientepoll

my epoll server can not detect client disconnection


I wrote an epoll server and a simple client. When the client sends a message, it exits immediately and the server does not notice the disconnection.

events[i].events & EPOLLERR || events[i].events & EPOLLHUP This judgment statement is of no use at all. What areas need improvement in my epoll server? I really want to know, please!

This is my server.cc

#include "server.h"
#include "../threadpool/threadpool.h"

static std::string getIpAddress() 
{
    struct ifaddrs *ifAddrStruct = nullptr;
    struct ifaddrs *ifa = nullptr;
    void *tmpAddrPtr = nullptr;
    std::string ipAddress;

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) 
    {
        if (!ifa->ifa_addr) 
            continue;
        if (ifa->ifa_addr->sa_family == AF_INET) 
        {
            tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);

            if (strcmp(addressBuffer, "127.0.0.1") != 0) 
            {
                ipAddress = std::string(addressBuffer);
                break;
            }
        }
    }

    if (ifAddrStruct != nullptr)
    {
        freeifaddrs(ifAddrStruct);
    }

    return ipAddress;
}

static void setFdNoblock(int fd)
{
    fcntl(fd, F_SETFL, fcntl(fd, F_SETFL) | O_NONBLOCK);
}

server::server(int port)
{
    epoll_fd = epoll_create1(0);
    if(epoll_fd == -1)
        throw std::runtime_error("Error in epoll_create1");

    struct sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if(server_socket == -1)
        throw std::runtime_error("Error in socket");

    int reuse = 1;
    if(setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
        throw std::runtime_error("Error in setsocketopt");

    if(bind(server_socket, reinterpret_cast<struct sockaddr*>(&server_addr), sizeof(server_addr)) == -1)
        throw std::runtime_error("Error in bind");

    if(listen(server_socket, SOMAXCONN) == -1)
        throw std::runtime_error("Error in listen");

    setFdNoblock(server_socket);



    std::cout << "server start on " << getIpAddress() << ':' << port << '\n';
}

server::~server()
{
    close(server_socket);
    close(epoll_fd);
}

void handleClient(int client_socket)
{
    printf("success!!!\n");
}

static int readn(int fd, char* buf, int size)
{
    char* pt = buf;
    int count = size;

    while(count > 0)
    {
        int len = recv(fd, pt, count, 0);
        if(len == -1)
            return -1;
        else if(len == 0)
            return size-count;

        pt += len;
        count -= len;
    }

    return size;
}

static int recvMsg(int cfd, char** msg)
{
    int len = 0;
    readn(cfd, (char*)&len, 4);
    len = ntohl(len);

    char *buf = (char*)malloc(len+1);
    int ret = readn(cfd, buf, len);
    if(ret != len)
    {
        close(cfd);
        free(buf);
        return -1;
    }

    buf[len] = '\0';
    *msg = buf;

    return ret;
}

void server::run()
{
    struct epoll_event event{};
    event.events = EPOLLIN;
    event.data.fd = server_socket;

    if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1)
        throw std::runtime_error("Error in epoll_ctl");

    threadpool threadPool(THREAD_NUM);

    epoll_event events[MAX_EVENTS];

    while(true)
    {
        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if(num_events == -1)
            throw std::runtime_error("Error in epoll_wait");

        for(int i = 0; i < num_events; i++)
        {
            if(events[i].data.fd == server_socket)
            {
                std::cout << "accepting new client...\n";
                int client_socket = accept(server_socket, nullptr, nullptr);
                if(client_socket == -1)
                {
                    std::cerr << "Failed to accept client connection\n";
                    continue;
                }
                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client_socket;
                setFdNoblock(client_socket);

                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
                std::cout << "client on line client_socket: " << client_socket << '\n';

                threadPool.enqueue(handleClient, client_socket);//---------------to do
            }
            else
            {
                if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP)
                {
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);
                    std::cout << "Client disconnected by EPOLLERR | EPOLLHUP: " << events[i].data.fd << '\n';
                    close(events[i].data.fd);
                }
                else if(events[i].events & EPOLLIN)
                {
                    char* msg = nullptr;
                    int ret = recvMsg(events[i].data.fd, &msg);
                    if(ret == -1)
                    {
                        std::cerr << "Failed to receive message from client_socket: " << events[i].data.fd << '\n';
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);
                        close(events[i].data.fd);
                    }
                    else if (ret == 0)
                    {
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);
                        std::cout << "Client disconnected: " << events[i].data.fd << '\n';
                        close(events[i].data.fd);
                    }
                    else
                    {
                        std::cout << "Received message from client_socket: " << events[i].data.fd << '\n';
                        std::cout << msg << '\n';
                        free(msg);
                    }
                }
            }
        }
    }
}

This is my client.cc

#include <sys/socket.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno> // For errno
#include <sys/types.h>

#define PORT_NUM 8000
#define IP "10.30.0.245"
#define BUF_SIZE 2048

void errExit(const char* error)
{
    perror(error);
    exit(EXIT_FAILURE);
}

int writen(int fd, const char* msg, int size)
{
    const char* buf = msg;
    int count = size;
    while(count > 0)
    {
        int len = send(fd, buf, count, 0);
        if(len == -1)
        {
            if(errno == EPIPE || errno == ECONNRESET)
            {
                printf("Network connection reset or broken.\n");
                return -1; // Return -1 to indicate network error
            }
            else
            {
                perror("send");
                close(fd);
                return -1;
            }
        }
        else if(len == 0)
        {
            continue;
        }

        buf += len;
        count -= len;
    }

    return size;
}

int sendMsg(int cfd, const char* msg, int len)
{
    if(msg == nullptr || len <= 0 || cfd <= 0)
    {
        return -1;
    }

    char* data = (char*)malloc(len+4);
    if(data == nullptr)
    {
        perror("malloc");
        return -1;
    }
    
    int bigLen = htonl(len);
    memcpy(data, &bigLen, 4);
    memcpy(data+4, msg, len);

    int ret = writen(cfd, data, len+4);

    free(data);

    return ret;
}

int main(void) {
    int sfd;
    struct sockaddr_in svaddr;
    char buf[BUF_SIZE];

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd == -1)
        errExit("socket");

    memset(&svaddr, 0, sizeof(struct sockaddr_in)); 
    svaddr.sin_family = AF_INET;
    svaddr.sin_port = htons(PORT_NUM);
    inet_pton(AF_INET, IP, &svaddr.sin_addr);

    if(connect(sfd, (const struct sockaddr*) &svaddr, sizeof(struct sockaddr_in)) == -1)
        errExit("connect");

    printf("\nPlease input: ");
    memset(buf, 0, BUF_SIZE);
    if(fgets(buf, BUF_SIZE, stdin) == nullptr)
    {
        printf("Failed to read input.\n");
        close(sfd);
        return EXIT_FAILURE;
    }

    int len = strlen(buf);
    
    // Remove trailing newline if present
    if (len > 0 && buf[len-1] == '\n') {
        buf[len-1] = '\0';
        len--;
    }

    if (len == 0) {
        printf("Input is empty.\n");
        close(sfd);
        return EXIT_FAILURE;
    }

    if (strcmp(buf, "exit") == 0) {
        printf("Exiting...\n");
        close(sfd);
        return EXIT_SUCCESS;
    }

    if(sendMsg(sfd, buf, len) == -1) {
        errExit("sendMsg");
    }

    close(sfd);
    return EXIT_SUCCESS;
}

I want the server to notice that the client disconnected, what i better do?


Solution

  • If I was debugging this code, two things I would do immediately:

    1. add a line to tell you what events have actually occurred in the epoll loop on the server

    std::cout << "Server event 0x" << std::hex << static_cast<unsigned>(events[i].events) << std::endl;

    1. Change the order of your if/else conditions in the server epoll loop. Put the detection of EPOLLIN before the EPOLLERR/EPOLLHUP check.

    Since your client sends data and then immediately closes its socket, you may be getting an event mask that has coalesced into (EPOLLIN | EPOLLHUP). Your server code doesn't handle that...