Search code examples
socketsudpepoll

Epoll events(EPOLLLT) only triggered once on udp socket


From online resource, they said that if epoll listening on file descriptors using default mode(level trigger), when the fd(file descriptor) is ready to read and the buffer data associated with fd not fully consumed, epoll will continue to trigger until all data is consumed, however, when I test with epoll(LT mode) listening on udp socket, when multiple characters comes epoll only trigger once. the process like below:

step 1: create epoll, udp socket fd, then make epoll listening on socket for write event.

step 2: send multiple characters("abc") to the udp socket

step 3: each time epoll triggered, then read 1 character from the udp socket.

I am expecting that epoll trigger three times as udp socket receive 3 characters, but the result is epoll only trigger once. here is my code:

#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define BUFFER_SIZE 512
#define log(fmt, arg...) printf(""fmt, ##arg)

void main(){
    int fd1,efd, fds, i, fd;
    int ret, addr_len;
    struct epoll_event g_event;             
    struct epoll_event *epoll_events_ptr; 
    char buffer[BUFFER_SIZE] = {0};
    struct sockaddr_in addr1;

    fd1 = socket(AF_INET, SOCK_DGRAM, 0);   
    if (fd1 == -1) {
        log("create socket fail \r\n");
        return ;
    }     

    addr1.sin_family = AF_INET;             
    addr1.sin_addr.s_addr = INADDR_ANY; 
    addr1.sin_port = htons(3500);    
    addr_len = sizeof(struct sockaddr_in);

    if (0 != bind(fd1, (struct sockaddr *)&addr1, sizeof(struct sockaddr_in))) { 
        log("bind local listening addr fail,errno : %d \r\n", errno);
        goto err;
    }


    efd = epoll_create1(0);                 
    if (efd == -1) {
        log("create epoll fail \r\n");
        goto err;
    }
    log("create epoll instance success \r\n");

    epoll_events_ptr = (struct epoll_event *)calloc(2, sizeof(struct epoll_event));
    if (epoll_events_ptr == NULL) {
        log("calloc fail \r\n");
        goto err;
    }

    g_event.data.fd = fd1; 
    g_event.events = EPOLLIN;   
    epoll_ctl(efd, EPOLL_CTL_ADD, fd1, &g_event);          

    while(1) {
        fds = epoll_wait(efd, epoll_events_ptr, 2, -1); 
        for (i = 0; i<fds; i++)
        {    
            if (epoll_events_ptr[i].events & EPOLLIN)
            {   
                ret = recv(fd1, buffer, 1, MSG_DONTWAIT);
                if(ret != -1)
                log("recv msg : %s \n", buffer);
            }
            memset(buffer, 0, BUFFER_SIZE);
        }        
    }   

err:
    close(fd1);
    if(epoll_events_ptr) 
        free(epoll_events_ptr);

    return ;
}

enter image description here


Solution

  • You are treating UDP as though it was a streaming protocol, i.e. TCP. It isn't. It is a datagram protocol. If you read a UDP datagram into a buffer that is too small to receive it, the remainder of the datagram is discarded. Not left in the buffer for next time.

    Reading one character at a time is therefore pointless in UDP, not to mention extremely inefficient in any protocol.

    NB You don't need the memset(), and this:

    log("recv msg : %s \n", buffer);
    

    is invalid. It should be:

    log("recv msg : %.*s \n", ret, buffer);
    

    You can't assume the received data is null-terminated.