[英]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();
目标系统: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.