本文转自网络,如侵犯蒋老师的版权,请留言删除。
PowerBuilder Sockets程序设计
蒋东兴
http://www.cic.tsinghua.edu.cn/jdx/book2/ws2chpc12z.htm
12.1 PowerBuilder网络程序概述
在PowerBuilder 5.0发布之前,PowerBuilder应用程序要实现网络通信是很困难的。为了解决此问题,PowerBuilder 5.0提供了网络程序接口,它是以应用程序示例的形式给出的。要安装PowerBuilder网络程序接口,在安装PowerBuilder 5.0 Enterprise for Intel 32产品时,需选中Application Gallery部件,并选中该部件中的FTP/Winsock *部件,这样,PowerBuilder 5.0安装后在目录Powersoft/PowerBuilder 5.0/Application Gallery/Ftp中将会出现下述文件:
FTPTEST.PBL(FTP客户程序PowerBuilder源文件)
PBFTP.DOC(winword格式的使用说明)
PBFTP.RTF(Rich Text Format格式的使用说明)
PBWS.C(PBWS.DLL的C语言源文件)
PBWS.DLL(提供16位版本的getservbyname()和gethostbyname()函数的PB版本)
PBWS32.C(PBWS32.DLL的C语言源文件)
PBWS32.DLL(提供32位版本的getservbyname()和gethostbyname()函数的PB版本)
RFC640.TXT(FTP协议的RFC文档)
RFC959.TXT(FTP协议的RFC文档)
SOCKETS.PBL(Winsock用户对象PowerBuilder源文件,它提供网络接口)
WHELLO.ICO(图标)
WHELLO.BMP(图象)
在这些文件中,两个动态连接库(PBWS.DLL、PBWS32.DLL)是运行时需要的,它将函数getservbyname()和gethostbyname()封装为PowerBuilder容易调用的形式,具体封装可以阅读其对应源文件。文件SOCKETS.PBL是网络程序接口的核心,它定义了PowerBuilder的网络部件,包括四个用户对象和三个结构。四个用户对象分别为:
· u_winsock:Winsock函数基本类;
· u_winsock_16:16位Winsock函数类;
· u_winsock_32:32位Winsock函数类;
· u_socket:Winsock对象。
三个结构为:
s_linger /* Winsock linger structure for setsockopt */
integer l_onoff;
integer l_linger;
s_sockaddr /* Winsock sockaddr structure for connect and bind */
integer sin_family;
unsignedinteger sin_port;
character sin_addr[4];
character sin_zero[8];
s_wsadata /* Winsock WSAData for WSAStartup */
unsignedinteger version;
unsignedinteger highversion;
character description[257];
character systemstatus[129];
unsignedinteger maxsockets;
unsignedinteger maxupddg;
string vendorinfo;
PowerBuilder应用程序可以通过使用这些用户对象与结构来实现与其它socket应用程序之间的相互网络通信。
12.2 使用Winsock函数
为了使用Winsock的函数,PowerBuilder定义了三个对象:Winsock函数基本类u_winsock、16位Winsock函数对象u_winsock_16和32位Winsock函数对象u_winsock_32。
用户对象u_Winsock是Winsock的基本类,定义它主要是为了声明的局部外部函数,这些函数包括Winsock DLL提供的函数、Windows函数、以及一些为PowerBuilder编写的Winsock函数(如GetHost)。下面是这些函数的声明。
FUNCTION int WSAStartup( uint wVersionRequested, ref s_wsadata lpWSAData ) LIBRARY "winsock.dll"
FUNCTION int WSACleanup() LIBRARY "winsock.dll"
FUNCTION int ntohs(int netshort) LIBRARY "winsock.dll"
FUNCTION int htons(int hostshort) LIBRARY "winsock.dll"
FUNCTION int WSAGetLastError ( ) LIBRARY "winsock.dll"
FUNCTION int getsockname(uint s,ref s_sockaddr name, ref int namelen) LIBRARY "winsock.dll"
FUNCTION int setsockopt (uint socket, int level, int optname,s_linger optval, int optlen ) LIBRARY "winsock.dll"
FUNCTION uint socket(int af, int ttype, int protocol) LIBRARY "winsock.dll"
FUNCTION int WSAAsyncSelect (uint socket, uint Wnd, uint wMsg, long lEvent ) LIBRARY "winsock.dll"
FUNCTION int bind (uint s, s_sockaddr name, int namelen ) LIBRARY "winsock.dll"
FUNCTION int listen(uint s, int backlog) LIBRARY "winsock.dll"
FUNCTION uint accept (uint s,s_sockaddr addr, ref int addrlen ) LIBRARY "winsock.dll"
FUNCTION int closesocket ( uint socket ) LIBRARY "winsock.dll"
FUNCTION int wsconnect (uint socket, s_sockaddr name, int namelen ) LIBRARY "winsock.dll" ALIAS for "connect"
FUNCTION int WSACancelBlockingCall ( ) LIBRARY "winsock.dll"
FUNCTION int recv ( int socket, ref blob buf, int len, int flags ) LIBRARY "winsock.dll"
FUNCTION int send ( int socket, ref blob buf, int len, int flags ) LIBRARY "winsock.dll"
FUNCTION string GetHost(string lpszhost, ref string lpszaddress ) library "pbws.dll"
FUNCTION int GetService(string lpszSevicet,string lpszProto ) library "pbws.dll"
FUNCTION boolean KillTimer(uint Wnd, int IDEvent) LIBRARY "user.exe"
FUNCTION uint SetTimer(uint Wnd, int IDEvent, uint Elapse, ulong Dummy) LIBRARY "user.exe"
对用户而言,如果需要用到其它的Winsock函数,他还可以在这里添加需要声明的其它函数。如:
FUNCTION long ntohl(long netlong) LIBRARY "winsock.dll"
FUNCTION long htonl(long hostlong) LIBRARY "winsock.dll"
另外,如果用户需要引用其它DLL中的函数,也可以使用同样的方法实现。如增加一对ntohl()和htonl()函数:
FUNCTION long ntohl(long netlong) LIBRARY "winsock.dll"
FUNCTION long htonl(long hostlong) LIBRARY "winsock.dll"
由于Windows 95系统中使用的Winsock.dll中的函数是16位版本,因此用户对象u_winsock_16引用的局部外部函数和u_winsock是一样的,这里就不再列举。
为了使32位的应用程序能够更好地使用32位版本的Winsock函数,用户对象u_winsock_32引用的局部外部函数都是32位版本的,这些函数的声明如下:
FUNCTION int WSAStartup( uint wVersionRequested, ref s_wsadata lpWSAData ) LIBRARY "wsock32.dll"
FUNCTION int WSACleanup() LIBRARY "wsock32.dll"
FUNCTION int ntohs(int netshort) LIBRARY "wsock32.dll"
FUNCTION int htons(int hostshort) LIBRARY "wsock32.dll"
FUNCTION int WSAGetLastError ( ) LIBRARY "wsock32.dll"
FUNCTION int getsockname(uint s,ref s_sockaddr name, ref int namelen) LIBRARY "wsock32.dll"
FUNCTION int setsockopt (uint socket, int level, int optname,s_linger optval, int optlen ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION uint socket(int af, int ttype, int protocol) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int WSAAsyncSelect (uint socket, uint Wnd, uint wMsg, long lEvent ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int bind (uint s, s_sockaddr name, int namelen ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int listen(uint s, int backlog) LIBRARY "wsock32.dll"
PRIVATE FUNCTION uint accept (uint s,s_sockaddr addr, ref int addrlen ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int closesocket ( uint socket ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int wsconnect (uint socket, s_sockaddr name, int namelen ) LIBRARY "wsock32.dll"ALIAS for "connect"
PRIVATE FUNCTION int WSACancelBlockingCall ( ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int recv ( int socket, ref blob buf, int len, int flags ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION int send ( int socket, ref blob buf, int len, int flags ) LIBRARY "wsock32.dll"
PRIVATE FUNCTION string GetHost(string lpszhost, ref string lpszaddress ) library "pbws32.dll"
PRIVATE FUNCTION int GetService(string lpszSevicet,string lpszProto ) library "pbws32.dll"
PRIVATE FUNCTION boolean KillTimer(uint Wnd, int IDEvent) LIBRARY "user32.dll"
PRIVATE FUNCTION uint SetTimer(uint Wnd, int IDEvent, uint Elapse, ulong Dummy) LIBRARY "user32.dll"
12.3 Winsock对象u_socket
用户对象u_socket是PowerBuilder 5.0实现Sockets网络通信的关键,它通过定义一系列实例变量(Instance Variables)和用户对象函数(User Object Function)将Winsock函数封装起来,使得PowerBuilder程序员能够方便地使用Winsock函数。
12.3.1 u_socket定义的实例变量
用户对象u_socket定义了一系列实例变量,这些变量包括Winsock函数类、Winsock函数要使用的数据结构、套接字状态和一些宏定义等。下面我们在给出这些实例变量列表的同时,给出每一个变量的简要说明。
PUBLIC u_winsock ws // Winsock函数类的实例变量,u_socket通过使用用户对象u_winsock(u_winsock_16或u_winsock_32)的一个实例来实现对Winsock函数的调用。
PRIVATE uint iui_handle // 用户对象u_socket的实例句柄。
PRIVATE uint iui_messagenum = 1024 // 常数,其值等于pbm_custom01,定义套接字处理的消息号,即WSAAsyncSelect()函数的wMsg参数值。
PRIVATE boolean ib_TimesUp // 用于函数wait(),为定时到期标志
PUBLIC PRIVATEWRITE s_wsadata ius_wsadata // 函数WSAStartup()使用的结构变量
PUBLIC PRIVATEWRITE us_socket ius_socket // 描述对象内部状态的结构变量
地址信息变量:
PUBLIC string is_hostname // 主机名
PUBLIC string is_hostaddress // 主机IP地址,按网络字节顺序存放
PUBLIC int ii_portno // 端口号
套接字状态:
PUBLIC PRIVATEWRITE int status_OK=0 // 正常状态
PUBLIC PRIVATEWRITE int status_Failure = -1 // 失败状态
PUBLIC PRIVATEWRITE int status_timeout = 1 // 超时状态
超时值(单位:毫秒):
PUBLIC uint iui_ConnectTimeout = 30000 // 连接超时值
PUBLIC uint iui_SendTimeout = 30000 // 发送超时值
PUBLIC uint iui_ReceiveTimeout = 30000 // 接收超时值
套接字工作状态:
PUBLIC PRIVATEWRITE int state_Idle = 0 // 空闲:套接字建立后初始状态
PUBLIC PRIVATEWRITE int state_Open = 1 // 打开:未用
PUBLIC PRIVATEWRITE int state_Closed = 2 // 关闭:套接字使用closesocket()函数关闭后或recv()函数接收到0字节数据
PUBLIC PRIVATEWRITE int state_Connected = 3 // 已连接:套接字使用accept()函数接收连接成功或使用函数connect()连接远程主机成功
PUBLIC PRIVATEWRITE int state_Connecting = 4 // 正连接:套接字使用connect()函数试图和远程主机建立连接时
PUBLIC PRIVATEWRITE int state_Listening = 5 // 监听:被动套接字使用listen()函数成功返回时状态,使用accept()时状态改变,在accept()函数成功返回后被动套接字恢复为此状态
PUBLIC PRIVATEWRITE int state_Accepting = 6 // 正接收:套接字使用accept()函数试图接收远程主机连接时
PUBLIC PRIVATEWRITE int state_Accepted = 7 // 已接收:套接字使用accept()函数成功返回
PUBLIC PRIVATEWRITE int state_Error = 8 // 错误:任何出错状态
结构长度:
PUBLIC PRIVATEWRITE int sizeof_IN_ADDR = 4 // 结构in_addr的长度
PUBLIC PRIVATEWRITE int sizeof_LINGER = 4 // 结构linger的长度
PUBLIC PRIVATEWRITE int sizeof_SOCKADDR = 16 // 结构sockaddr_in的长度
用到的套接字常数:
PRIVATE int PF_INET = 2 // Internet协议族
PRIVATE int SOCK_STREAM = 1 // 流套接字类型(TCP)
PRIVATE int IPPROTO_IP = 0 // 虚构的IP协议号,用作socket()函数的protocol参数
PRIVATE long FD_READ = 1 // 函数WSAAsyncSelect()使用的消息值:可读
PRIVATE long FD_WRITE = 2 // 函数WSAAsyncSelect()使用的消息值:可写
PRIVATE long FD_OOB = 4 // 函数WSAAsyncSelect()使用的消息值:带外数据
PRIVATE long FD_ACCEPT= 8 // 函数WSAAsyncSelect()使用的消息值:有外来连接
PRIVATE long FD_CONNECT = 16// 函数WSAAsyncSelect()使用的消息值:连接建立
PRIVATE long FD_CLOSE = 32 // 函数WSAAsyncSelect()使用的消息值:连接关闭
PUBLIC PRIVATEWRITE int SOL_SOCKET = -1 // 套接字层选项的层,用于函数getsockopt()和setsockopt()的level参数
PUBLIC PRIVATEWRITE int SO_LINGER = 128 // 套接字选项:套接字上有数据时关闭操作延迟
PRIVATE uint INVALID_SOCKET = 65535 // 非法套接字,多用于函数返回值
PRIVATE int SOCKET_ERROR = -1 // 套接字出错,多用于函数返回值
PRIVATE int WSAEWOULDBLOCK = 10035 // 错误码:指示非阻塞套接字不能立即成功返回,发布的操作将被阻塞
12.3.2 u_socket定义的结构
u_socket定义了两个用户对象结构:主机事件结构us_hostent和描述套接字内部状态的结构us_socket。其结构定义如下:
us_hostent // 主机事件结构hostent,
string h_name // 主机正式名
string h_aliases // 主机别名
integer h_addrtype // 主机地址类型
integer h_length // 地址长度
string h_addr_list // 地址列表
us_socket // 描述套接字内部状态的结构
integer state // 套接字工作状态(监听、连接、关闭,…等)
integer status // 套接字状态(正常、出错、超时等)
boolean datain // 套接字上有数据可读
boolean sendok // 套接字可以发送数据
integer errmsg // 套接字的错误码(WSAGetLastError()的返回值)
unsignedinteger socketnumber // 套接字描述符
12.3.3 u_socket的事件处理程序
u_socket使用了四个事件:constructor、destructor、ue_socket_notification和ue_timer,其中ue_socket_notification和ue_timer是用户自定义事件。
事件constructor在用户对象创建时发生,它主要用来启动Winsock并设置套接字的初始状态值。其代码如下:
int rc
environment e
this.visible = FALSE
iui_handle = Handle(this) // 用户对象u_socket的实例句柄
// 根据操作系统信息,实例化Winsock函数对象
GetEnvironment(e)
if e.Win16 then
ws = create u_winsock_16
else
ws = create u_winsock_32
end if
// 初始化socket内部状态
ius_socket.DataIn = FALSE
ius_socket.SendOK = FALSE
ius_socket.State = state_Idle
ius_socket.ErrMsg = 0
is_hostaddress = "~000~000~000~000"
ii_portno = 0
// 启动Winsock
rc = ws.WSAStartup(257,ius_wsadata) // 257=0x0101,指示Winsock版本1.1
if rc = 0 then
ius_socket.Status = status_OK
else
ius_socket.Status = status_Failure
ius_socket.ErrMsg = rc
end if
事件destructor在用户对象消亡时发生,它主要用来关闭Winsock并释放资源。其代码如下:
ws.WSACleanup()
destroy ws
事件ue_socket_notification处理用户自定义的Winsock事件pbm_custom01,该事件是用户调用u_socket的用户对象函数uf_open打开套接字时注册的。其代码如下:
long l
l = Message.LongParm
choose case WSAGetSelectEvent(l)
case FD_CONNECT // 连接建立完成
SocketConnected( l )
case FD_READ // 有数据可读
SocketRead( l )
case FD_WRITE // 套接字可写
SocketWrite( l )
case FD_CLOSE // 套接字上的连接关闭
SocketClose( l )
case FD_ACCEPT // 有外来连接到来
SocketAccept( l )
case else // 其它事件
SocketOther( l )
end choose
事件ue_timer处理用户自定义事件pbm_timer,它释放定时器,并通过设置变量ib_TimesUp为True来通知等待函数。此事件由wait函数设置的定时器产生,其代码如下:
ws.KillTimer(iui_handle,1)
ib_TimesUp = True
12.3.4 u_socket定义的用户对象函数
u_socket定义了21个用户对象函数,这些函数可以分为三类:Winsock事件处理函数、套接字操作函数和其它函数。这些函数的定义及其功能可以用表12.1简单描述:
表12.1 u_socket定义的用户对象函数表
类型
语 法
说 明
事
件
处
理
函
数
socketaccept(long pl_info)
FD_ACCEPT事件处理函数
socketclose(long pl_info)
FD_CLOSE事件处理函数
socketconnected(long pl_info)
FD_CONNECT事件处理函数
socketother(long pl_info)
其它事件处理函数
socketread(long pl_info)
FD_READ事件处理函数
socketwrite(long pl_info)
FD_WRITE事件处理函数
套
接
字
操
作
函
数
uf_acceptconnection(integer pi_accepttimeout, window pw_socketholder) return u_socket
接收连接
uf_close() return boolean
关闭连接
uf_connecttohost() return boolean
建立连接
uf_dobind() return boolean
捆绑套接字
uf_dolisten(integer pi_backlog) return boolean
让套接字监听连接(用于服务器)
uf_gethost(string ps_host) return boolean
获取主机名字与地址
uf_getservice(string ps_service, string ps_proto) return boolean
获取Internet服务的端口号
uf_inet_ntoa() return string
将网际地址转换为字符串形式
uf_open() return boolean
创建套接字
uf_receivedata(blob pb_buffer) return integer
从已连接的套接字上读数据
uf_senddata(blob pb_buffer, pi_buffersize) return integer
向已连接的套接字上写数据
其
它
wait(unsignedinteger pui_miliseconds)
等待函数(放弃CPU)
wsagetselecterror(long pl_error) return long
从事件参数中提取错误码
wsagetselectevent(long pl_event) return long
从事件参数中提取事件代码
debug(string ps_text)
调试函数
在上述函数中,事件处理函数是u_socket的私有用户对象函数,它们用来处理Winsock的相应网络事件;套接字操作函数是公用用户对象函数,它们用来实现对套接字的创建、连接、读/写、关闭等操作,应用程序通过调用u_socket的这些公用用户对象函数来实现Winsock的网络通信;其它函数是几个私有用户对象函数,其中的两个事件参数提取函数相当于Winsock定义的同名的宏。下面我们在每类函数中选取一个例子来看看它们所作的工作。
事件处理函数socketread(long pl_info)
此函数用来处理Winsock的数据可读事件FD_READ,它只是设置套接字的内部状态为可读(DataIn = True),而没有作实际的读数据操作。其代码如下:
ius_socket.DataIn = True
if WSAGetSelectError(pl_info) <> 0 then
ius_socket.State = state_error
ius_socket.Status = status_failure
ius_socket.ErrMsg = WSAGetSelectError(pl_info)
else
ius_socket.Status = status_ok
ius_socket.ErrMsg = 0
end if
套接字操作函数uf_receivedata(blob pb_buffer) return integer
此函数用来从已连接套接字上读取数据。它首先设置套接字内部状态为无数据可读(DataIn = False),然后从套接字上读取数据。如果recv操作成功返回,则说明读数据完成,设置套接字状态正常(status = status_OK)并返回读到的字节数。如果recv操作出错,则获取错误码,并判断是否因套接字上数据可读而操作可能阻塞产生的错误(WSAEWOULDBLOCK),如果是则等待,否则设置套接字状态为出错(status = status_failure)并设置错误码ErrMsg后返回。在等待时,如果套接字上有数据可读(由FD_READ事件的处理程序设置了套接字内部状态DataIn = True)或等待超时,则结束等待。如果是有数据可读结束的等待,则重新调用recv()函数读取数据;如果是超时结束的等待,则设置套接字状态为出错后返回。其代码如下:
int err, bytesread
uint tout
ius_socket.DataIn = False // 设置套接字内部状态为无数据可读
bytesread = ws.recv(ius_socket.SocketNumber, pb_buffer, Len(pb_buffer), 0) // 读数据
if bytesread = SOCKET_ERROR then // 如果出错返回
err = ws.WSAGetLastError() // 获取错误码
if err = WSAEWOULDBLOCK then // 错误原因为可能阻塞
for tout = 100 to iui_receivetimeout step 100 // 等待
wait(100)
if ius_socket.DataIn then exit // 等待中有数据可读了则退出等待状态
next
if ius_socket.DataIn and ius_socket.ErrMsg = 0 then // 有数据可读
ius_socket.DataIn = False // 置套接字内部状态为数据已读完
do // 读取数据,如果阻塞则循环读
bytesread=ws.recv(ius_socket.SocketNumber,pb_buffer,Len(pb_buffer),0)
if bytesread <> SOCKET_ERROR then exit
LOOP UNTIL ws.WSAGetLastError() <> WSAEWOULDBLOCK
if bytesread = SOCKET_ERROR then // 读数据出错,则设置状态为出错
ius_socket.ErrMsg = ws.WSAGetLastError()
ius_socket.Status = status_failure
else // 读数据成功,设置状态为正常
ius_socket.status = status_ok
end if
else // 超时退出循环,设置套接字状态为出错
ius_socket.Status = status_failure
if not ius_socket.DataIn then // should probably cancel blocking call
ius_socket.ErrMsg = -1
else
ius_socket.DataIn = False
end if
end if
else // 其它错误导致的recv操作出错,设置套接字状态为出错
ius_socket.ErrMsg = err
ius_socket.status = status_failure
end if
else // 读取数据成功,设置套接字状态为正常
ius_socket.status = status_OK
ius_socket.ErrMsg = 0
end if
// 如果读取的字节数为0,则说明套接字被对等方关闭
if bytesread = 0 then ius_socket.state = state_Closed
return bytesread // 返回字节数
其它函数wsagetselectevent(long pl_event) return long
此函数相当于Winsock定义的宏WSAGETSELECTEVENT,它从事件通知的参数中提取网络事件。其代码为:
return Mod(pl_event,65536) // loword
12.4 应用程序示例
我们用一个简单的点对点网络实时通信程序来示例PowerBuilder网络程序的设计。该实例分两部分:客户程序与服务器程序。其工作过程是:服务器首先启动,它创建套接字之后等待客户的连接;客户启动后,创建套接字,然后和服务器建立连接;连接建立后,客户接收键盘输入,每接收到一行则将数据发送到服务器,服务器收到数据后,计算字符长度并将字符串发送回来,客户将收到的数据在窗口中显示;如此循环,直到客户程序使用关闭命令时,它关闭连接和套接字后退出。服务器在用户关闭窗口时关闭套接字然后退出。
12.4.1 客户程序
创建一个新的应用程序pbechoc,将sockets.pbl中的用户对象u_socket,u_winsock,u_winsock16,u_winsock32和数据结构s_linger,s_sockaddr,s_wsadata拷贝到新的PowerBuilder程序库pbechoc.pbl中。建立客户程序主窗口w_main如图12.1所示,并将应用程序pbechoc的启动代码设置为打开此窗口。
图12.1 客户程序主窗口
按钮“Open Connection”的作用是打开和服务器的连接,它主要完成下述工作:创建用户对象u_socket的实例;根据用户输入的服务器主机名获取远程主机地址;根据用户输入的服务名获取服务的端口号和协议等信息;创建套接字,并和服务器建立连接。其Clicked事件的脚本(script)为:
string s
parent.OpenUserObject(iu_socket, "u_socket") /* 创建用户对象u_socket的实例 */
if iu_socket.ius_socket.Status = iu_socket.status_OK then
mle_1.text = iu_socket.ius_wsadata.description
else
mle_1.text = "Error"
end if
if not iu_socket.uf_GetHost(sle_1.text) then /* 获取远程主机地址 */
mle_1.text = mle_1.text + "~r~n" + "GetHostByName Failed"
else
s = String(Asc(Mid(iu_socket.is_hostaddress,1,1)))
s = s + "." + String(Asc(Mid(iu_socket.is_hostaddress,2,1)))
s = s + "." + String(Asc(Mid(iu_socket.is_hostaddress,3,1)))
s = s + "." + String(Asc(Mid(iu_socket.is_hostaddress,4,1)))
mle_1.text = mle_1.text + "~r~n" + iu_socket.is_hostname + "~r~n" + "Host: " + s
end if
if not iu_socket.uf_GetService(sle_2.text,'tcp') then /* 获取服务的端口号和协议等信息 */
mle_1.text = mle_1.text + "~r~n" + "GetServByName Failed"
else
mle_1.text = mle_1.text + "~r~nPort: " + String(iu_socket.ws.ntohs(iu_socket.ii_portno))
end if
if iu_socket.uf_open() then /* 创建套接字 */
mle_1.text = mle_1.text + "~r~n" + "Open succeeds"
if iu_socket.ius_socket.status = iu_socket.status_OK then
mle_1.text = mle_1.text + "-state OK status=" + string(iu_socket.ius_socket.status)
else
mle_1.text = mle_1.text + " - state failed" + string(iu_socket.ius_socket.status)
end if
else
mle_1.text = mle_1.text + "~r~n" + "Open failed" + string(iu_socket.ius_socket.status)
end if
if iu_socket.uf_connecttohost() then /* 和服务器建立连接 */
mle_1.text = mle_1.text + "~r~n" + "Connected."
else
mle_1.text = mle_1.text + "~r~n" + "Not Connected – ErrMsg = " + &
String(iu_socket.ius_socket.ErrMsg)
end if
按钮“Close Connection”的作用是关闭和服务器的连接。为了通知服务器作相应的关闭连接处理,客户程序先给服务器程序发送一个协商好的字符串“Quit”,然后在关闭套接字及用户对象u_socket的实例。其Clicked事件的脚本为:
blob{4096} buffer
int bytesread
/* 给服务器发送“Quit”消息 */
buffer = Blob("Quit")
bytesread = iu_socket.uf_SendData(buffer, Len(String(buffer)))
if iu_socket.ius_socket.Status <> iu_socket.status_ok then
mle_1.text = mle_1.text + "~r~nError: State = " + string(iu_socket.ius_socket.state) + &
" ErrMsg = " + string(iu_socket.ius_socket.ErrMsg)
end if
if iu_socket.uf_close() then /* 关闭套接字 */
mle_1.text = mle_1.text + "~r~n" + "close succeeds"
if iu_socket.ius_socket.status = iu_socket.status_OK then
mle_1.text = mle_1.text + " - state OK status " + string(iu_socket.ius_socket.status)
else
mle_1.text = mle_1.text + "-state failed status"+ string(iu_socket.ius_socket.status)
end if
else
mle_1.text = mle_1.text + "close failed status " + string(iu_socket.ius_socket.status)
end if
parent.CloseUserObject(iu_socket) /* 关闭用户对象u_socket的实例 */
单行文本输入框“Input”用于用户输入,它接收用户输入并将其发送给服务器,然后接收服务器的回应(echo),并显示在多行文本框中。其Modified事件的脚本为:
blob{4096} buffer
int bytesread
buffer = Blob(sle_3.text)
bytesread = iu_socket.uf_SendData(buffer, Len(String(buffer))) /* 发送用户输入字符串 */
if iu_socket.ius_socket.Status <> iu_socket.status_ok then
mle_1.text = mle_1.text + "~r~nError: State = " + string(iu_socket.ius_socket.state) + &
" ErrMsg = " + string(iu_socket.ius_socket.ErrMsg)
end if
bytesread = iu_socket.uf_ReceiveData(buffer) /* 接收服务器回应 */
if iu_socket.ius_socket.Status = iu_socket.status_ok then
if bytesread > 0 then
mle_1.text = mle_1.text + "~r~n" + String(buffer)
mle_1.Scroll(mle_1.LineCount())
end if
else
mle_1.text = mle_1.text + "~r~nError: State = " + string(iu_socket.ius_socket.state) + &
" ErrMsg = " + string(iu_socket.ius_socket.ErrMsg)
end if
sle_3.SelectText(1, Len(sle_3.Text))
12.4.2 服务器程序
创建一个新的应用程序pbechos,将sockets.pbl中的用户对象u_socket,u_winsock,u_winsock16,u_winsock32和数据结构s_linger,s_sockaddr,s_wsadata拷贝到新的PowerBuilder库pbechos.pbl中。建立客户程序主窗口w_main如图12.2所示,并将应用程序pbechos的启动代码设置为打开此窗口。
图12.2 服务器程序主窗口
服务器的程序比较简单,它只需编写窗口的“Open”事件的脚本程序。这段程序完成一个套接字服务器的创建,并接收客户程序的连接;在新建连接的套接字上接收客户方发送的数据,并将其发送回客户程序;如果接收到“Quit”命令,则关闭套接字,回到服务器套接字等待接收连接状态。其脚本为:
u_socket iu_newsock
blob{4096} buffer
int bytesread
This.Show()
This.OpenUserObject(iu_socket,"u_socket") /* 创建用户对象u_socket的实例 */
if iu_socket.ius_socket.Status = iu_socket.status_OK then
mle_1.text = mle_1.text + "~r~n" + iu_socket.ius_wsadata.description
else
mle_1.text = "Error"
end if
if not iu_socket.uf_GetService('SjC160','tcp') then /* 获取服务信息 */
mle_1.text = mle_1.text + "~r~n" + "GetServByName Failed"
else
mle_1.text = mle_1.text + "~r~nPort: " + String(iu_socket.ws.ntohs(iu_socket.ii_portno))
end if
if iu_socket.uf_open() then /* 创建套接字 */
mle_1.text = mle_1.text + "~r~n" + "Open succeeds"
if iu_socket.ius_socket.status = iu_socket.status_OK then
mle_1.text = mle_1.text + "-state OK status=" + string(iu_socket.ius_socket.status)
else
mle_1.text = mle_1.text + " - state failed" + string(iu_socket.ius_socket.status)
end if
else
mle_1.text = mle_1.text + "~r~n"
end if
if iu_socket.uf_dobind() then /* 绑扎本地地址 */
mle_1.text = mle_1.text + "~r~n " + "Bind succeeds"
else
mle_1.text = mle_1.text + "~r~n" + "Bind to local address Failed"
end if
if iu_socket.uf_dolisten(1) then /* 使套接字成为被动套接字,处于监听状态 */
mle_1.text = mle_1.text + "~r~n " + "Listen succeeds"
else
mle_1.text = mle_1.text + "~r~n" + "Listen Failed"
end if
accept1: /* 等待接收外来连接状态 */
iu_newsock = iu_socket.uf_AcceptConnection(30000, This) /* 接收客户程序的连接 */
if isNull(iu_newsock) then
mle_1.text = mle_1.text + "~r~n" + "Accept Failed"
goto accept1
else
mle_1.text = mle_1.text + "~r~n " + "Accept succeeds"
end if
receive1: /* 等待接收数据状态 */
bytesread = iu_newsock.uf_ReceiveData(buffer) /* 接收客户程序发送的数据 */
if iu_newsock.ius_socket.Status = iu_socket.status_ok then
if bytesread > 0 then
mle_1.text = mle_1.text + "~r~n" + String(bytesread) + " bytes =" + &
Mid(String(buffer), 1, bytesread)
mle_1.Scroll(mle_1.LineCount())
if Mid(String(buffer), 1, 4) = "Quit" then /* 如果是“Quit”命令,则转关闭 */
goto close1
end if
else
mle_1.text = mle_1.text + "~r~nOK but 0 bytes recvd"
end if
else
mle_1.text = mle_1.text + "~r~nError: State = " + string(iu_socket.ius_socket.state) + &
" ErrMsg = " + string(iu_socket.ius_socket.ErrMsg)
end if
bytesread = iu_newsock.uf_SendData(buffer, bytesread) /* 将接收到的数据发送回客户 */
if iu_newsock.ius_socket.Status <> iu_newsock.status_ok then
mle_1.text = mle_1.text + "~r~nError: State = " + string(iu_newsock.ius_socket.state) &
+ " ErrMsg = " + string(iu_newsock.ius_socket.ErrMsg)
end if
goto receive1 /* 循环接收数据 */
close1:
if iu_newsock.uf_close() then /* 关闭套接字 */
mle_1.text = mle_1.text + "~r~n" + "close succeeds"
if iu_newsock.ius_socket.status = iu_newsock.status_OK then
mle_1.text = mle_1.text +"-state OK status" + string(iu_newsock.ius_socket.status)
else
mle_1.text =mle_1.text+"-state failed status"+string(iu_newsock.ius_socket.status)
end if
else
mle_1.text = mle_1.text + "close failed status " + string(iu_newsock.ius_socket.status)
end if
goto accept1 /* 循环接收客户程序的连接 */