非阻塞 socket 在send之后返回 WSAEWOULDBLOCK分析

当使用非阻塞socket发送数据时遇到WSAEWOULDBLOCK异常,意味着输出缓冲区满或忙。本文介绍了如何通过监听FD_WRITE事件来判断何时能重新发送数据,而非简单地Sleep。详细阐述了FD_WRITE事件在连接建立、接受连接和发送失败后的触发条件,并提供了一段示例代码展示如何等待并响应FD_WRITE事件来重试发送。

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

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


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

 

下面主要对FD_WRITE的方法进行描述。引用网友的文章

https://blog.youkuaiyun.com/gotosola/article/details/60578704

 

FD_WRITE事件只有在以下三种情况下才会触发

①client 通过connect(WSAConnect)首次和server建立连接时,在client端会触发FD_WRITE事件

②server通过accept(WSAAccept)接受client连接请求时,在server端会触发FD_WRITE事件

③send(WSASend)/sendto(WSASendTo)发送失败返回WSAEWOULDBLOCK,并且当缓冲区有可用空间时,则会触发FD

<think>我们正在讨论MFC中的非阻塞Socket通信实现。根据引用内容,我们知道在MFC中实现非阻塞Socket通信的关键是利用CSocketWnd作为消息池,通过事件通知机制来处理多个客户端连接和通信。下面将详细说明实现步骤。 ### 1. 使用CAsyncSocket类 MFC提供了`CAsyncSocket`类,它封装了异步非阻塞Socket通信的基本功能。我们可以通过继承`CAsyncSocket`并重写其事件处理函数来实现非阻塞通信。 ```cpp class CMySocket : public CAsyncSocket { public: virtual void OnAccept(int nErrorCode); virtual void OnConnect(int nErrorCode); virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); // ... 其他事件处理函数 }; ``` ### 2. 创建Socket并设置为非阻塞模式 在创建Socket时,默认就是非阻塞的(因为`CAsyncSocket`就是为异步设计的)。我们只需要调用`Create`函数创建Socket即可。 ```cpp // 服务器端监听Socket CMySocket listenSocket; listenSocket.Create(1234, SOCK_STREAM); // 在1234端口创建TCP Socket listenSocket.Listen(); // 开始监听 ``` ### 3. 处理事件 当有客户端连接请求时,会触发`OnAccept`事件;当有数据到达时,会触发`OnReceive`事件。我们需要在这些事件处理函数中编写相应的逻辑。 例如,在`OnAccept`中接受连接: ```cpp void CMySocket::OnAccept(int nErrorCode) { if (nErrorCode == 0) { CMySocket* pClient = new CMySocket; if (Accept(*pClient)) { // 成功接受连接,将pClient保存起来以便后续通信 } else { delete pClient; } } CAsyncSocket::OnAccept(nErrorCode); } ``` 在`OnReceive`中接收数据: ```cpp void CMySocket::OnReceive(int nErrorCode) { if (nErrorCode == 0) { char buffer[1024]; int nReceived = Receive(buffer, sizeof(buffer) - 1); if (nReceived > 0) { buffer[nReceived] = '\0'; // 处理接收到的数据 } } CAsyncSocket::OnReceive(nErrorCode); } ``` ### 4. 消息循环 MFC内部有一个隐藏窗口(`CSocketWnd`)用于接收Socket事件消息,并将这些消息分发给对应的`CAsyncSocket`对象。因此,我们需要确保程序中有消息循环(在MFC应用程序中通常由主框架处理)。 ### 5. 注意事项 - 在非阻塞模式下,发送数据时如果发送缓冲区已满,`Send`函数会返回一个错误`WSAEWOULDBLOCK`。此时,我们需要等待`OnSend`事件(当可以继续发送时触发)再进行发送。 - 需要管理好客户端Socket对象的生命周期,避免内存泄漏。 ### 6. 使用CSocket类 虽然`CSocket`类默认是阻塞的,但可以通过将其与`CSocketFile`和`CArchive`结合使用来实现伪异步(实际上是在后台线程中处理阻塞操作)。然而,对于真正的非阻塞通信,建议使用`CAsyncSocket`。 ### 7. 一个简单的非阻塞服务器示例 以下是一个简单的非阻塞服务器示例代码框架: ```cpp // 监听Socket类 class CListenSocket : public CAsyncSocket { public: virtual void OnAccept(int nErrorCode); }; void CListenSocket::OnAccept(int nErrorCode) { if (nErrorCode == 0) { CClientSocket* pClient = new CClientSocket; if (Accept(*pClient)) { // 保存客户端Socket指针 } else { delete pClient; } } CAsyncSocket::OnAccept(nErrorCode); } // 客户端Socket类 class CClientSocket : public CAsyncSocket { public: virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); }; void CClientSocket::OnReceive(int nErrorCode) { // 接收数据并处理 } void CClientSocket::OnClose(int nErrorCode) { // 客户端关闭连接,清理资源 Close(); delete this; // 假设在堆上创建,自行管理生命周期 } ``` ### 8. 引用说明 在MFC中,非阻塞Socket通信的实现依赖于内部的消息机制[^1]。通过`CAsyncSocket`类,我们可以方便地处理各种网络事件[^3]。同时,需要注意在非阻塞模式下对发送和接收操作的处理,避免阻塞主线程[^2]。 §§相关问题§§ 1. 如何在MFC中处理非阻塞Socket的发送缓冲区满的情况? 2. 如何管理多个客户端连接以及如何高效地处理数据? 3. `CAsyncSocket`和`CSocket`在非阻塞通信中有什么区别?</think>### MFC 非阻塞 Socket 通信实现方法 在 MFC 中实现非阻塞 Socket 通信的核心是使用 `CAsyncSocket` 类,它封装了 Windows Socket API 的异步操作机制。以下是详细实现步骤: #### 1. 创建自定义 Socket 类 继承 `CAsyncSocket` 并重写关键事件处理函数: ```cpp class CMySocket : public CAsyncSocket { public: virtual void OnAccept(int nErrorCode); virtual void OnConnect(int nErrorCode); virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); // 其他事件:OnSend(), OnOutOfBandData() }; ``` #### 2. 服务器端实现 **步骤 1:创建监听 Socket** ```cpp // 在应用程序类或对话框初始化中 CMySocket m_listenSocket; // 创建监听Socket非阻塞模式) m_listenSocket.Create(1234); // 指定端口 m_listenSocket.Listen(); // 开始监听 ``` **步骤 2:处理连接请求 (`OnAccept`)** ```cpp void CMySocket::OnAccept(int nErrorCode) { if (nErrorCode == 0) { CMySocket* pClient = new CMySocket; if (Accept(*pClient)) { // 成功接受连接,保存pClient指针 } else { delete pClient; // 连接失败 } } CAsyncSocket::OnAccept(nErrorCode); } ``` #### 3. 客户端实现 **步骤 1:连接服务器** ```cpp CMySocket m_clientSocket; m_clientSocket.Create(); // 创建客户端Socket m_clientSocket.Connect("192.168.1.100", 1234); // 异步连接 ``` **步骤 2:处理连接结果 (`OnConnect`)** ```cpp void CMySocket::OnConnect(int nErrorCode) { if (nErrorCode == 0) { // 连接成功,可开始通信 } else { // 连接失败处理 } } ``` #### 4. 数据收发处理 **接收数据 (`OnReceive`)** ```cpp void CMySocket::OnReceive(int nErrorCode) { if (nErrorCode == 0) { char buffer[1024]; int nReceived = Receive(buffer, sizeof(buffer)-1); if (nReceived > 0) { buffer[nReceived] = '\0'; // 处理接收到的数据 } } CAsyncSocket::OnReceive(nErrorCode); } ``` **发送数据** ```cpp void SendData(const CString& data) { // 非阻塞发送(可能不会立即发送全部数据) int nSent = Send(data.GetBuffer(), data.GetLength()); if (nSent == SOCKET_ERROR) { if (GetLastError() == WSAEWOULDBLOCK) { // 等待OnSend事件再重试 } } } ``` #### 5. 关闭连接 ```cpp void CMySocket::OnClose(int nErrorCode) { // 对方关闭连接 ShutDown(); Close(); delete this; // 如果动态创建 } ``` #### 关键技术要点 1. **事件驱动机制**:MFC 通过隐藏窗口 `CSocketWnd` 接收 Socket 事件消息,并路由到对应的 `CAsyncSocket` 对象[^1] 2. **非阻塞特性**: - 所有操作立即返回 - 耗时操作通过事件回调通知结果 - 需处理 `WSAEWOULDBLOCK` 错误码 3. **多客户端管理**: - 使用 `CList` 或 `CMap` 管理客户端 Socket 对象 - 每个连接创建独立的 `CMySocket` 实例 4. **线程安全**: - 所有 Socket 操作在主线程进行 - 无需额外同步机制(消息队列天然线程安全) > ⚠️ **重要提示**:避免在对话框中使用模态对话框或阻塞操作,否则会中断消息泵导致事件无法处理[^3]。 #### 引用说明 MFC 的非阻塞通信依赖于内部的消息泵机制,通过 `CSocketWnd` 窗口处理所有 Socket 事件通知[^1]。相比阻塞模式,这种设计能高效处理多客户端连接,但需注意正确处理异步事件和错误码[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值