Widgets Socket的简明教程

坛子里有好多人问关于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:
// 建立一个服务端socket wxSocketServer.... 如果出错返回NULL
      // nPort 是端口号(例如:HTTP是80, FTP常用21等等)
      wxSocketServer* wxCreateMyServerSocket ( int nPort )
      {
          //
          // 为端口(nPort)创建一个地址。
          // 只有在你指定的端口号特别不常用时才失败。
          //
          wxIPV4address addr;
          if (!addr. Service (nPort ) )
              return NULL;

          //
          //为刚才创建的地址创建一个服务端socket
          //用if语句判断是否创建成功
          //
          wxSocketServer* pServerSocket = new wxSocketServer (addr );

          //
          // 是否成功创建?
          //
          if (!pServerSocket-> Ok ( ) )
          {
              //
              // 如果因为端口已经被占用而失败则用一个消息框显示
              // 其他情况直接返回
              //
              if (pServerSocket-> LastError ( ) == wxSOCKET_INVPORT )
                      wxMessageBox (wxT ( "Port in use!" ) );
             
              pServerSocket-> Destroy ( );
              return NULL;
          }

          //
          //  如果成功则返回创建成功的服务端socket指针!
          //
          return pServerSocket;
      }



2.用wxSocketServer::Accept方法与客户端取得连接。
用Accept方法是与客户端取得连接的最基本的方法,在图形用户界面的程序里不常用到。因为Accept方法会挂起主线程等待客户端直到接收到客户端。不过在控制台程序里还是很好用的:

wxwidgets:
//
      // 通常情况是利用一个循环来接收
      // 是一个bool值用来控制循环
      // 当bServeClients被赋予false值时循环停止
      //   
      while ( bServeClients )
      {
          //
          //  调用Accept方法
          //  前面提到过,这回挂起主线程直到有客户端被接受到
          //
          wxSocketBase * pSocket = pServerSocket-> Accept ( );
         
          //
          //  如果pSocket的值是NULL那表示出错了,我们需要用break跳出循环。
          //
          if ( !pSocket )
              break;

          //
          //  OnClientConnect(pSocket)是一个你自己定义的函数
          //  用来处理读/接收和写/发送等操作
          //
          OnClientConnect (pSocket );

         
          //
          //  注销客户端socket指针pSocket
          //
          pSocket-> Destroy ( );
      }

      //
      //  注销服务端socket指针pServerSocket
      //
      pServerSocket-> Destroy ( );





3. 利用事件驱动方法来响应客户端
事件驱动的方法经常用于图形用户界面程序中。需要注意的是wxBase不支持事件驱动方法所以你不能在wxBase-only程序里使用事件。子曾经曰过:君子不可不学。所以诸位看官坚持住,wxSocket不是一蹴而就的事。

(1) 准备工作和关联事件

wxwidgets:
//
            // 用事件句柄pHandler为服务端socket指针pServerSocket建立事件
            //
            void wxMySetupSocketEvents ( wxSocketServer* pServerSocket,
                                        wxEvtHandler* pHandler )
            {
                // 用wxASSERT_MSG确保pHandler是一个有效的句柄
                wxASSERT_MSG (pHandler != NULL || wxTheApp != NULL,
                                    wxT ( "NO EVENT HANDLER!" ) );

                //
                //如果句柄值为null我们可以用wxTheApp来处理事件
                //
                if (pHandler == NULL )
                    pHandler = wxTheApp;

                //
                //设置事件句柄为ID_SERVER
                //
                pServerSocket-> SetEventHandler (*pHandler, ID_SERVER );

                //
                //指定和连接我们想接收的事件类型
                //发送服务端事件到wxMyServer::OnServerEvent
                //发送客户端事件到wxMyServer::OnSocketEvent
                //
                pHandler-> Connect (ID_SERVER, wxEVT_SOCKET,
                    (wxObjectEventFunction ) &wxMyServer::OnServerEvent );
                pHandler-> Connect (ID_SOCKET, wxEVT_SOCKET,
                    (wxObjectEventFunction ) &wxMyServer::OnSocketEvent );

                //
                //基本上只有几类事件我们可以从服务端接收,
                //通常我们只是连接事件就行了。
                //
                pServerSocket-> SetNotify (wxSOCKET_CONNECTION_FLAG );
               
                //
                //  告诉服务端我们准备好接收事件了
                //  (其他类不需要这样,这是wxSockets类的一个专利)
                //
                pServerSocket-> Notify ( true );
            }




(2) 服务端事件(wxMyServer::OnServerEvent)

