【问题标题】:UDP client socket read less data then sentUDP 客户端套接字读取较少的数据然后发送
【发布时间】:2022-12-14 03:05:27
【问题描述】:

我面临一个非常奇怪的问题。我有一个运行 UDP 套接字并等待传入​​数据的服务器应用程序。一旦它收到命令,它就会开始发回一个流。只是为了测试,我限制服务器只发送一条 8000 字节长的数据。我不提供服务器代码,因为它按预期工作。它接收命令并发回数据,我可以用 Wireshark 看到它。我的问题是客户规模。

问题:我实例化一个客户端非阻塞 UDP 套接字并将“Hello”发送到响应 8000 字节数据的服务器。我正在尝试以 1024 字节的块循环读取数据。但问题在于只有一个数据块已读取。下一个循环无限返回 -1。如果我尝试读取 recv 中的 8000 个字节,我会成功读取它,如果我尝试读取 recv 中的 8100 个字节,我会读取已发送的 8000 个字节。我的意思是只有一次调用 recv 成功。尽管尚未读取所有数据,但所有后续调用都会返回错误。

这是一个简化的代码:

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];
}

测试用例:

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

根据 Wireshard 8000 字节已发送:

目标系统:Ubuntu 22.04

输出:

1024, 0
-1, 11
-1, 11
-1, 11
-1, 11
-1, 11
...

【问题讨论】:

  • 被谁抛弃?我毫无问题地收到 8000 个字节,但前提是我读取了 8000 个字节。我在本地机器上收到这些 8000 个字节,不会被丢弃。
  • 抱歉,重复错误;请参阅here。尽管是 python,但同样适用于 C 或 C++。数据报需要一次性读取。这就是为什么它适用于一次读取 8000 个,但不适用于 1024 字节的块。未从数据报中读取的内容在读取一部分后将被丢弃。
  • recv...
  • 好的,答案应该是上面链接中的描述(我可能错过了):如果消息太长而无法放入提供的缓冲区,则可能会丢弃多余的字节,具体取决于从中接收消息的套接字类型

标签: c++ sockets udp nonblocking


【解决方案1】:

我正在尝试以 1024 字节的块循环读取数据。

这不适用于 UDP,因为它是面向消息的,而不是像 TCP 那样面向流的。

在 UDP 中,发送和读取之间存在 1:1 的关系。如果 UDP 服务器发送一条 8000 字节的消息,客户端必须在一次读取中接收整个消息,它不能像您尝试的那样在多次读取中接收它。如果您正在读入的缓冲区太小而无法接收整个消息,则读取将失败并且未读字节将被丢弃。

【讨论】:

    猜你喜欢
    • 2018-09-08
    • 1970-01-01
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    • 2018-10-11
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多