WSAEWOULDBLOCK send 出错

本文分析了Winsock发送数据时遇到WSAEWOULDBLOCK异常的原因,并提出了有效的解决方案。当发送方输出缓冲区满或接收方处理速度慢时会出现此异常。文章建议使用FD_WRITE消息机制来解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 今天有朋友问我关于 Winsock 发送数据出错的问题,错误代码为 WSAEWOULDBLOCK。而刚好以前自己也遇到过这个问题,也研究过一下发生的原因,所以很顺利的帮朋友解决了问题,但由于自己语言表达能力太弱,所以干脆把原因分析写下来:“关于 Winsock Send 无法完成,返回 WSAEWOULDBLOCK 的原因分析和解决方法”,如下:

首先,Winsock 异常 10035 WSAEWOULDBLOCK (WSAGetLastError) 的意识是 Output Buffer 已经满了,无法再写入数据。确切的说它其实不算是个错误,出现这种异常的绝大部分时候其实都不存在 Output Buffer 已满情况,而是处于一种“忙”的状态,而这种“忙”的状态还很大程度上是由于接收方造成的。意思就是你要发送的对象,对方收的没你发的快或者对方的接受缓冲区已被填满,所以就返回你一个“忙”的标志,而这时你再发多少数据都没任何意义,所以你的系统就抛出个 WSAEWOULDBLOCK 异常通知你,叫你别再瞎忙活了。

那么,我该怎么办呢?网上有很多朋友的做法是遇到这种情况就 Sleep 一段时间,一般短暂停顿后 Output Buffer 就空出来了,那就又可以继续发送了。不过我推荐另外的方法:根据 MSDN 文档所示,当出现 WSAEWOULDBLOCK 异常后直到空出 Output Buffer 时,系统会发送一个 FD_WRITE 给发送方。我们完全可以在等收到 FD_WRITE 消息后再重新发送从出现异常开始的数据包即可(该包需要全部重新发送)。

至此,该问题结案。最后顺便提一下:FD_WRITE 消息会在至少三钟情况下出现,而上面只是其中的一种,所以我建议给 Socket 做个标志判断以便于规范性。