wxwidgets:
void wxMyServer::OnServerEvent ( class wxSocketEvent& evt )
            {
                //我们只注册了连接事件
                //所以确保我们接收的事件类型为wxSOCKET_CONNECTION
                wxASSERT (evt. GetSocketEvent ( ) == wxSOCKET_CONNECTION );
               
                //
                // 得到服务端socket
                //
                wxSocketServer* pServerSocket = ( wxSocketServer* ) evt. GetSocket ( );
               
                //                      
                //调用Accept方法,但由于我们知道将得到一个socket所以我们告诉他不要阻塞
              //
                wxSocketBase *  pSocket = pServerSocket-> Accept ( false );

                //
                // 如果由于某种意外pSocket的值是null那么退出
                //
                if (!pSocket )
                    return;

                //
                // 设置客户端事件
                // 已经为服务端调用了Connect方法,所以我们不需要在这里调用它了
                //
                pSocket-> SetEventHandler (* ( ( wxEvtHandler* ) this ), ID_SOCKET );
               
                //
                // 接收输入事件,这表明socket已准备好了读写等操作
                // Lost事件意味着客户已从我们的服务端断开
                // 我们需要注销Socket
                //
                pSocket-> SetNotify (wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG );
               
                //
                // 调用Notify表明我们已经准备好了接收客户端socket事件
                //
                pSocket-> Notify ( true );
            }



(3) 客户端事件 (wxMyServer::OnClientEvent)

wxwidgets:
void  wxMyServer::OnSocketEvent ( class wxSocketEvent& evt )
            {
                //
                // 测试事件为我们注册的类型
                //
                wxASSERT (evt. GetSocketEvent ( ) == wxSOCKET_INPUT ||
                         evt. GetSocketEvent ( ) == wxSOCKET_LOST );

                //
                // 获得客户端socket
                //
                wxSocketBase* pSocket = evt. GetSocket ( );

                //
                // 如果我们得到一个输入事件,这意味着我们可以通过这个客户socket读/写
                //
                if (evt. GetSocketEvent ( ) == wxSOCKET_INPUT )
                {
                    //
                    //  只注册Lost事件
                    //  我们不再对输入事件作响应
                    //
                    pSocket-> SetNotify (wxSOCKET_LOST_FLAG );

                    //
                    //  和Accept例子一样
                    //  这是个你自己定义的函数用来从客户端socket读/写等.
                    //
                    OnClientConnect (pSocket );
                }

                //
                // 注销客户端socket
                //
                pSocket-> Destroy ( );
            }


注意在事件驱动版本中,你需要调用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:
class wxMyServerThread : public wxThread
      {
          wxMyServer*   m_pServer;  //服务端socket
          wxSocketBase* m_pSocket;  //客户端socket,用来读写
      public:
          wxMyServerThread (wxMyServer* pServer, wxSocketBase* pSocket ) :
                  m_pServer (pServer ), m_pSocket (pSocket ) { }
          ~wxMyServerThread ( ) { }

          virtual ExitCode Entry ( )
          {
              //
              //  还记得上面例子中的wxMyServer::OnSocketEvent方法吗?
              //  和那里一样。我们在这进行读写socket的操作。
              //  要记得你是在使用多线程所以要注意线程安全
              //
              m_pServer-> OnClientConnect (m_pSocket );
             
              //
              //  在使用完客户端socket后注销它。
              //
              m_pSocket-> Destroy ( );
              return 0;
          }
      };



接下来我们对前面的wxMyServer::OnSocketEvent例子稍作改动来配合线程:

wxwidgets:
void  wxMyServer::OnSocketEvent ( class wxSocketEvent& evt )
      {
          //
          // 检测我们注册过的事件
          //
          wxASSERT (evt. GetSocketEvent ( ) == wxSOCKET_INPUT ||
                   evt. GetSocketEvent ( ) == wxSOCKET_LOST );

          //
          // 获得客户端socket
          //
          wxSocketBase* pSocket = evt. GetSocket ( );

          //
          // 如果我们得到一个输入事件那意味着我们可以向这个客户端socket里读或写了
          //
          if (evt. GetSocketEvent ( ) == wxSOCKET_INPUT )
          {
              //
              //  只注册Lost事件,因为我们不再输入了
              //
              pSocket-> SetNotify (wxSOCKET_LOST_FLAG );

              //
              //  创建和运行线程
              //  我们不需要使wxMyServerThread成为类成员变量
              //  因此在结束时wxThread会自动注销它自己当然你也可以手动注销
              //       
              wxMyServerThread* pThread =
                      new wxMyServerThread ( this, pSocket );
              pThread-> Create ( );
              pThread-> Run ( );
          }
          else
          {
              //
              // 当不再用客户端socket后注销它。
              // 但我们只针对Lost事件这么做。
              // 我们在线程最后针对读写事件注销socket。
              //
              pSocket-> Destroy ( );
          }
      }



最主要的事是记得在使用socket前在线程中调用wxSocketBase::Initialize方法。这可以很容易的在你继承的wxApp类的OnInit方法里完成:

wxwidgets:
bool MyApp::OnInit ( )
      {
          //这允许我们在第二个线程中用wxSocket
          wxSocketBase::Initialize ( );
         
          //...
          return true;
      }





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.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值