现在的位置: 首页 > 综合 > 正文

PowerSocket程序设计初学者指南

2013年08月02日 ⁄ 综合 ⁄ 共 11485字 ⁄ 字号 评论关闭

PowerSocket程序设计初学者指南

注意:本文讲述了winSock的通信原理,但如果需要完成本文的功能,建议你可以下载本站内一个已有pb例程(是用于实现 聊天的程序,由sybase网站下载,非常好用),它是采用pb 6/7/8已内置的对象实现winsock的功能,更加方便

网友:王华 Email: wh1978@netease.com

下载:www.pdriver.com/pb1121/SocketTest.zip (size: 413kb) (包括一个服务端和客户端的例子)

PowerSocket程序设计  
  初学者指南  
http://topic.csdn.net/t/20010607/09/148961.html
   
  PowerBuilder是一个最重要的客户-服务器应用开发工具。当Internet已经成为共同开发的重要部分,PowerBuilder编写   TCP/IP应用的能力也有所增强。在PC平台上,有个叫WinSock的DLL提供TCP/IP功能。PowerBuilder有能力访问DLL中的函数。因而,调用WinSock.DLL应当不成问题。但这只是片面的。WinSock.DLL中大多数函数可以通过声明局部外部函数在PowerBuilder应用中调用。但对这个方法有重要限制。在WinSock.DLL中的某些函数与PowerBuilder中的保留字相冲突。这可以通过在PowerBuilder中声明时给你的函数命别名的方法解决。另外还有一个问题。WinSock.DLL中某些函数引用了指针的指针,PowerBuilder不能处置这类型参数传递。因而,创建了一个叫PowerSocket的DLL,以处理名字/保留字冲突,及处理指针的指针问题。SCIENCE.ORG的科学家们发现PowerSocket库很有用,并创建了PowerBuilder用户对象使在应用中集成PowerSocket更加简单了。这些用户对象使得TCP/IP程序设计更加简单。不过仍然需要了解一些基本知识,以知道如何构造TCP/IP网络应用。我们希望本初学指南能在你通向用PowerBuilder与PowerSocket成功构造Internet/Intranet应用的成功之路上给你帮助。  
   
  Sockets  
   
  TCP/IP程序设计也被称为socket程序设计。编写TCP/IP应用的整体思想是计算机能在网络上相互通信。这个通信策略使用Soket(插座)的思想。当你要将设备连接到电源时,你把设备插入socket(插座)。当两个计算机程序想要通讯时,它们也创建socket插座与另一个连接。通过socket建立连接不是同时的。首先,一台计算机建立监听socket。已经建立监听socket的程序被称为服务器。连接到监听socket的程序被称为客户。一旦一个程序连接到另一个程序,服务器与客户之间就没有太大差别。信息通过socket功能传递。这些通信会话工作方式已经建立了许多成熟协议。几乎所有socket程序的基本结构是相同的,它们的不同是通信会话如何进行(它们遵守什么协议)。如同你在自己和另一个国家中某人之间建立电话连接那样。用于建立电话连接的手段可能是同样的,但是,使用的语言,讨论的方式可能是不同的。Socket编程已经使用了多年。它是UNIX操作系统的一个嵌入部分。UNIX已经成为Internet与许多自用Intranet事实上的的标准操作系统。若干年前,由马丁霍尔领导的小组,现在的星团技术(Stardust   Technologies),设计与创建了关于PC提供socket的能力。(相似技术已经被设计于Macintosh,叫Mac   TCP)。这个小组设计了叫WinSock的DLL,它已经成为PC平台网络计算的标准。  
   
  PowerSocket  
   
  PowerSocket是一个中间DLL,允许PowerBuilder访问WinSockDLL中的所有函数。目前,只支持PowerSocket版本2.0。当前有PowerSocket的   16与32位版本。关于PowerSocket的16与32位版本有些重要的事:PowerBuilder   4.0   for   Windows   3.1   and   Windows   95是16位应用。因而,当编写用于Windows   95的程序,即使它是32位操作系统,你仍然必须使用PowerSocket的16位版本。在Windows   95上运行的PowerBuilder5.0是32位应用。32位PowerSocket库目前在PowerBuilder   5.0不可用。PowerSocket将在PowerBuilder   5.0的最后版本发行是被更新。PowerSocket的   32位版本在Windows   NT3.5与3.51上运行良好。它没有在NT的其他版本上被测试过。16与32位DLL都被命名为PSLIB2.DLL。在你的路径中,只能有其中一个。这种命名方案将在将来被校正。PowerSocket具有完全功能参考(PSLIB2.HTM)。你可以在你的Web浏览器中阅读该文件。它也包括PSLIB2.DLL,该文件必须在你的路径中。它还包含WINSOCK.PBD或WINSOCK.PBL中的一个。该文件必须包含在你的PowerBuilder应用库路径列表中。在你的库路径列表里包含PBD文件是个技巧。强制包含它!  
   
  创建基本PowerSocket应用  
   
  在我们开始学习服务器与客户应用之间不同部分之前,所有PowerSocket应用有共同的部分。跟随这些基本步骤,你将通向完成第一个soket应用之路。由创建新应用开始。你的应用是MDI或SDI应用无足轻重。创建你的应用所需的用户界面。如果你已经有了一个应用,你将要对现存应用进行下列修改。声明全局变量:winsock   ws。最好使用ws作为你的WinSock对象的名字。本文档将直接适用。而且,在某些PowerSocket的早期版本要求你使用该名字。在你的应用的open事件中,创建你的WinSock对象实例:ws=create   winsock。在你的PowerSocket代码中不要使用messageboxe!Messagebox()函数会在你的应用的通讯部分产生临界定时错误(critical   timing   errors)。在不涉及socket通信的部分使用messagebox()是允许的。如果你需要从你的socket代码返回值,推荐你使用静态文本或多行编缉控件,填在它们的text值中。  
   
  创建PowerSocket服务器应用  
   
  在完成创建基本PowerSocket应用的步骤后,你将准备好开始创建服务器应用。服务器与客户应用之间的主要差别是服务器监听传入的连接。服务器应用在端口上监听传入的连接。此处不是硬件端口,它是软件端口,更简单地或,一个数字。软件端口与物理连接无关。它是帮助socket程序决定当连接时使用什么的协议的设备。socket在不同口上使用不同协议建立连接时。每台计算机拥有一个物理地址,即IP地址。你可以把端口认为是端口的扩展。例如,我的服务器IP数是204.94.74.209。如果我想要连接该及其的HTTP服务器,我包括一个端口号对应HTTP服务器,它是80。看起来象这样:204.94.74.209:80。  
   
  下列是常用服务与协议及它们相关的端口号。  
   
  服务/协议 端口号  
  echo                         7  
  daytime                   13  
  ftp-data                 20  
  ftp                           21  
  telnet                     23  
  smtp                         25  
  time                         37  
  name                         42  
  whois                       43  
  gopher                     70  
  http                         80  
  finger                     79  
  x400                         103  
  x400-snd                 104  
  pop-2                       109  
  pop-3                       110  
  uucp-path               117  
  nntp                         119  
  NeWS                         144  
   
  一个服务器应用必须完成五件事以成为一个服务器。  
  创建一个窗口  
  创建一个socket  
  在特定端口号绑定IP地址  
  监听  
  创建消息映射  
  除了这4步,其他事与协议有关。  
   
  创建socket  
   
  有两种类型的socket,Datagram和Stream。Datagram   socket用来创建无连接应用,有时称为UDP   socket。我们将在这指南中谈论Stream   socket,用于应用建立连接保证信息传送。  
  Stream   socket有时称为TCP   socket。  
   
  如果你熟悉在C中socke编程,你将发现使用PowerSocket库已经使建立socket更简单。PowerSocket库包含特殊用户对象叫socketstream。  
  你可以象这样创建stream:  
  socketstream   sSock   //declare   a   new   socketstream   object  
   
  sSock   =   create   socketstream   //create   a   new   socketstream   object  
  sSock只是对象名。  
  你可以将你的socketstream对象命名为任何你喜欢的名字。  
   
  绑定IP地址到特定端口号  
   
  捆绑是简单的,但是要求几个步骤。绑定socket就是'关联'主机IP地址与特定端口号。  
  它此刻还未监听,只是将IP与端口联合起来。  
  bind()函数是socket对象的一个方法,要求两个参数,uladdr(unsignedlong表示要绑定的主机地址)和iPort(integer表示端口编号)。  
  下一步需要建立uladdr参数。创建两个变量。一个变量是无符号长整型,将保存uladdr参数。第二个是串参数,我们已经任意命名为sHostName。  
  然后保存64个空格到sHostName   。  
  ulong   ulAddr   //create   a   new   variable   of   type   unsigned   long  
   
  string   sHostName   //create   a   string   variable   to   hold   the   host   name  
   
  sHostName   =   space(64)  
  下一个步对PowerBuilder程序员陌生但对C程序员很普通。我们将以“引用”方式传递sHostName参数。这意味着将该变量初始化为无意义值,仅是存储值的地方。这就是为何我们在sHostName中储存64个空格。  
  该函数将填一个值到变量中,代替返回一个值。winsock对象有个叫gethostname()的方法。该函数填写引用传递的sHostName变量为你的机器的主机名。传入的第二个参数是我们创建的缓冲区的长度,我们储存了64个空格到sHostName中。你将发现缓冲在WinSock程序设计中是很常见的。  
  你将经常需要知道创建的缓冲区的长度。  
  ws.gethostname(sHostName,len(sHostName))  
   
  现在,你有你的机器或服务器程序运行的机器的主机名,保存在变量sHostName中,我们将使用该信息填写非常重要的结构,它是WinSock对象的一部分。pbhostent结构包含关于网络主机的重要值。该结构通过调用WinSock对象的若干方法一些填充。我们将使用的方法叫gethostbyname()。首先,创建pbhostent的新实例。在下面的例子中我们已经任意命名新结构为heTmp(hostent   temporary的缩写)。而后通过调用gethostbyname()函数填充它。  
  传入先前创建的变量sHostName。  
  pbhostent   heTmp   //create   a   new   instance   of   the   structure   pbhostent  
   
  heTmp=ws.gethostbyname(sHostName)  
   
  我们现在已经做完所有“家务”。我们有了绑定服务器并让它监听需要的所有信息!下一块代码检查heTmp结构是否被gethostbyname()适当地填充。如果它有值,ulAddr最后将从这结构中取得。pbhostent结构(目前的heTmp)有个数组类型的成员h_addr_list。在大多数情况下,该数组将只有一个元素,但是我们仍然必须用下标引用该成员,象这样,h_add_list[1]。  
   
  现在,开始令人兴奋的部分。有了手头的ulAddr变量,你现在可以用socket的方法bind()绑定socket了。如我们早先提及,bind()接受两个参数,ulAddr和端口号。在本例子中我们将绑定HTTP端口(80)。HTTP是WWW协议。  
  最后,使用socket方法listen()告诉绑定了的socket在端口80监听。  
   
   
  if   isnull(heTmp)   =   FALSE   then  
   
  ulAddr   =   heTmp.h_addr_list[1]  
   
  sSock.bind(ulAddr,80)  
   
  sSock.listen(5)  
   
  else  
   
  messagebox("Error","Could   not   determine   IP   address   of   local   machine")  
   
  end   if  
   
  你现在有了一个socket在端口80上监听。要让你的socket在不同端口上监听,只需简单地改变bind()函数的第二个参数。你就快完成一个通用服务器应用了。  
  下一步,创建一个消息映射,并学习有了它以后怎么做。  
   
  创建消息映射作为PowerBuilder程序员,你熟悉当Windows发生某事时触发事件的思想,每个对象都有一系列事件处理程序,当事件触发时被执行。PowerBuilder为你准备好了一切。你被惯坏了,承认吧?现在,你将不得不做点儿工作。你必须写几行代码来建立特定的消息映射来触发你的PowerBuilder应用中的事件。这并不难,有专门的WinSock函数叫WSAAsyncSelect()来创建消息映射。WSAASyncSelect()函数接受三个参数,第一个是处理事件的对象的窗口句柄(handle)。为什么我们跳过了上面列的步骤中的“创建一个窗口”?我们假设你知道在PowerBuilder中如何创建窗口。现在你知道为什么要创建一个窗口:为了创建消息映射,你需要一个窗口来处理。在下面的例子中我们使用PowerBuilder   handle()函数以返回当前窗口句柄。第二个参数需要点儿说明。每个Windows事件对应一个数。窗口open事件,按钮的clicked事件均如此。在我们的程序中,我们使用一个PowerBuilder用户定义事件。PowerBuilder用户定义事件Custom_01对应Windows事件1024。Custom_02对应1025等等。WSAASyncSelect()的第三个参数决定Custom_01对应什么事件。WinSock用户对象将所有WinSock事件定义成枚举数据类型。这是在它们的一览表:  
   
  ws.FD_ACCEPT   –   收到连接请求  
   
  ws.FD_READ   –   读就绪  
   
  ws.FD_WRITE   –   写就绪  
   
  ws.FD_CLOSE   –   将要关闭  
   
  ws.FD_CONNECT   –   连接完成  
   
  ws.OOB   –   数据越界  
   
  经常会有这中情况,你希望多个事件发生时触发一个事件。你可以象这样用加把它们'或'起来:  
   
  aEvents   =   ws.FD_READ   +   ws.FD_WRITE   +   ws.FD_CLOSE  
   
  在设置包含监听socket的窗口的消息映射时,你将触发Custom_01l来接收socket连接。那么,代码如下:  
   
  aEvents   =   ws.FD_ACCEPT  
  sockSocket.WSAAsyncSelect(handle(this),1024,aEvents)  
   
  太棒了!你现在有了监听socket,及一个消息映射,当收到socket连接时触发Custom_01事件。现在只剩下让你的服务器程序在收到连接请求时做点什么。  
   
  接受连接  
  在前一节中你已经设置了消息映射,当你的服务器接收socket连接请求时,触发PowerBuilder的Custom_01事件。你的下一个步是给你的窗口创建Custom_01事件。  
  这在PowerBuilder中通过选择Declare|User   Events...菜单完成。接受连接不费吹灰之力。我们在现在向你展示让你的服务器接受多连接的方法。该方法使你的服务器可以在同一时间处理若干socket通信会话。这称为异步socket通信。  
  这正是你下面要做的,及其背后的原因。停止编辑你的主窗口几分钟,创建一个Powerbuilder可视用户对象(visual   user   object)。你创建可视用户对象而不是非可视用户对象的原因是因为非可视用户对象没有你需要的窗口句柄。我们将需要该句柄来创建另一个消息映射。命名你的新可视用户对象为connected_socket。  
  这是任意的,但是你应遵守本指南以便继续。  
  Creating   the   connected_socket   object  
  connected_socket   对象接手监听socket的工作。connected_socket   对象将继续socket通信会话,将监听socket解放出来以便接受另一个连接。  
  每次监听socket接受另一个连接,它将创建新的connected_socket对象。  
   
  connected_socket对象是编制管理你的“协议相关”通信会话的代码的地方。  
  现在,我们将从该对象要求两个部件开始。  
  在connected_socket对象中,声明一个socket类型的实例变量。  
   
  socket   sSocket  
   
  而后,在connected_socket对象的constructor中创建一个新的socket对象。  
  你应该将visible属性设为FALSE,除非你希望可视用户对象出现在屏幕上。  
  sSocket   =   create   socket  
   
  visible   =   FALSE  
  保存这段脚本,然后创建叫initsocket()的用户对象函数。同样,该函数的名字是任意。  
  在该函数中你将初始化socket并创建消息映射。  
  该用户对象将拥有自己的消息映射,并将响应FD_READ,   FD_WRITE和FD_CLOSE事件。  
   
  integer   aEvent  
   
  aEvent   =   ws.FD_READ   +   ws.FD_WRITE   +   ws.FD_CLOSE  
   
  sSocket.initsocket(uiTmp)  
   
  sSocket.WSAAsyncSelect(handle(this),1024,aEvent)  
   
  为用户对象创建Custom_01事件。  
  这是你处理连接的地方。再回到编辑主窗口Custom_01事件的地方。  
  在该事件中,你将:  
  创建你的可视用户对象的新实例。用socket对象的accept()方法接收socket连接。accept()函数返回socket标识符,即socket编程中众所周知的socket号。socket号传递到新的connected_socket对象。用OpenUserObject()函数在窗口上打开用户对象。  
   
  ulong   ulAddr  
   
  int   iPort  
   
  uint   uiTmp  
   
  connected_socket   csTmp  
   
  uiTmp   =   sockSocket.accept(ulAddr,iPort)  
   
  if   uiTmp   >   0   then  
   
  OpenUserObject(csTmp,0,0)  
   
  csTmp.initsocket(uiTmp)  
   
  end   if  
   
  在socket连接上通讯  
   
  如果你按本指南中描述的创建一个PowerSocket服务器程序,socket通信会话将发生在connected_socket用户对象的custom_01事件中。  
  事件  
   
  关于插座通信会话要知道的最重要的事是区分不同事件。你可以用WinSock函数WSAGetSelectEvent()决定哪个事件发生了。PowerBuilder   消息目标(message)包含该函数需要的参数。  
   
  iEvent   =   ws.WSAGetSelectEvent(message.longparm)  
   
  在新消息映射在connected_socket对象的initsocket()函数中被创建是,FD_WRITE事件将发生。它表示socket连接好了并“写就绪”。在你的程序中只有两中情况FD_WRITE会发生。一种是当socket被首先连接。另一种是你在试图写时遇到块条件(blocking   condition)。你将暂停脚本直到下一个FD_WRITE。FD_READ   不同。在仍有数据要读时,你将持续收到FD_READ事件。你将不得不准备的一个异常条件,就是在缓冲里还有数据要读时收到FD_CLOSE事件。我们设置了一个标志,让我们知道收到这一事件。我们检验IoctlSocket()函数来看看在缓冲里是否还有数据要读。  
   
  发送与接收数据  
   
  在PowerSocket应用中所有数据作为blob在socket上发出。这意味着你必须使用PowerBuilder   blob处理函数管理你的数据。你将在下面的例子看到数据在缓冲(bolbBuf)中被派送。  
  下列例子模拟HTTP服务器方式处理通信会话。  
   
  blob   blobBuf,   blobItem   //create   blob   variables  
   
  int   iLen,   iPos,   iEvent  
   
  string   sTmp  
   
  window   parentwindow  
   
  iEvent   =   ws.WSAGetSelectEvent(message.longparm)   //Determine   what   event   occurred  
   
  if   iEvent   =   ws.FD_READ   then   //If   the   FD_READ   event   occurred  
   
  blobBuf   =   blob(space(1024))  
   
  iLen   =   sSocket.recv(blobBuf,len(blobBuf),0)  
   
  sTmp   =   string(blobmid(blobBuf,1,iLen))  
   
  iPos   =   pos(sTmp,"/")  
   
  if   iPos   >   0   then  
   
  sTmp   =   mid(sTmp,iPos   +   1)  
   
  end   if  
   
  iPos   =   pos(sTmp,"   ")  
   
  if   iPos   >   0   then  
   
  sTmp   =   left(sTmp,iPos   -   1)  
   
  end   if  
   
  if   sTmp   =   ""   then  
   
  sTmp   =   "index.html"  
   
  end   if  
   
  iPos   =   pos(sTmp,"~r~n")  
   
  if   iPos   >   0   then  
   
  sTmp   =   left(sTmp,iPos   -   1)  
   
  end   if  
   
  select   html   into   :html   from   documents   where   name   =   :sTmp;  
   
  if   sqlca.sqlcode   <>   0   then  
   
  html   =   "<html><head><title>Error:   Document   not   found</title>"  
   
  html   =   html   +   "</head><body><h1>Error:   Document   not   found</h1>"  
   
  html   =   html   +   "Please   try   again.</body></html>"  
   
  end   if  
   
  iEvent   =   ws.FD_WRITE  
   
  bWrite   =   TRUE   //   FD_WRITE   event   occurs   upon   socket   connect.   Only  
   
  //   interested   in   FD_WRITE   after   send()'ing   starts  
   
  end   if  
   
  if   iEvent   =   ws.FD_WRITE   and   bWrite   =   TRUE   then  
   
  blobBuf   =   blob(left(html,512))  
   
  do   while   sSocket.send(blobBuf,len(blobBuf),0)   <>   ws.SOCKET_ERROR  
   
  html   =   mid(html,513)  
   
  if   len(html)   =   0   then  
   
  parentwindow   =   parent  
   
  parentwindow.closeuserobject(this)  
   
  exit  
   
  else  
   
  blobBuf   =   blob(left(html,512))  
   
  end   if  
   
  loop  
   
  end   if  
   
  if   iEvent   =   ws.FD_CLOSE   then  
   
  parentwindow   =   parent  
   
  parentwindow.closeuserobject(this)  
   
  end   if  
 

抱歉!评论已关闭.