【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto() 您所在的位置:网站首页 10035怎么读 【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()

【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()

2024-07-12 03:24| 来源: 网络整理| 查看: 265

@zhz:

疑问:有时候会看到某些代码,sendto()时用了while循环, 而recvfrom()时没使用while循环?

答:他们都可以使用循环语句,可参考TCP数据粘包的处理。

什么时候需要使用循环,什么时候不使用循环,可以看下面的分析:

以下其实是我根据自己项目使用的udp协议中的recvfrom()和sendto()进行测试没问题后分析的。但是对于TCP粘包的问题,却并非如此,并非recv每次只取一次完整发送的数据(UDP的recvfrom()为什么可以取这么准?),我目前还没测试。 1. recvfrom()要使用与不使用循环的情况:

我们通常指定的接收端一次接收长度都会 >= 发送端一次发送的数据长度。通常情况下,我们发送端一次发送的数据长度都不会是固定的,所以就需要接收端设置一个合适的固定的接收长度,这个固定长度需要大于等于发送端一次发送的最大数据长度。

当recvfrom()函数指定buf的长度后,并且一次recvfrom()函数读取到的数据小于指定长度max_length(这个是可以保证的),那么:

如果能确定每次recvfrom()实际读取到的数据是发送端一次发送的完整数据,那就不用循环recvfrom()。如果每次recvfrom()实际读取到的数据不是发送端一次发送的完整数据,就需要循环recvfrom()。 2.sendto()要使用与不使用循环的情况:

sendto()一般情况下需要使用循环,因为假如一个数据包太大,如长度为10MB,一次sendto()发送到输出缓冲区可能发不完整,此时就需要对sendto()使用循环发送,直到把10MB的数据都拷贝到输出缓冲区。 sendto()函数中参数指定的数据长度,就是本次发送(就是写入输出缓冲区)的数据长度,都会提前计算好之后再填入,每次发送的数据长短可能不一样,所以他就不是固定长度的。 而recvfrom()函数中指定的长度是固定的。

3.recvfrom()和sendto()例子:

recvfrom()和sendto()的第三个参数len都是指定第二个参数buf的长度。

1.recvfrom()从输入缓冲区中拷贝数据到应用程序缓冲区buf,在此需要指定buf的长度。他的长度一般在定义缓冲buf时就定下来了,如 constexpr std::size_t kBufferSize = 1024; ... uint8_t buf[kBufferSize] = {0}; //定义buf时,长度也定下来了 std::memset(buf, 0, kBufferSize); ... length = data_stream_->read(buf, kBufferSize, 0); 上面的read()函数会调用: ret = ::recvfrom(sockfd_, buffer, kBufferSize, 0) 2.sendto()从输出缓冲区中拷贝数据到应用程序缓冲区buf,在此需要指定buf的长度。他的长度都会提前计算好之后再填入,每次发送的数据长短可能不一样,所以他就不是固定长度的: constexpr std::size_t kBufferSize = 1024; char buf[kBufferSize] = "abcd"; // proto_msg_是一个已经赋值后的protobuf消息的变量 int proto_msg_length = proto_msg_.ByteSize(); // 把 proto_msg_ 序列化进buf,从buf第四字节开始,前四个字节为"cidi" proto_msg_.SerializeToArray(&buf[4], proto_msg_length); int send_size = proto_msg_length + 4; // 加上"cidi"四个字节 // 虽然 buf有1024字节,但是只将他的前 send_size 字节写入输出缓冲区 std::size_t len = data_stream_->write(reinterpret_cast(buf), send_size, 0); 上面的write()函数会调用: ret = ::sendto(sockfd_, buffer, kBufferSize, 0) sendto() size_t UdpStream::write(const uint8_t* data, size_t length, uint8_t flag) { size_t total_nsent = 0; // if (flag) { // peer_sockaddr_.sin_addr.s_addr = htonl(INADDR_BROADCAST); // } peer_sockaddr_.sin_addr.s_addr = peer_addr_; peer_sockaddr_.sin_port = peer_broad_port_; SDEBUG // error if (errno == EINTR) { continue; } else { // error if (errno == EPIPE || errno == ECONNRESET) { status_ = Stream::Status::DISCONNECTED; errno_ = errno; } else if (errno != EAGAIN) { status_ = Stream::Status::ERROR; errno_ = errno; } return total_nsent; } } total_nsent += nsent; length -= nsent; data += nsent; } return total_nsent; } recvfrom() size_t UdpStream::read(uint8_t* buffer, size_t max_length, uint8_t flag) { ssize_t ret = 0; struct sockaddr_in addrfrom; addrfrom.sin_addr.s_addr = htonl(INADDR_ANY); if (flag) { peer_sockaddr_.sin_addr.s_addr = htonl(INADDR_ANY); } else { addrfrom.sin_addr.s_addr = peer_sockaddr_.sin_addr.s_addr; } while ((ret = ::recvfrom(sockfd_, buffer, max_length, 0, (struct sockaddr*)&peer_sockaddr_, reinterpret_cast(&socklenth_))) continue; } else { // error if (errno != EAGAIN) { status_ = Stream::Status::ERROR; errno_ = errno; } } return 0; } // 接收来自本车obu的数据包:0x63,0x69,0x64,0x69分别表示cidi的ASCII码:99,105,100,105 // 如果不是"cidi",1.如果是单播,就把ip保持为上一次成功单播的ip; // 2.如果是广播,就把ip设为0.0.0.0(即htonl(INADDR_ANY)),即本机任意网卡的ip if (0x63 != buffer[0] && 0x69 != buffer[1] && 0x64 != buffer[2] && 0x69 != buffer[3]) { peer_sockaddr_.sin_addr.s_addr = addrfrom.sin_addr.s_addr; } // // 0x60,0x61 分别对应'`'和'a'的ASCII码 96(`),97(a) // if (buffer[0] != 0x60 && buffer[1] != 0x61) { // peer_sockaddr_.sin_addr.s_addr = addrfrom.sin_addr.s_addr; // } SDEBUG


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有