0

I am facing a very strange problem. I have a server application that runs UDP socket and wait for incoming data. As soon as it gets the command it begins to send back a stream. Just for testing, I limit the server to sending only one piece of data 8000 bytes long. I don't provide the server code since it work as expected. It receives the command and sends data back, I can see it with Wireshark. My problem is the client size.

The issue: I instantiate a client non-blocking UDP socket and send "Hello" to the server that responses with 8000 bytes of data. I'm trying to read data in a loop in chunks of 1024 bytes. But the problem that only one chunk of data has read. the next loop returns -1 infinitely. If I try to read 8000 bytes in recv I read it successfully, If I try to read 8100 bytes in recv I read 8000 bytes that sent. I mean that only one call to recv succeed. All subsequent calls return an error although not all data has read yet.

Here is a simplified code:

class ClienSocket
{
public:
    void Init()
    {
        pollfd m_poll = {};
        m_poll.fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if(m_poll.fd == -1)
        {    
            throw std::runtime_error(GetLastError());
        }

        int optval = 1;
        setsockopt(m_poll.fd, SOL_SOCKET, SO_REUSEADDR, static_cast<const void *>(&optval), sizeof(int));
        int on = 1;
        if(ioctl(m_poll.fd, FIONBIO, &on) < 0)
        {
            throw std::runtime_error(std::string("failed to set the client socket non-blocking: ") + strerror(errno));
        }
    }

    void Run()
    {
        struct sockaddr_in serv_addr;
        m_servaddr.sin_family = AF_INET;
        m_servaddr.sin_addr.s_addr = inet_addr(m_address.c_str());
        m_servaddr.sin_port = htons(static_cast<uint16_t>(m_port));
        m_poll.events = POLLIN;
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(m_port);        

        m_running = true;
        if(pthread_create(&m_readThread, nullptr, &ClienSocket::ReadThreadWrapper, this) != 0)
        {
            m_running = false;    
            throw std::runtime_error(std::string("thread creating error");
        }
    }
    
    void ClienSocket::Write(const char *data, size_t size)
    {
        sendto(m_poll.fd, data, size, MSG_NOSIGNAL, reinterpret_cast<const struct sockaddr *>(&(m_servaddr)), sizeof(sockaddr_in));   
    }

    static void *ClienSocket::ReadThreadWrapper(void *ptr)
    {
        ClienSocket *instance = static_cast<ClienSocket *>(ptr);
        if(instance != nullptr)
        {
            return instance->ReadThreadFunc();
        }

        return nullptr;
    }

    void *ClienSocket::ReadThreadFunc()
    {
        while(m_running)
        {
            retval = poll(&m_poll, 1, 1000);
            if(retval > 0)
            {
                if(m_poll.revents == POLLIN)
                {
                    bool readMore = true;
                    do
                    {
                        ssize_t readBytes = recv(m_poll.fd, m_readBuffer, READ_BUFFER_SIZE, 0);                        
                        std::cout << readBytes << ", " << errno << std::endl;
                        if (readBytes < 0)
                        {
                            if (errno != EWOULDBLOCK)
                            {
                                throw std::runtime_error(std::string("socket error");                                
                            }
                        }
                        else if(readBytes == 0)
                        {
                            readMore = false;                            
                        }
                        else
                        {
                            ProcessData(m_readBuffer, readBytes);
                        }                        
                    }
                    while(readMore == true);
                }
            }
        }    
        return nullptr;
    }
    
    void ClienSocket::Wait()
    {
        if(m_running)
        {        
            pthread_join(m_readThread, nullptr);            
        }
    }
    
    void ProcessData(const char *data, size_t length)
    {
        std::cout << length << std::endl;
    }
    
private:
    bool m_running = false;
    int m_port = 3335;
    std::string m_address = "192.168.5.1";
    struct sockaddr_in m_servaddr;
    pollfd m_poll = {};
    pthread_t m_readThread;
    static constexpr size_t READ_BUFFER_SIZE = 1024;
    char m_readBuffer[READ_BUFFER_SIZE];
}

The testcase:

ClienSocket client;
client.Init();
client.Run();
client.Write("hello", 5);
clientWait();

According to Wireshard 8000 bytes has sent: Wireshark

Target system: Ubuntu 22.04

The output:

1024, 0
-1, 11
-1, 11
-1, 11
-1, 11
-1, 11
...
folibis
  • 12,048
  • 6
  • 54
  • 97
  • Discarded by who? I receive 8000 bytes without problem but only in case if I read 8000. I receive these 8000 at the local machine, that doesn't discarded. – folibis Dec 13 '22 at 10:20
  • Sorry, wrong duplicate; see [here](https://stackoverflow.com/questions/11026316/reading-all-the-data-from-a-udp-socket) instead. Albeit being python same applies for C or C++ as well. Datagrams need to be read in one go. That's why it works for reading 8000 at once, but not with chunks of 1024 bytes. What is not read from a datagram gets discarded after reading a part of. – Aconcagua Dec 13 '22 at 10:34
  • [recv](https://man7.org/linux/man-pages/man2/recv.2.html)... – Aconcagua Dec 13 '22 at 10:40
  • OK, the answer should be the description (that I probably missed) from the link above : _If a message is too long to fit in the supplied buffer, excess bytes may be discarded depending on the type of socket the message is received from_ – folibis Dec 13 '22 at 10:45

1 Answers1

0

I'm trying to read data in a loop in chunks of 1024 bytes.

That will not work with UDP, as it is message-oriented rather than stream-oriented, like TCP is.

In UDP, there is a 1:1 relationship between sends and reads. If the UDP server sends a single message of 8000 bytes, the client must receive the entire message in a single read, it cannot receive it across multiple reads, like you are attempting to do.

If the buffer you are reading into is too small to receive the entire message, the read will fail with an EMSGSIZE error code and the unread bytes will be discarded, you can't recover them.

That is why your subsequent reads are failing (withan EWOULDBLOCK/EAGAIN error code), as there is no data available to read until the server sends a new message.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Actually I get the EAGAIN error instead. I understand the the principle of operation but it looks like kind of lack if implementation. In fact all these 8000 are already in the local buffer and I guess I should have an option to read that either at one time or in a loop. logically speaking but theoretically of course. – folibis Dec 14 '22 at 06:36
  • @folibis You will get EAGAIN on subsequent reads (since you are using a non-blocking socket), not on the read that failed to receive the complete message in full and so discarded its bytes. There is no option to read a message in a loop, it has to be read in full at one time. That's just how UDP sockets work. – Remy Lebeau Dec 14 '22 at 15:33