[已译完]Sockets FAQ/Tutorial

这是wxWidgets论坛的中文版本。在这里,您可以用您的母语汉语讨论上面任一子论坛所涉及的所有关于wxWidgets的话题。欢迎大家参与到对有价值的帖子的中英互译工作中来!
Post Reply
Utensil
Moderator
Moderator
Posts: 423
Joined: Sun Feb 03, 2008 11:38 am
Location: China

[已译完]Sockets FAQ/Tutorial

Post by Utensil »

征求志愿者翻译

Sockets FAQ/Tutorial

http://forums.wxwidgets.org/viewtopic.php?t=2736

有关志愿者翻译事务,请参见 对wx论坛的中文版块的展望

谢谢您的支持与参与! :)
Last edited by Utensil on Sat Jun 28, 2008 1:49 pm, edited 1 time in total.
In fascination of creating worlds by words, and in pursuit of words behind the world.

On Github: http://utensil.github.com
Technical Blog in Chinese: http://utensil.iteye.com/
00061205
Knows some wx things
Knows some wx things
Posts: 41
Joined: Mon Jun 16, 2008 3:43 am
Location: Beijing, China

Re: [Translation]Sockets FAQ/Tutorial

Post by 00061205 »

Utensil wrote:征求志愿者翻译

Sockets FAQ/Tutorial

http://forums.wxwidgets.org/viewtopic.php?t=2736

有关志愿者翻译事务,请参见 对wx论坛的中文版块的展望

谢谢您的支持与参与! :)
我来翻译。翻译完了发在哪?这里还是Ryan Norton的帖子后面?
Utensil
Moderator
Moderator
Posts: 423
Joined: Sun Feb 03, 2008 11:38 am
Location: China

Post by Utensil »

欢迎你来到wxWidgets中文论坛!谢谢你的热心!

目前翻译的成果先放在这里,以后计划移到wxWiki上。

-Utensil
In fascination of creating worlds by words, and in pursuit of words behind the world.

On Github: http://utensil.github.com
Technical Blog in Chinese: http://utensil.iteye.com/
00061205
Knows some wx things
Knows some wx things
Posts: 41
Joined: Mon Jun 16, 2008 3:43 am
Location: Beijing, China

Sockets FAQ/Tutorial (Chinese)

Post by 00061205 »

坛子里有好多人问关于wxSocket的问题因此Ryan Norton对这些问题做了集中的回答。
我把它翻译成中文贴在这里,原帖地址:Sockets FAQ/Tutorial http://forums.wxwidgets.org/viewtopic.php?t=2736
如果发现有翻译不当的地方请跟帖指正,我将及时更正。我恨把socket翻译成套接字的人,套接这个字儿曾迷惑了我好一阵子,所以socket我不做翻译,它就是socket。

1. 服务器Socket的创建
如果你要在Windows操作系统创建一个Socket你只需利用API函数Socket()创建一个就可以了。然而wxWidgets把它区分为客户端socket(用来接收服务端信息)和服务端socket(接收客户端socket的信息).下面是个例子

Code: Select all

// 建立一个服务端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方法会挂起主线程等待客户端直到接收到客户端。不过在控制台程序里还是很好用的:

Code: Select all

 //
      // 通常情况是利用一个循环来接收
      // 是一个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) 准备工作和关联事件

Code: Select all

//
            // 用事件句柄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)

Code: Select all

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)

Code: Select all

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中使用线程是很简单的。首先创建一个线程类,代码如下:

Code: Select all

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例子稍作改动来配合线程:

Code: Select all

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方法里完成:

Code: Select all

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/009 ... cket.h.htm
Official reference for the Unix socket API.

一休哥教导我们:就到这里了,休息,休息一下。
Utensil
Moderator
Moderator
Posts: 423
Joined: Sun Feb 03, 2008 11:38 am
Location: China

Post by Utensil »

=D>

高效率,高质量,赞一个~

