Winsock控件实现多机互连方案
下面是实现多机互连的三种方案。这里以三台机(分别命名为1号机、2号机和3号机)为例介绍。
方案一:一台机作为服务器,其余两台机作为客户机
1号机作为服务器,用一个Winsock控件数组负责监听客户机的呼叫请求并用来与客户机建立连接。用TcpServer(2)和TcpServer(3)两个Winsock控件数组分别与2号机和3号机建立连接。
这里采用的是动态加载和卸载Winsock控件数组来实现连接的,也就是服务器一定要开着,下面的客户机才能与其通过Winsock控件实现通讯,当服务器已经与其中的一台建立连接后,其它客户机还要呼叫服务器时,服务器就会加载新的Winsock控件来与其建立连接,当客户机退出连接时,服务器再卸载该Winsock控件。服务器建立连接时是根据客户机的IP地址来接受响应的,所以可以方便的区分不同客户机的呼叫请求。
不过这种方案会遇到一些问题:比如只要服务器关闭,其它客户机之间就无法进行数据交换了,而在服务器开的情况下,可以通过服务器的转发来完成客户机之间的数据交换。
方案二:三台机同时作为服务器和客户机
1号机、2号机和3号机没有层次等级之分,采用C/S模式。比如1号机既可以作为服务器接受其它两台机的呼叫请求,又可以作为客户机对其它两台机进行呼叫。用1号机做个比方:在1号机程序窗口中用两个Winsock控件数组,分别命名为TcpServer和TcpClient,TcpServer(0)用来对客户机进行监听,TcpServer(2)和TcpServer(3)是动态加载用来接受相应的客户机的请求来建立连接的。而TcpClient(2)和TcpClient(3)不是动态加载的,而是在Form_Load初始化过程中加载,用来呼叫相应的服务器。
这样也是可以实现多机互连的,不过也有些问题。如果1号机作为客户机呼叫2号机并已经收到2号机的响应建立了一条通讯链路,这时1号机又作为服务器接收到2号机的呼叫请求,并且也建立了一条通讯链路。这样二台机之间建立了两条链路,理论上两台机只要有一条链路就可以正常通讯,现在建立了两条链路,收发数据是否正常呢?通过测试,收发数据不会出错:当1号机给2号机发数据时,是通过1号机的TcpServer(2)或TcpClient(2)发送数据给2号机,而2号机是通过其TcpClient(1)或TcpServer(1)接收1号机的数据,双方两条链路互不干扰。不过这种方案还是不太可取,因为加载控件需要占用内存资源,每两台机之间其实只需一条链路就能正常通讯,现在又多了条链路,这对系统有限的资源是极大的浪费。
方案三:三台机有差互连
先解释一下什么叫有差互连。具体做法是:1号机只作为服务器监听2号和3号机的呼叫请求而不呼叫它们;2号机既是客户机又是服务器:作为客户机只呼叫1号机,而作为服务器监听3号机的呼叫请求;3号机只作为客户机对1号机和2号机进行呼叫,而不具备服务器监听的功能。所以说这几台机的连接是有差的,这种连接方式不会在两台机之间建立两条链路,因为网络中任意两台机只有一台可以呼叫对方或监听对方的呼叫请求,这样无论如何都不会产生两条通讯链路,节省了系统资源,又满足了局域网中任意两台机互连的要求,由此看来这个方案是最优方案。
下面介绍方案三的实现过程。
1号机
1号机的窗体(Form)上放置两个Winsock控件,一个名为TcpLsn,负责监听2号机和3号机的呼叫请求,另外一个为TcpConn,这是个控件数组,Index为2,即已经加载了TcpConn(2),这个控件是为了和2号机建立连接。在初始化过程中(Form_Load)设置服务器的监听端口号(TcpLsn.LocalPort)并使其处于监听状态(TcpLsn.Listen),并置与客户机连接成功的标志(TcpConnected数组,布尔型常量)为False。当TcpLsn监听到某个客户机的呼叫请求后(具体是哪个客户机是根据客户机IP地址判断),在TcpLsn的ConnectionRequest事件中动态加载TcpConn控件并调用Accept方法接受客户机的请求,与其建立连接(如果客户机是2号机,则无需再加载控件,因为在Form_Load中已经加载过了)。要注意的是,TcpLsn只是用来监听客户机的呼叫请求,而不是用来与客户机建立连接的,TcpConn控件数组才是与客户机建立连接的。当某一客户机断开连接时,会触发Tcpconn控件数组的Close事件,在这里可以根据客户机的IP地址来调用Close方法关闭与其相连的Tcpconn控件,并动态卸载之。同样TcpConn(2)不能卸载,因为其不是动态加载的。
2号机
2号机窗体(Form)上也放置两个Winsock控件,一个名为TcpLsn,负责监听3号机的呼叫请求,另外一个为TcpConn,这是个控件数组,Index为1,即已经加载了TcpConn(1),这个控件是为了与1号机建立连接。在初始化过程中(Form_Load)同样要设置2号机作为服务器的监听端口号,然后使其处于监听状态,还要设置与其他几个站点连接成功的标志:TcpConnected(数组)。2号机作为服务器监听的过程同上,而呼叫1号机是通过VB6的定时器(Timer)实现的,定时器的作用是每隔一段时间(可自行设定间隔事件)触发Timer事件,即执行Timer事件中的代码,利用这个原理就可以实现一运行此程序就自动进行呼叫工作,首先将定时器间隔时间定为1000毫秒(定时器命名为TimConn,TimConn.Interval = 1000),然后在定时时间到事件中(TimConn_Timer)调用TcpConn的Connect方法呼叫1号机,当然要加一个判断:当TcpConnected(1)=False且TcpConn(1).State=sckClosed时才进行呼叫。
在客户机调用了TcpConn方法后,其连接状态是sckConnecting(正在连接服务器,值为6)。如果此时1号机在一段时间内(连接服务器超时时间)没有接受请求或者根本没有开启,那么就会触发连接错误事件(TcpConn_Error),这时连接状态是sckError(连接错误,值为9),就无法再进行连接服务器的工作了,如果1号机此时打开,客户机也不会再呼叫服务器了。要解决这个问题,可以在TcpConn的错误事件中(TcpConn_Error)加上一条语句:TcpConn(Index).Close即在错误事件中关闭当前的连接,使当前TcpConn控件状态处于sckClosed,这样在下一次的定时时间到事件中客户机又能呼叫1号机了。呼叫1号机成功后,不要忘记在TcpConn的Connect事件中置连接1号机成功的标志位。同样要注意关闭事件中不能卸载TcpConn(1)。
3号机
3号机在所有站点中只呼叫1号机和2号机。所以只要一个Winsock控件数组即可,命名为TcpClient,Index=1。在程序初始化过程中,加载呼叫服务器的所有Winsock控件:Load TcpClient(2)
TcpClient(1)已经放在窗体中了,故不必重复加载。还要设置连接成功的标志位(TcpConnected数组为False),并设置定时器间隔时间。然后在定时时间到事件中调用TcpClient的Connect方法连接服务器,这与2号机作为客户机呼叫服务器的过程类似,同样要在TcpClient的连接错误事件中添加以下语句:TcpClient(Index).Close。要注意的是在服务器的断开连接触发的客户机关闭事件中(TcpClient_Close)只需要置标志位,而不能卸载TcpClient控件数组,因为其不是动态加载的。
到此为止,基本上实现了运行此程序即进行多机互连的功能。
典型问题解析 1.各个站点建立连接后关闭3号机的程序,在其它的站点就会弹出对象已加载的错误提示。出现这个错误的可能原因是Winsock控件已经加载,而后又执行了一次加载动作。不过实验证明不是此Winsock控件被重复加载。在微软公司的官方网站,VB6最新的Service Pack 5补丁(SP5)的说明文档中有这样一条很重要的修正说明:重复加载或卸载Winsock控件会引起内存泄露。这一修正是不是可以针对用Winsock控件实现网络连接及通讯的程序呢?理论实践证明了这一猜测的真实性。下载完SP5并成功安装后,将程序原封不动运行一遍,“对象已加载”的错误窗口就再没出现过,这个问题也就成功的解决了。
2.由于设置了客户机的本地端口号(LocalPort),导致必须先关闭服务器再关闭客户机才能在下一次正常连接以及客户机异常退出时(比如客户机突然停电)导致下次无法正常连接。这是由于没有释放连接端口号造成的。这个错误的解决方法是不要设置客户机的本地端口号;如果非得设置,那么可以利用动态改变服务器监听端口号和客户机呼叫端口号的方法。具体做法是在服务器的Form_Load中改变监听端口号,在客户机的Winsock控件错误事件中改变呼叫端口号,端口号只要用两个就可以了(如1000和1001)。
查看本文来源