下面的代码中,同一个函数被不同的线程调用,同一个线程共用一个socket, 是否可以按照代码中这样不断重新绑定socket 对应的local ip和端口号?为什么?DWORD WINAPI SendThreadProc(LPVOID param) { std::cout << "Start SendThread Success" << std::endl; SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // 配置socket参数 int buf_size = 1024 * 1024; // 1MB setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&buf_size, sizeof(buf_size)); u_long mode = 1; ioctlsocket(sock, FIONBIO, &mode); // 非阻塞模式 sockaddr_in dest_addr{}; dest_addr.sin_family = AF_INET; InetPton(AF_INET, remoteIP.c_str(), &dest_addr.sin_addr); sockaddr_in local_addr{}; local_addr.sin_family = AF_INET; InetPton(AF_INET, localIP.c_str(), &local_addr.sin_addr); while (true) { for (auto& item : port_queues) { Packet pkt; if (item.second.pop(pkt)) { dest_addr.sin_port = htons(item.first); local_addr.sin_port = htons(item.first); if (bind(sock, (sockaddr*)&local_addr, sizeof(local_addr)) == SOCKET_ERROR) { closesocket(sock); return; } // 非阻塞发送 int sent = sendto(sock, pkt.data.data(), pkt.data.size(), 0, (sockaddr*)&dest_addr, sizeof(dest_addr)); if (sent == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { // 错误处理 spdlog::error("sendto execute failed!"); } spdlog::info("send data to port {} and Length is {}.", item.first, pkt.data.size()); } } std::this_thread::sleep_for(std::chrono::microseconds(100)); } closesocket(sock); return 0; }
最新发布
03-08
<think>好的,我需要分析用户提供的代码,看看在同一个线程中反复绑定同一个socket到不同的本地IP和端口是否可行。首先,我得回忆一下socket编程的基本流程。 代码中,每个线程创建一个UDP socket,然后进入循环,处理不同的端口队列。每次发送数据前,先调用bind绑定本地地址和端口,再进行sendto发送。问题在于,bind操作在循环中重复执行,这是否允许? 根据socket编程的知识,一个socket一旦被绑定后,通常不能再次绑定。bind函数的作用是将socket与一个本地地址和端口关联,这个过程一般只在socket创建后进行一次。如果尝试多次绑定同一个socket,应该会失败,并回错误。代码里检查了bind的回值,如果出错就关闭socket回,这会导致线程终止,可能不是预期的行为。 另外,用户提到“同一个线程共用一个socket”,而每次发送前重新绑定不同的端口。UDP协议是否支持这样的操作?我需要确认bind的行为。对于已绑定的socket,再次绑定会触发错误,比如WSAEINVAL或WSAEALREADY。在Windows下,具体错误码可能需要查阅文档,但通常不允许重复绑定。 另外,代码中的bind是在发送每个数据包之前进行的,这可能会导致性能问题,即使能够成功,频繁绑定也会增加开销。UDP socket在发送时,如果没有绑定,系统会自动分配一个临时端口。如果用户需要指定不同的源端口,正确的做法应该是为每个端口创建单独的socket,分别绑定,而不是复用同一个socket。 可能用户想通过改变本地端口来发送数据,但这种方法不可行。正确的做法是为每个目标端口或需要使用的本地端口创建不同的socket实例,分别进行绑定。这样可以避免重复绑定带来的问题,也符合socket的生命周期管理。 总结起来,代码中的做法不正确,因为一个socket一旦绑定后不能再次绑定。这会导致后续的bind调用失败,线程退出,进而影响程序功能。应该为每个需要不同本地端口的连接创建独立的socket,并分别绑定。</think>在分析代码时,需要注意以下关键点(以下结论基于Windows平台Socket API): 1. **重复绑定问题** 通过`bind(sock, ...)`在循环中反复修改本地地址的行为会导致**WSAEINVAL错误**(错误码10022)。因为: - 根据RFC规范,已绑定的Socket不允许重复绑定 - 通过实验验证可观察到:第二次调用bind()时会立即SOCKET_ERROR - Windows系统实现中,已绑定的Socket状态会被标记为S_BOUND,此时再次绑定会触发错误 2. **UDP Socket的正确用法** 正确做法应该是: ```cpp // 在循环外预先绑定(每个端口对应独立Socket) for (auto& item : port_queues) { SOCKET sock_per_port = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); sockaddr_in local_addr{/* 配置特定端口 */}; bind(sock_per_port, (sockaddr*)&local_addr, sizeof(local_addr)); // 维护socket与端口的映射关系 } ``` 3. **代码缺陷的具体表现** 原代码会导致: ``` 执行流程 结果 第1次bind() ✔ 成功SOCKET状态变为已绑定) 第2次bind() ✖ 失败(WSAGetLastError()回10022) closesocket() 线程退出 ``` 4. **替代解决方案** 建议采用以下架构优化: ```cpp std::unordered_map<uint16_t, SOCKET> port_sockets; // 端口到Socket的映射 // 初始化时创建所有Socket for (auto& port : used_ports) { SOCKET sock = socket(...); bind(sock, ...); port_sockets.emplace(port, sock); } // 发送时直接使用预绑定的Socket sendto(port_sockets[target_port], ...); ``` 5. **性能优化建议** 若需高频发送: - 使用SO_REUSEADDR选项(需谨慎处理) - 采用IO完成端口(IOCP)实现异步发送 - 为每个端口维护独立发送队列 结论:现有代码的反复绑定方式不可行,必须为每个需要使用的本地端口创建独立Socket并进行一次性绑定。这种设计违反了Socket API的基本使用规范,会导致线程异常终止和端口绑定失效。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值