我先知道socket后知道“套接字”的,糟透了的翻译。然而实在找不到一个合适的翻译。

-Utensil
In fascination of creating worlds by words, and in pursuit of words behind the world.

On Github: http://utensil.github.com
Technical Blog in Chinese: http://utensil.iteye.com/
kevin_fly
Experienced Solver
Experienced Solver
Posts: 50
Joined: Tue Apr 15, 2008 12:55 am

Post by kevin_fly »

劳苦功高
敬礼!!!
http://blog.163.com/kevin_fly/
-----我的博客,我的知识库------
Loaden
I live to help wx-kind
I live to help wx-kind
Posts: 177
Joined: Tue Feb 19, 2008 10:21 am
Location: China

Post by Loaden »

敬礼!!作者辛苦了!!!
Life is not fair, get used to it.
Alli
In need of some credit
In need of some credit
Posts: 2
Joined: Fri Aug 14, 2009 5:12 am

Re: [Translation]Sockets FAQ/Tutorial

Post by Alli »

00061205兄语言好啊:子曾经曰过:君子不可不学。
Hang on though - this is not a single-step process...
范家兵
In need of some credit
In need of some credit
Posts: 2
Joined: Sat Jul 21, 2012 7:29 am

Re: [已译完]Sockets FAQ/Tutorial

Post by 范家兵 »

各位兄弟们,能不能帮我看看这个问题。
最近用wxSocket和wxFile写了一个上传和下载文件的程序,可是上传完文件后再下载下来后,文件的大小是对的,而且TXT的文件是正常的。但是打开其他的文件时
就会报错说文件被破坏了。
服务器是64为的linux,客户机是32位的winXP

Code: Select all

wxFile * uf=new wxFile(path,wxFile::write);
cloud_oper->Read(buffer,rs);
rs = uf->Write(buffer,rs);
if(uf->Error())
{
   wxMessageBox(_("传输数据中止!"),_("Error"));
   uf->Close();
   cloud_oper->Close();
   delete uf;
   delete cloud_oper;
   return false;
}
kipade
Earned some good credits
Earned some good credits
Posts: 126
Joined: Fri Nov 11, 2011 2:45 am
Location: China

Re: [已译完]Sockets FAQ/Tutorial

Post by kipade »

因为文本文件中没有0(遇到0刚好到达文件结尾)
但是,其它文件,你基本上是无法保证这一点哦
Slackware GNU/Linux x86_64
wxWidgets-3.3.0
infoworld
In need of some credit
In need of some credit
Posts: 5
Joined: Fri May 09, 2014 2:47 am

Re: [已译完]Sockets FAQ/Tutorial

Post by infoworld »

范家兵 wrote:各位兄弟们,能不能帮我看看这个问题。
最近用wxSocket和wxFile写了一个上传和下载文件的程序,可是上传完文件后再下载下来后,文件的大小是对的,而且TXT的文件是正常的。但是打开其他的文件时
就会报错说文件被破坏了。
服务器是64为的linux,客户机是32位的winXP

Code: Select all

wxFile * uf=new wxFile(path,wxFile::write);
cloud_oper->Read(buffer,rs);
rs = uf->Write(buffer,rs);
if(uf->Error())
{
   wxMessageBox(_("传输数据中止!"),_("Error"));
   uf->Close();
   cloud_oper->Close();
   delete uf;
   delete cloud_oper;
   return false;
}
用wxFFile试试,打开模式用"wb".
lfjking
Earned some good credits
Earned some good credits
Posts: 102
Joined: Mon Nov 14, 2016 1:35 pm

Re: [已译完]Sockets FAQ/Tutorial

Post by lfjking »

辛苦 大神 的翻译 高质量 给力 =D>
kyozy
In need of some credit
In need of some credit
Posts: 2
Joined: Mon Jul 02, 2018 8:11 am

Re: [已译完]Sockets FAQ/Tutorial

Post by kyozy »

翻译真是个体力活,辛苦了
Post Reply