這里對(duì)Winsock控件的屬性、方法和事件的介紹限于篇幅就不介紹了,下面以最簡(jiǎn)單的C/S模式下一臺(tái)服務(wù)器和一臺(tái)客戶機(jī)的連接來說明其整個(gè)連接過程。
用框圖表示如圖1所示。首先運(yùn)行服務(wù)器端的程序,使TcpServer處于監(jiān)聽狀態(tài),然后運(yùn)行客戶機(jī)端的程序,單擊【連接服務(wù)器】按鈕后,客戶機(jī)端調(diào)用Connect方法呼叫服務(wù)器(根據(jù)RemoteHostIP遠(yuǎn)程計(jì)算機(jī)IP地址和RemotePort遠(yuǎn)程計(jì)算機(jī)端口號(hào)兩個(gè)參數(shù)),然后客戶機(jī)便處于正在連接服務(wù)器狀態(tài),等待服務(wù)器的響應(yīng)。客戶機(jī)調(diào)用Connect方法觸發(fā)了服務(wù)器ConnectRequest事件,這時(shí)服務(wù)器端可以在此事件中判斷是否要接受客戶機(jī)的請(qǐng)求,如要就調(diào)用Accept方法,并置標(biāo)志位說明已成功連接客戶機(jī)。服務(wù)器端調(diào)用Accept方法后又觸發(fā)了客戶機(jī)端Connect事件,說明服務(wù)器端接受客戶端的請(qǐng)求,雙方已經(jīng)建立連接了,這時(shí)再置客戶機(jī)端的標(biāo)志位,這就是一個(gè)完整的連接過程。當(dāng)服務(wù)器或客戶機(jī)調(diào)用Close方法關(guān)閉連接時(shí),都會(huì)觸發(fā)對(duì)方的Close事件,使其關(guān)閉連接。另外建立連接后發(fā)送數(shù)據(jù)的情況是客戶機(jī)通過調(diào)用SendData方法發(fā)送數(shù)據(jù)給服務(wù)器,會(huì)觸發(fā)服務(wù)器端的DataArrival事件,在這個(gè)事件中,服務(wù)器端可以調(diào)用GetData或PeekData方法把客戶機(jī)發(fā)來的數(shù)據(jù)保存起來。服務(wù)器給客戶機(jī)發(fā)信息同理。要注意一點(diǎn)的是,在服務(wù)器的Close事件中應(yīng)該加上繼續(xù)監(jiān)聽的代碼,這樣客戶機(jī)才可以繼續(xù)呼叫服務(wù)器。
Winsock控件實(shí)現(xiàn)多機(jī)互連方案
下面是實(shí)現(xiàn)多機(jī)互連的三種方案。這里以三臺(tái)機(jī)(分別命名為1號(hào)機(jī)、2號(hào)機(jī)和3號(hào)機(jī))為例介紹。
方案一:一臺(tái)機(jī)作為服務(wù)器,其余兩臺(tái)機(jī)作為客戶機(jī)
1號(hào)機(jī)作為服務(wù)器,用一個(gè)Winsock控件數(shù)組負(fù)責(zé)監(jiān)聽客戶機(jī)的呼叫請(qǐng)求并用來與客戶機(jī)建立連接。用TcpServer(2)和TcpServer(3)兩個(gè)Winsock控件數(shù)組分別與2號(hào)機(jī)和3號(hào)機(jī)建立連接。
這里采用的是動(dòng)態(tài)加載和卸載Winsock控件數(shù)組來實(shí)現(xiàn)連接的,也就是服務(wù)器一定要開著,下面的客戶機(jī)才能與其通過Winsock控件實(shí)現(xiàn)通訊,當(dāng)服務(wù)器已經(jīng)與其中的一臺(tái)建立連接后,其它客戶機(jī)還要呼叫服務(wù)器時(shí),服務(wù)器就會(huì)加載新的Winsock控件來與其建立連接,當(dāng)客戶機(jī)退出連接時(shí),服務(wù)器再卸載該Winsock控件。服務(wù)器建立連接時(shí)是根據(jù)客戶機(jī)的IP地址來接受響應(yīng)的,所以可以方便的區(qū)分不同客戶機(jī)的呼叫請(qǐng)求。
不過這種方案會(huì)遇到一些問題:比如只要服務(wù)器關(guān)閉,其它客戶機(jī)之間就無(wú)法進(jìn)行數(shù)據(jù)交換了,而在服務(wù)器開的情況下,可以通過服務(wù)器的轉(zhuǎn)發(fā)來完成客戶機(jī)之間的數(shù)據(jù)交換。
方案二:三臺(tái)機(jī)同時(shí)作為服務(wù)器和客戶機(jī)
1號(hào)機(jī)、2號(hào)機(jī)和3號(hào)機(jī)沒有層次等級(jí)之分,采用C/S模式。比如1號(hào)機(jī)既可以作為服務(wù)器接受其它兩臺(tái)機(jī)的呼叫請(qǐng)求,又可以作為客戶機(jī)對(duì)其它兩臺(tái)機(jī)進(jìn)行呼叫。用1號(hào)機(jī)做個(gè)比方:在1號(hào)機(jī)程序窗口中用兩個(gè)Winsock控件數(shù)組,分別命名為TcpServer和TcpClient,TcpServer(0)用來對(duì)客戶機(jī)進(jìn)行監(jiān)聽,TcpServer(2)和TcpServer(3)是動(dòng)態(tài)加載用來接受相應(yīng)的客戶機(jī)的請(qǐng)求來建立連接的。而TcpClient(2)和TcpClient(3)不是動(dòng)態(tài)加載的,而是在Form_Load初始化過程中加載,用來呼叫相應(yīng)的服務(wù)器。
這樣也是可以實(shí)現(xiàn)多機(jī)互連的,不過也有些問題。如果1號(hào)機(jī)作為客戶機(jī)呼叫2號(hào)機(jī)并已經(jīng)收到2號(hào)機(jī)的響應(yīng)建立了一條通訊鏈路,這時(shí)1號(hào)機(jī)又作為服務(wù)器接收到2號(hào)機(jī)的呼叫請(qǐng)求,并且也建立了一條通訊鏈路。這樣二臺(tái)機(jī)之間建立了兩條鏈路,理論上兩臺(tái)機(jī)只要有一條鏈路就可以正常通訊,現(xiàn)在建立了兩條鏈路,收發(fā)數(shù)據(jù)是否正常呢?通過測(cè)試,收發(fā)數(shù)據(jù)不會(huì)出錯(cuò):當(dāng)1號(hào)機(jī)給2號(hào)機(jī)發(fā)數(shù)據(jù)時(shí),是通過1號(hào)機(jī)的TcpServer(2)或TcpClient(2)發(fā)送數(shù)據(jù)給2號(hào)機(jī),而2號(hào)機(jī)是通過其TcpClient(1)或TcpServer(1)接收1號(hào)機(jī)的數(shù)據(jù),雙方兩條鏈路互不干擾。不過這種方案還是不太可取,因?yàn)榧虞d控件需要占用內(nèi)存資源,每?jī)膳_(tái)機(jī)之間其實(shí)只需一條鏈路就能正常通訊,現(xiàn)在又多了條鏈路,這對(duì)系統(tǒng)有限的資源是極大的浪費(fèi)。
方案三:三臺(tái)機(jī)有差互連
先解釋一下什么叫有差互連。具體做法是:1號(hào)機(jī)只作為服務(wù)器監(jiān)聽2號(hào)和3號(hào)機(jī)的呼叫請(qǐng)求而不呼叫它們;2號(hào)機(jī)既是客戶機(jī)又是服務(wù)器:作為客戶機(jī)只呼叫1號(hào)機(jī),而作為服務(wù)器監(jiān)聽3號(hào)機(jī)的呼叫請(qǐng)求;3號(hào)機(jī)只作為客戶機(jī)對(duì)1號(hào)機(jī)和2號(hào)機(jī)進(jìn)行呼叫,而不具備服務(wù)器監(jiān)聽的功能。所以說這幾臺(tái)機(jī)的連接是有差的,這種連接方式不會(huì)在兩臺(tái)機(jī)之間建立兩條鏈路,因?yàn)榫W(wǎng)絡(luò)中任意兩臺(tái)機(jī)只有一臺(tái)可以呼叫對(duì)方或監(jiān)聽對(duì)方的呼叫請(qǐng)求,這樣無(wú)論如何都不會(huì)產(chǎn)生兩條通訊鏈路,節(jié)省了系統(tǒng)資源,又滿足了局域網(wǎng)中任意兩臺(tái)機(jī)互連的要求,由此看來這個(gè)方案是最優(yōu)方案。
下面介紹方案三的實(shí)現(xiàn)過程。
1號(hào)機(jī)
1號(hào)機(jī)的窗體(Form)上放置兩個(gè)Winsock控件,一個(gè)名為TcpLsn,負(fù)責(zé)監(jiān)聽2號(hào)機(jī)和3號(hào)機(jī)的呼叫請(qǐng)求,另外一個(gè)為TcpConn,這是個(gè)控件數(shù)組,Index為2,即已經(jīng)加載了TcpConn(2),這個(gè)控件是為了和2號(hào)機(jī)建立連接。在初始化過程中(Form_Load)設(shè)置服務(wù)器的監(jiān)聽端口號(hào)(TcpLsn.LocalPort)并使其處于監(jiān)聽狀態(tài)(TcpLsn.Listen),并置與客戶機(jī)連接成功的標(biāo)志(TcpConnected數(shù)組,布爾型常量)為False。當(dāng)TcpLsn監(jiān)聽到某個(gè)客戶機(jī)的呼叫請(qǐng)求后(具體是哪個(gè)客戶機(jī)是根據(jù)客戶機(jī)IP地址判斷),在TcpLsn的ConnectionRequest事件中動(dòng)態(tài)加載TcpConn控件并調(diào)用Accept方法接受客戶機(jī)的請(qǐng)求,與其建立連接(如果客戶機(jī)是2號(hào)機(jī),則無(wú)需再加載控件,因?yàn)樵贔orm_Load中已經(jīng)加載過了)。要注意的是,TcpLsn只是用來監(jiān)聽客戶機(jī)的呼叫請(qǐng)求,而不是用來與客戶機(jī)建立連接的,TcpConn控件數(shù)組才是與客戶機(jī)建立連接的。當(dāng)某一客戶機(jī)斷開連接時(shí),會(huì)觸發(fā)Tcpconn控件數(shù)組的Close事件,在這里可以根據(jù)客戶機(jī)的IP地址來調(diào)用Close方法關(guān)閉與其相連的Tcpconn控件,并動(dòng)態(tài)卸載之。同樣TcpConn(2)不能卸載,因?yàn)槠洳皇莿?dòng)態(tài)加載的。
2號(hào)機(jī)
2號(hào)機(jī)窗體(Form)上也放置兩個(gè)Winsock控件,一個(gè)名為TcpLsn,負(fù)責(zé)監(jiān)聽3號(hào)機(jī)的呼叫請(qǐng)求,另外一個(gè)為TcpConn,這是個(gè)控件數(shù)組,Index為1,即已經(jīng)加載了TcpConn(1),這個(gè)控件是為了與1號(hào)機(jī)建立連接。在初始化過程中(Form_Load)同樣要設(shè)置2號(hào)機(jī)作為服務(wù)器的監(jiān)聽端口號(hào),然后使其處于監(jiān)聽狀態(tài),還要設(shè)置與其他幾個(gè)站點(diǎn)連接成功的標(biāo)志:TcpConnected(數(shù)組)。2號(hào)機(jī)作為服務(wù)器監(jiān)聽的過程同上,而呼叫1號(hào)機(jī)是通過VB6的定時(shí)器(Timer)實(shí)現(xiàn)的,定時(shí)器的作用是每隔一段時(shí)間(可自行設(shè)定間隔事件)觸發(fā)Timer事件,即執(zhí)行Timer事件中的代碼,利用這個(gè)原理就可以實(shí)現(xiàn)一運(yùn)行此程序就自動(dòng)進(jìn)行呼叫工作,首先將定時(shí)器間隔時(shí)間定為1000毫秒(定時(shí)器命名為TimConn,TimConn.Interval = 1000),然后在定時(shí)時(shí)間到事件中(TimConn_Timer)調(diào)用TcpConn的Connect方法呼叫1號(hào)機(jī),當(dāng)然要加一個(gè)判斷:當(dāng)TcpConnected(1)=False且TcpConn(1).State=sckClosed時(shí)才進(jìn)行呼叫。
在客戶機(jī)調(diào)用了TcpConn方法后,其連接狀態(tài)是sckConnecting(正在連接服務(wù)器,值為6)。如果此時(shí)1號(hào)機(jī)在一段時(shí)間內(nèi)(連接服務(wù)器超時(shí)時(shí)間)沒有接受請(qǐng)求或者根本沒有開啟,那么就會(huì)觸發(fā)連接錯(cuò)誤事件(TcpConn_Error),這時(shí)連接狀態(tài)是sckError(連接錯(cuò)誤,值為9),就無(wú)法再進(jìn)行連接服務(wù)器的工作了,如果1號(hào)機(jī)此時(shí)打開,客戶機(jī)也不會(huì)再呼叫服務(wù)器了。要解決這個(gè)問題,可以在TcpConn的錯(cuò)誤事件中(TcpConn_Error)加上一條語(yǔ)句:TcpConn(Index).Close即在錯(cuò)誤事件中關(guān)閉當(dāng)前的連接,使當(dāng)前TcpConn控件狀態(tài)處于sckClosed,這樣在下一次的定時(shí)時(shí)間到事件中客戶機(jī)又能呼叫1號(hào)機(jī)了。呼叫1號(hào)機(jī)成功后,不要忘記在TcpConn的Connect事件中置連接1號(hào)機(jī)成功的標(biāo)志位。同樣要注意關(guān)閉事件中不能卸載TcpConn(1)。
3號(hào)機(jī)
3號(hào)機(jī)在所有站點(diǎn)中只呼叫1號(hào)機(jī)和2號(hào)機(jī)。所以只要一個(gè)Winsock控件數(shù)組即可,命名為TcpClient,Index=1。在程序初始化過程中,加載呼叫服務(wù)器的所有Winsock控件:Load TcpClient(2)
TcpClient(1)已經(jīng)放在窗體中了,故不必重復(fù)加載。還要設(shè)置連接成功的標(biāo)志位(TcpConnected數(shù)組為False),并設(shè)置定時(shí)器間隔時(shí)間。然后在定時(shí)時(shí)間到事件中調(diào)用TcpClient的Connect方法連接服務(wù)器,這與2號(hào)機(jī)作為客戶機(jī)呼叫服務(wù)器的過程類似,同樣要在TcpClient的連接錯(cuò)誤事件中添加以下語(yǔ)句:TcpClient(Index).Close。要注意的是在服務(wù)器的斷開連接觸發(fā)的客戶機(jī)關(guān)閉事件中(TcpClient_Close)只需要置標(biāo)志位,而不能卸載TcpClient控件數(shù)組,因?yàn)槠洳皇莿?dòng)態(tài)加載的。
到此為止,基本上實(shí)現(xiàn)了運(yùn)行此程序即進(jìn)行多機(jī)互連的功能。
典型問題解析
1.各個(gè)站點(diǎn)建立連接后關(guān)閉3號(hào)機(jī)的程序,在其它的站點(diǎn)就會(huì)彈出對(duì)象已加載的錯(cuò)誤提示。出現(xiàn)這個(gè)錯(cuò)誤的可能原因是Winsock控件已經(jīng)加載,而后又執(zhí)行了一次加載動(dòng)作。不過實(shí)驗(yàn)證明不是此Winsock控件被重復(fù)加載。在微軟公司的官方網(wǎng)站,VB6最新的Service Pack 5補(bǔ)丁(SP5)的說明文檔中有這樣一條很重要的修正說明:重復(fù)加載或卸載Winsock控件會(huì)引起內(nèi)存泄露。這一修正是不是可以針對(duì)用Winsock控件實(shí)現(xiàn)網(wǎng)絡(luò)連接及通訊的程序呢?理論實(shí)踐證明了這一猜測(cè)的真實(shí)性。完SP5并成功安裝后,將程序原封不動(dòng)運(yùn)行一遍,“對(duì)象已加載”的錯(cuò)誤窗口就再?zèng)]出現(xiàn)過,這個(gè)問題也就成功的解決了。
2.由于設(shè)置了客戶機(jī)的本地端口號(hào)(LocalPort),導(dǎo)致必須先關(guān)閉服務(wù)器再關(guān)閉客戶機(jī)才能在下一次正常連接以及客戶機(jī)異常退出時(shí)(比如客戶機(jī)突然停電)導(dǎo)致下次無(wú)法正常連接。這是由于沒有釋放連接端口號(hào)造成的。這個(gè)錯(cuò)誤的解決方法是不要設(shè)置客戶機(jī)的本地端口號(hào);如果非得設(shè)置,那么可以利用動(dòng)態(tài)改變服務(wù)器監(jiān)聽端口號(hào)和客戶機(jī)呼叫端口號(hào)的方法。具體做法是在服務(wù)器的Form_Load中改變監(jiān)聽端口號(hào),在客戶機(jī)的Winsock控件錯(cuò)誤事件中改變呼叫端口號(hào),端口號(hào)只要用兩個(gè)就可以了(如1000和1001)。