坛子里有好多人问关于wxSocket的问题因此Ryan Norton对这些问题做了集中的回答。
我把它翻译成中文贴在这里,原帖地址:Sockets FAQ/Tutorial http://wxforum.shadonet.com/viewtopic.php?t=2736
如果发现有翻译不当的地方请跟帖指正,我将及时更正。我恨把socket翻译成套接字的人,套接这个字儿曾迷惑了我好一阵子,所以socket我不做翻译,它就是socket。
1. 服务器Socket的创建
如果你要在Windows操作系统创建一个Socket你只需利用API函数Socket()创建一个就可以了。然而wxWidgets把它区分为客户端socket(用来接收服务端信息)和服务端socket(接收客户端socket的信息).下面是个例子
wxwidgets: |
2.用wxSocketServer::Accept方法与客户端取得连接。
用Accept方法是与客户端取得连接的最基本的方法,在图形用户界面的程序里不常用到。因为Accept方法会挂起主线程等待客户端直到接收到客户端。不过在控制台程序里还是很好用的:
wxwidgets: |
3. 利用事件驱动方法来响应客户端
事件驱动的方法经常用于图形用户界面程序中。需要注意的是wxBase不支持事件驱动方法所以你不能在wxBase-only程序里使用事件。子曾经曰过:君子不可不学。所以诸位看官坚持住,wxSocket不是一蹴而就的事。
(1) 准备工作和关联事件
wxwidgets: |
(2) 服务端事件(wxMyServer::OnServerEvent)
wxwidgets: |
(3) 客户端事件 (wxMyServer::OnClientEvent)
wxwidgets: |
注意在事件驱动版本中,你需要调用Destroy()或delete来注销不再用到的服务端socket
4.设置旗标wxSocketBase::SetFlags
在你调用Read或Write读写客户端socket之前你可以通过调用SetFlags来控制wxSocke如何等待数据。有5个旗标用来控制它的行为。
wxSOCKET_NOWAIT
这个旗标意味着你调用最原始的send和recv函数。与调用xSocketBase::Read或Write时一样。
wxSOCKET_NONE
默认的wxSocketBase旗标。有人认为它和wxSOCKET_NOWAIT工作方式一样但实际上wxSOCKET_NONE有很多特别之处。下面我们来解释一下。
在unix/WINSOCK有recv和send函数。有基本的fread和fwrite函数供socket使用(如果你很长没用unix/WINSOCK了那么想象一下wxFile::Read和wxFile::Write供wxSockets使用)。当你调用recv和send函数后你会得到"WOULDBLOCK"的错误消息。这表明数据还没准备好。
大多数socket程序使用select()函数来阻塞线程直到数据准备好-你可以在wx使用这个方法(wxSocketBase::GetError() == wxSOCKET_WOULDBLOCK)。忽视它然后再次调用read或write函数。
wxSOCKET_NONE和wxSOCKET_WAITALL的方式是,他们调用select函数,然后开启线程处理如果他们在主线程里那么将通过事件机制处理,最后检查错误消息是否为"WOULDBLOCK"。如果是那么忽略掉,如果不是那么返回错误消息。在wxSOCKET_NONE的情况下,它循环地做这些事情直到超时(用wxSocketBase::SetTimeout设置)否则数据大小将被传递给read/write函数。(注意这表示wxSOCKET_NOWAIT方式将忽略超时这件事)
关于在主线程里调用它的一点说明. 基本上它调用wxYield(). 问题是如果递归调用即调用了不止一次。wxYield会返回false而且不会处理任何事件因此出现了一个阻塞的情形这是我们不希望的。因此要小心确保没有在调用socket后多次调用wxYield。
wxSOCKET_BLOCK
这个旗标和wxSOCKET_NONE一样但他不产生当前线程,或不在主线程里处理用户界面事件。
wxSOCKET_WAITALL
一个潜在的危险旗标是一个无限循环直到所有数据被读完或写完或者有错误发生。-这正是wxSOCKET_WAITALL的作用。上文说过wxSOCKET_NOWAIT会忽略超时,但如果用另一种方式-不返回直到你指定读取或写入的数据全部读写完或有一个错误产生。这是非常危险的,因为如果客户端突然断开那么服务端将陷入这个无线循环中。
wxSOCKET_BLOCK | wxSOCKET_WAITALL
这个组合旗标与wxSOCKET_WAITALL相像,除了它不产生当前线程或处理事件如果在主线程的话。换句话说,这就像在一个无限循环里以wxSOCKET_BLOCK方式调用read和write直到所有的数据被发送或接收完或有错误产生才退出。
小结一下
总的来说如果你在wxSOCKET_NONE方式下小心避免递归的产生线程问题,那么你将不用设置旗标。这也是为什么我在最初的例子代码里没有提及它的原因。如果你想完全控制,那就用wxSOCKET_NOWAIT方式并且自己来侦测wxSOCKET_WOULDBLOCK错误消息。在控制台程序中最好使用wxSOCKET_BLOCK方式。如果你100%确定你能过接收到完整的数据那么就用wxSOCKET_WAITALL方式或用wxSOCKET_BLOCK | wxSOCKET_WAITALL方式如果要处理阻塞模式下的socket。
5.结合wxStreams使用wxSockets
创建wxSocketOutputStream和wxSocketInputStream类,传送一个socket给他们的构造函数。然后你就可以调用Read或Write函数了,就像普通的wxStream一样。但只能是Read或Write,Seeking/Length等方法没有用。
6.结合wxTreads使用wxSockets
在客户端socket中使用线程是很简单的。首先创建一个线程类,代码如下:
wxwidgets: |
接下来我们对前面的wxMyServer::OnSocketEvent例子稍作改动来配合线程:
wxwidgets: |
最主要的事是记得在使用socket前在线程中调用wxSocketBase::Initialize方法。这可以很容易的在你继承的wxApp类的OnInit方法里完成:
wxwidgets: |
7.其他的参考文献:
http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm
Old crash course in Unix sockets programming.
http://www.sockets.com/winsock.htm
Information and reference about the WINSOCK API.
http://www.opengroup.org/onlinepubs/009695399/basedefs/sys/socket.h.htm
Official reference for the Unix socket API.