這裡對Winsock控件的屬性、方法和事件的介紹限於篇幅就不介紹了,下面以最簡單的C/S模式下一台服務器和一台客戶機的連接來說明其整個連接過程。
用框圖表示如圖1所示。首先運行服務器端的程序,使TcpServer處於監聽狀態,然後運行客戶機端的程序,單擊【連接服務器】按鈕後,客戶機端調用Connect方法呼叫服務器(根據RemoteHostIP遠程計算機IP地址和RemotePort遠程計算機端口號兩個參數),然後客戶機便處於正在連接服務器狀態,等待服務器的響應。客戶機調用Connect方法觸發了服務器ConnectRequest事件,這時服務器端可以在此事件中判斷是否要接受客戶機的請求,如要就調用Accept方法,並置標志位說明已成功連接客戶機。服務器端調用Accept方法後又觸發了客戶機端Connect事件,說明服務器端接受客戶端的請求,雙方已經建立連接了,這時再置客戶機端的標志位,這就是一個完整的連接過程。當服務器或客戶機調用Close方法關閉連接時,都會觸發對方的Close事件,使其關閉連接。另外建立連接後發送數據的情況是客戶機通過調用SendData方法發送數據給服務器,會觸發服務器端的DataArrival事件,在這個事件中,服務器端可以調用GetData或PeekData方法把客戶機發來的數據保存起來。服務器給客戶機發信息同理。要注意一點的是,在服務器的Close事件中應該加上繼續監聽的代碼,這樣客戶機才可以繼續呼叫服務器。
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控件:LoadTcpClient(2)
TcpClient(1)已經放在窗體中了,故不必重復加載。還要設置連接成功的標志位(TcpConnected數組為False),並設置定時器間隔時間。然後在定時時間到事件中調用TcpClient的Connect方法連接服務器,這與2號機作為客戶機呼叫服務器的過程類似,同樣要在TcpClient的連接錯誤事件中添加以下語句:TcpClient(Index).Close。要注意的是在服務器的斷開連接觸發的客戶機關閉事件中(TcpClient_Close)只需要置標志位,而不能卸載TcpClient控件數組,因為其不是動態加載的。
到此為止,基本上實現了運行此程序即進行多機互連的功能。
典型問題解析
1.各個站點建立連接後關閉3號機的程序,在其它的站點就會彈出對象已加載的錯誤提示。出現這個錯誤的可能原因是Winsock控件已經加載,而後又執行了一次加載動作。不過實驗證明不是此Winsock控件被重復加載。在微軟公司的官方網站,VB6最新的ServicePack5補丁(SP5)的說明文檔中有這樣一條很重要的修正說明:重復加載或卸載Winsock控件會引起內存洩露。這一修正是不是可以針對用Winsock控件實現網絡連接及通訊的程序呢?理論實踐證明了這一猜測的真實性。下載完SP5並成功安裝後,將程序原封不動運行一遍,“對象已加載”的錯誤窗口就再沒出現過,這個問題也就成功的解決了。
2.由於設置了客戶機的本地端口號(LocalPort),導致必須先關閉服務器再關閉客戶機才能在下一次正常連接以及客戶機異常退出時(比如客戶機突然停電)導致下次無法正常連接。這是由於沒有釋放連接端口號造成的。這個錯誤的解決方法是不要設置客戶機的本地端口號;如果非得設置,那麼可以利用動態改變服務器監聽端口號和客戶機呼叫端口號的方法。具體做法是在服務器的Form_Load中改變監聽端口號,在客戶機的Winsock控件錯誤事件中改變呼叫端口號,端口號只要用兩個就可以了(如1000和1001)。->