繁体   English   中英

UDP 客户端套接字读取较少数据然后发送

[英]UDP client socket read less data then sent

我面临一个非常奇怪的问题。 我有一个运行 UDP 套接字并等待传入数据的服务器应用程序。 一旦它收到命令,它就会开始发回 stream。为了测试,我将服务器限制为仅发送一条 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

output:

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

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

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

在UDP中,发送和读取是1:1的关系。 如果 UDP 服务器发送一条 8000 字节的消息,则客户端必须在一次读取中接收整个消息,它不能像您尝试的那样在多次读取中接收它。

如果您正在读取的缓冲区太小而无法接收整个消息,则读取将失败并EMSGSIZE错误代码,并且未读取的字节将被丢弃,您无法恢复它们。

这就是为什么您的后续读取失败( EWOULDBLOCK / EAGAIN错误代码)的原因,因为在服务器发送新消息之前没有数据可供读取。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM