18.3 Socket 标记
Socket 的行为可能随着创建时指定的不同而有很大的差异,下表列出了 Socket 可以指定的标记:
wxSOCKET_NONE | 普通行为(行为和底层的 send 和 recv 函数一致)。 |
---|---|
wxSOCKET_NOWAIT | 读和写操作尽可能快速的返回。 |
wxSOCKET_WAITALL | 等待所有的读写数据完成操作,除非出现系统错误。 |
wxSOCKET_BLOCK | 在读写数据的时候阻塞 GUI 界面。 |
如果没有指定任何标记(或者指定了 wxSOCKET_NONE 标记),I/O 操作将在部分数据被读写的时候返回,甚至在整个数据还没有传输完的情况下也是这样.这和使用阻塞方式调用底层的 recv 或者 send 函数的效果是一样的.注意这里所说的阻塞指的是函数被阻止返回,并不意味着图形用户界面被冻结。
如果指定了 wxSOCKET_NOWAIT 标记,I/O 将立刻返回.对于读操作,它将读取所有当前输入缓冲区拥有的数据后立刻返回,对于写操作,它将尽可能多的发送数据以后立刻返回,这取决于当前的输出缓冲区的大小,这种方式等同于使用非阻塞方式调用底层函数 recv 或 send.同样,这里的阻塞也指的是函数返回,而不是用户界面阻塞。
如果指定了 wxSOCKET_WAITALL 标记,I/O 操作将在所有要求的数据被读取或者被写入以后(或者发生系统错误) 才会返回. 如果需要的话,将以阻塞的方式调度底层系统函数.这相当于使用一个循环重复以阻塞的方式调用 recv 或者 send 函数以便传输所有的数据.同样,这里的阻塞也指的是阻塞底层函数而不是 GUI.注意 ReadMsg 和 WriteMsg 函数将隐式使用这种方式,并且忽略你可能设置的 wxSOCKET_NONE 或 wxSOCKET_NOWAIT 标记。
用来指示 wxSOCKET_BLOCK 是否在 I/O 操作的间隙执行 Yield 操作(译者注:参见前面关于线程的替代方案中的描述),如果指定了这个标记,socket 在底层操作间隙将不会执行 Yield 动作,反之,如果没有指定这个标记,那么你要非常小心这可能产生的代码重入的问题。
总的来说:
- wxSOCKET_NONE 总是试图读取或者写入一些数据,但是不关心具体是多少数据。
- wxSOCKET_NOWAIT 只关心尽快返回,即使没有读取或写任何数据。
- wxSOCKET_WAITALL 将在所有的数据都被写入或者是读取的数据达到要求的数目的时候返回。
- wxSOCKET_BLOCK 和前面的标记没有关系,只控制否则在底层操作的间隙执行 Yield 动作。
wxWidget 中的阻塞和非阻塞 socket
在 wxWidgets 中的阻塞方式有双重的含义.在一般的编程中,socket 阻塞意味着当前的线程被挂起直至 socket 操作超时或者数据操作完成.如果是主线程阻塞,则用于界面也相应阻塞。
而在 wxWidgets 中,有两种类型的阻塞:socket 阻塞和用户界面阻塞.wxSOCKET_BLOCK 标记指示在 socket 阻塞的时候,是否同时阻塞用户界面.你也许回问,这怎么可能呢,怎么可能作到阻塞了 socket 操作而不阻塞用户界面呢?这主要是因为在 socket 被阻塞的时候,wxWidgets 还可以处理所有的事件,因为 socket 的底层函数处理的间隙调用了 wxYield,这个函数可以处理队列中所有未处理的事件,包括用户界面相关的事件.虽然在 socket 操作未结束之前,代码一直在 socket 函数中运转,但是所有事件还是可以被有序的处理。
对于刚开始使用 wxWidgets 的人来说,听上去这是一个很美妙的事情.如果你是第一次使用 wxWidgets 进行 socket 编程,你可能会觉得,再也不需要使用任何单独的线程来处理 socket 了,你可以将 socket 设置为 wxSOCKET_WAITALL 和 wxSOCKET_BLOCK,这样你可以通过事件机制处理 socket 数据,而 GUI 也不会被阻塞,不幸的是,我必须先警告你,这种想法可能是你痛苦的开始。
让我们来假设一个服务端有两个活动连接,每一个都设置了 wxSOCKET_WAITALL 标记.更进一步,我们假设其中一个连接正在以一种很缓慢的速度传输一个很大量的数据.Socket 1 的读缓冲区没有数据了,因此它调用了 wxYield,而 Socket2 还有未处理的事件在队列里,这时候会发生的事情是:Socket1 调用的 wxYield 试图处理 Socket2 相关的消息,但是因为 Socket2 的连接很缓慢,它的事件总是结束不了,它也会调用 wxYield 来避免 GUI 阻塞,这将导致出现一个名声不太好的告警消息"wxYield called recursively"(wxYield 被递归调用),在 Socket2 的数据传输结束之前,应用程序的堆栈将最终被 wxYield 的递归调用给耗尽,因为递归调用使用这些堆栈一直没有机会释放,于是人们开始联系 wxWidgets 社区,报告发现了一个 bug,而实际上,这应该是应用程序自己的问题而不是 wxWidgets 的问题.应用程序不应该以这种方式来编程,它应该避免这种情形出现,因此,恐怕 wxWidgets 永远没有办法改正这个问题。
另外一方面,性能也是一个问题,为了不阻止 GUI,你的应用程序将不得不浪费 CPU 的资源.试想一下,用户界面要立即响应, Socket 也要不停的监视是否有数据到来以便产生事件通知应用程序,要让两者都得到满足,wxWidgets 所能做的唯一的办法就是使用循环,不停的以非阻塞的方式去用系统操作 select 去测试 socket,然后再调用 wxYield 处理 GUI 事件。
不可用的标记组合
容我再罗嗦一句,不要天真到认为 wxWidgets 采用了一种神奇的 socket 处理机制.无论这些 Socket 的标记在你的第一印象中看起来是多么的诱人,你都不可能同时满足下面的这些要求:
- wxSOCKET_WAITALL
- 不阻塞 GUI
- 少于 100%的 CPU 占用率
- 单线程
你可以指定 wxSOCKET_WAITALL 并且也不阻塞 GUI,但是这将导致 100%的 CPU 占用率.如果你愿意付出阻塞 GUI 作为代价(指定 wxSOCKET_BLOCK 标记),你将可以得到 wxSOCKET_WAITALL 和 0%的 CPU 占用率. 或者你可以使用多线程来实现同时使用 wxSOCKET_WAITALL 又不阻塞 GUI,并且也不用 100%的 CPU 占用率.你也可以不用 wxSOCKET_WAITALL 而使用 wxSOCKET_NOWAIT 以便可以既不占用 100%的 CPU 又不堵塞 GUI.总之一句话,上面的四个条件,你总可以同时满足任意三个,但是你不可以四个条件同时满足。
这些标记是怎样影响 Socket 的行为的
wxSOCKET_NONE, wxSOCKET_NOWAIT 和 wxSOCKET_WAITALL 是互斥的,你不可以同时使用他们中间的任何两个,而 wxSOCKET_BLOCK 和 wxSOCKET_NOWAIT 的组合也是没有意义的 (如果任何函数都立即返回,怎么可能阻塞 GUI 呢?), 因此下面五种标记的组合是有意义的:
- wxSOCKET_NONE | wxSOCKET_BLOCK: 这种组合和标准的 socket 调用(recv 和 send) 的行为相同。
- wxSOCKET_NOWAIT: 和标准的非阻塞的 socket 调用行为相同。
- wxSOCKET_WAITALL | wxSOCKET_BLOCK: 和普通的阻塞式 socket 调用的行为相同,只不过 recv 和 send 函数将被连续多次调用以便接受或者发送完整的数据。
- wxSOCKET_NONE: 和标准的 socket 调用行为相同,只是由于在完成系统调用之前(比如数据完整接收缓冲区数据之前) 调用了 wxYield,因此看上去 GUI 并不会阻塞。
- wxSOCKET_WAITALL: 和 wxSOCKET_WAITALL | wxSOCKET_BLOCK 的行为相同只不过 GUI 将不被阻塞。
只有最后两种情况可能出现前面介绍的 wxYield 函数重入的问题,不过这俩组标记也是在 wxWidgets 中基于事件的 socket 编程中最主要的两种方式(因为他们阻塞了 socket 但是却不阻塞 GUI).使用这两组标记的时候要非常小心避免这个问题,虽然它们很强大,很有用,却也往往是错误和麻烦的根源,因为它们太容易被误解了。
标准 socket 和 wxSocket
使用 wxSOCKET_NONE | wxSOCKET_BLOCK 或 wxSOCKET_NOWAIT 的时候和直接使用 socket 系统调用的效果并没有不同,唯一的不同是你使用的是 wxWidgets 提供的 API 而不是标准 C 的 API.不过,即使这样,还是有足够的理由要使用 wxSocket,这些理由包括:wxSocket 提供了一个面向对象的接口,隐藏了很多平台相关的初始化代码,还提供了一些高级的函数比如 WriteMsg 和 ReadMsg.另外,下一节我们也将看到, wxSocket 也使我们可以用流的方式来操作 socket。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论