利用 WinSock 控件可以與遠程計算機建立連接,並通過用戶數據文報協議 (UDP)或者傳輸控制協議 (TCP)進行數據交換。這兩種協議都可以用來創建客戶與服務器應用程序。與 Timer 控件類似,WinSock 控件在運行時是不可見的。
可能的用途
- 創建收集用戶信息的客戶端應用程序,並將收集的信息發送到某中央服務器。
- 創建一個服務器應用程序,作為多個用戶的數據的匯入點。
- 創建“聊天”應用程序。
選擇通訊協議
在使用 WinSock 控件時,首先需要考慮使用什么協議。可以使用的協議包括 TCP 和 UDP。兩種協議之間的重要區別在於它們的連接狀態:
- TCP 協議控件是基於連接的協議,可以將它同電話系統相比。在開始數據傳輸之前,用戶必須先建立連接。
- UDP 協議是一種無連接協議,兩台計算機之間的傳輸類似於傳遞郵件:消息從一台計算機發送到另一台計算機,但是兩者之間沒有明確的連接。另外,單次傳輸的最大數據量取決於具體的網絡。
到底選擇哪一種協議通常是由需要創建的應用程序決定的。下面的幾個問題將有助於選擇適宜的協議:
- 在收發數據的時候,應用程序是否需要得到客戶端或者服務器的確認信息?如果需要,使用 TCP 協議,在收發數據之前先建立明確的連接。
- 數據量是否特別大(例如圖象與聲音文件)?在連接建立之后,TCP 協議將維護連接並確保數據的完整性。不過,這種連接需要更多的計算資源,因而是比較“昂貴”的。
- 數據發送是間歇的,還是在一個會話內?例如,如果應用程序在某個任務完成的時候需要通知某個計算機,UDP 協議是更適宜的。UDP 協議適合發送少量的數據。
協議的設置
在設計時,可以按如下方式設置應用程序使用的協議:在“屬性”窗口中單擊“協議”,然后選擇 sckTCPProtocol 或者 sckUDPProtocol。也可以使用程序代碼來設置 Protocol 屬性,如下所示:
-
Winsock1.Protocol = sckTCPProtocol
確定計算機的名稱
在與遠程計算機相連接的時候,需要知道它的 IP 地址或者它的“好聽的名字”。IP 地址是一串數字,每三個數字為一組,中間用點隔開(形如 xxx.xxx.xxx.xxx)。通常,最易記住的是計算機的“好聽的名字”。
要確定計算機的名字,請按照以下步驟執行:
- 在計算機的“任務欄”上,單擊“啟動”。
- 在“設置”項中,單擊“控制面板”。
- 雙擊“網絡”圖標。
- 單擊“標識”選項卡。
- 在“計算機名稱”框中可以找到計算機的名稱。
上面找到的計算機名稱可以作為 RemoteHost 屬性的值。
TCP 連接初步
如果應用程序要使用 TCP 協議,那么首先必須決定應用程序是服務器還是客戶端。如果要創建一個服務器端,那么應用程序需要“監聽”指定的端口。當客戶端提出連接請求時,服務器端能夠接受請求並建立連接。在連接建立之后,客戶端與服務器端可以自由地互相通訊。
下列步驟創建一個非常簡單的服務器:
要創建一個 TCP 服務器,請按照以下步驟執行:
- 創建新的 Standard EXE 工程。
- 將缺省窗體的名稱改為 frmServer。
- 將窗體的標題改為“TCP 服務器”。
- 在窗體中放入一個 Winsock 控件,並將它的名字改為 tcpServer。
- 在窗體上添加兩個 TextBox 控件。將第一個命名為 txtSendData,第二個為 txtOutput。
- 為窗體添加如下的代碼。
- VBScript code 復制代碼
-
Private Sub Form_Load() '將 LocalPort 屬性設置為一個整數。 '然后調用 Listen 方法。 tcpServer.LocalPort = 1001 tcpServer.Listen frmClient.Show '顯示客戶端的窗體。 End Sub Private Sub tcpServer_ConnectionRequest _ (ByVal requestID As Long) '檢查控件的 State 屬性是否為關閉的。 '如果不是, '在接受新的連接之前先關閉此連接。 If tcpServer.State <> sckClosed Then _ tcpServer.Close '接受具有 requestID 參數的 '連接。 tcpServer.Accept requestID End Sub Private Sub txtSendData_Change() '名為 txtSendData 的 TextBox 控件中 '包含了要發送的數據。當用戶往文本框中 '鍵入數據時,使用 SendData 方法 '發送輸入的字符串。 tcpServer.SendData txtSendData.Text End Sub Private Sub tcpServer_DataArrival _ (ByVal bytesTotal As Long) '為進入的數據聲明一個變量。 '調用 GetData 方法,並將數據賦予名為 txtOutput '的 TextBox 的 Text 屬性。 Dim strData As String tcpServer.GetData strData txtOutput.Text = strData End Sub
上面的步驟創建了一個簡單的服務器應用程序。為了使它能夠工作,還必須為它創建一個客戶端的應用程序。
要創建 TCP 客戶端,請按照以下步驟執行:
- 在工程中添加一個新的窗體,將其命名為 frmClient。
- 將窗體的標題改為“TCP Client”。
- 在窗體中添加一個 Winsock 控件,並將其命名為 tcpClient。
- 在 frmClient 中添加兩個 TextBox 控件。將第一個命名為 txtSend,第二個為 txtOutput。
- 在窗體上放一個 CommandButton 控件,並將其命名為 cmdConnect。
- 將 CommandButton 控件的標題改為 Connect。
- 在窗體中添加如下的代碼。
重點 必須將 RemoteHost 屬性值修改為您的計算機的名字。
- VBScript code 復制代碼
-
Private Sub Form_Load() 'Winsock 控件的名字為 tcpClient。 '注意:要指定遠程主機,可以使用 ' IP 地址(例如:"121.111.1.1"),也可以使用 '計算機的“好聽的名字”如下所示。 tcpClient.RemoteHost = "RemoteComputerName" tcpClient.RemotePort = 1001 End Sub Private Sub cmdConnect_Click() '調用 Connect 方法,初始化連接。 tcpClient.Connect End Sub Private Sub txtSendData_Change() tcpClient.SendData txtSend.Text End Sub Private Sub tcpClient_DataArrival _ (ByVal bytesTotal As Long) Dim strData As String tcpClient.GetData strData txtOutput.Text = strData End Sub
上面的代碼創建了一個簡單的客戶/服務器模式的應用程序。我們可以將兩者都運行起來:運行工程,然后單擊“連接”。在兩個窗體之一的 txtSendData 文本框中鍵入文本,可以看到同樣的文字將出現在另一個窗體的 txtOutput 文本框中。
接受多個連接請求
上面設計的基本服務器只能接受一個連接請求。通過創建控件數組,使用一個控件也可以同時接受多個連接請求。利用這種方法,不需要關閉連接,而只需創建新的控件實例(通過設置其索引屬性),然后在新的實例上調用 Accept 方法。
下面的代碼假定名為 sckServer 的窗體上有一個 Winsock 控件,它的 Index 屬性被設置為 0;因此控件是控件數組的一部分。在聲明部分,聲明了一個模塊級的變量 intMax。在窗體的 Load 事件中,intMax 被設置為 0,數組中第一個控件的 LocalPort 屬性被設置為 1001。然后調用控件的 Listen 方法,使之成為“監聽”控件。在連接請求到達時,代碼將檢測 Index 是否為 0(“監聽”控件的值)。如果為 0,監聽控件將增加 intMax 的值,並使用該號碼來創建新的控件實例。然后,使用新的控件實例接受連接請求。
- VBScript code 復制代碼
-
Private intMax As Long Private Sub Form_Load() intMax = 0 sckServer(0).LocalPort = 1001 sckServer(0).Listen End Sub Private Sub sckServer_ConnectionRequest _ (Index As Integer, ByVal requestID As Long) If Index = 0 Then intMax = intMax + 1 Load sckServer(intMax) sckServer(intMax).LocalPort = 0 sckServer(intMax).Accept requestID Load txtData(intMax) End If End Sub
UDP 初步
創建 UDP 應用程序比創建 TCP 應用程序還要簡單,因為 UDP 協議不需要顯式的連接。在上面的 TCP 應用程序中,一個 Winsock 控件必須顯式地進行“監聽”,另一個必須使用 Connect 方法初始化連接。
UDP 協議不需要顯式的連接。要在兩個控件中間發送數據,需要完成以下的三步(在連接的雙方):
- 將 RemoteHost 屬性設置為另一台計算機的名稱。
- 將 RemotePort 屬性設置為第二個控件的 LocalPort 屬性。
- 調用 Bind 方法,指定使用的 LocalPort。(下面將詳細地討論該方法。)
因為兩台計算機的地位可以看成“平等的”,這種應用程序也被稱為點到點的。為了具體說明這個問題,下面將創建一個“聊天”應用程序,兩個人可以通過它進行實時的交談。
要創建一個 UDP 伙伴,請按照以下步驟執行:
- 創建一個新的 Standard EXE 工程。
- 將缺省的窗體的名稱修改為 frmPeerA。
- 將窗體的標題修改為“Peer A”。
- 在窗體中放入一個 Winsock 控件,並將其命名為 udpPeerA。
- 在“屬性”頁上,單擊“協議”並將協議修改為 UDPProtocol。
- 在窗體中添加兩個 TextBox 控件。將第一個命名為 txtSend,第二個命名為 txtOutput。
- 為窗體增加如下的代碼。
- VBScript code 復制代碼
-
Private Sub Form_Load() '控件的名字為 udpPeerA With udpPeerA '重點:必須將 RemoteHost 的值 '修改為計算機的名字。 .RemoteHost = "PeerB" .RemotePort = 1001 '連接的端口號。 .Bind 1002 '綁定到本地的端口。 End With frmPeerB.Show '顯示第二個窗體。 End Sub Private Sub txtSend_Change() '在鍵入文本時,立即將其發送出去。 udpPeerA.SendData txtSend.Text End Sub Private Sub udpPeerA_DataArrival _ (ByVal bytesTotal As Long) Dim strData As String udpPeerA.GetData strData txtOutput.Text = strData End Sub
要創建第二個 UDP 伙伴,請按照以下步驟執行:
- 在工程中添加一個標准窗體。
- 將窗體的名字修改為 frmPeerB。
- 將窗體的標題修改為“Peer B”。
- 在窗體中放入一個 Winsock 控件,並將其命名為 udpPeerB。
- 在“屬性”頁上,單擊“協議”並將協議修改為“UDPProtocol”。
- 在窗體上添加兩個 TextBox 控件。將第一個命名為 txtSend,第二個命名為 txtOutput。
- 在窗體中添加如下的代碼。
- VBScript code 復制代碼
-
Private Sub Form_Load() '控件的名字為 udpPeerB。 With udpPeerB '重點:必須將 RemoteHost 的值改為 '計算機的名字。 .RemoteHost = "PeerA" .RemotePort = 1002 '要連接的端口。 .Bind 1001 '綁定到本地的端口上。 End With End Sub Private Sub txtSend_Change() '在鍵入后立即發送文本。 udpPeerB.SendData txtSend.Text End Sub Private Sub udpPeerB_DataArrival _ (ByVal bytesTotal As Long) Dim strData As String udpPeerB.GetData strData txtOutput.Text = strData End Sub
如果要試用上面的例子,按 F5 鍵運行工程,然后在兩個窗體的 txtSend TextBox 中分別鍵入一些文本。鍵入的文字將出現在另一個窗體的 txtOutput TextBox 中。
關於 Bind 方法
在上面的代碼中,在創建 UDP 應用程序時調用了 Bind 方法,這是必須的。Bind 方法的作用是為控件“保留”一個本地端口。例如,如果將控件綁定到 1001 號端口,那么其它應用程序將不能使用該端口進行“監聽”。該方法阻止其它應用程序使用同樣的端口。
Bind 方法的第二個參數是任選的。如果計算機上存在多個網絡適配器,可以用 LocalIP 參數來指定使用哪一個適配器。如果忽略該參數,控件使用的將是計算機上“控制面板”設置中“網絡”控制面板對話框中列出的第一個適配器。
在使用 UDP 協議的時候,可以任意地改變 RemoteHost 和 RemotePort 屬性,同時始終保持綁定在同一個 LocalPort 上。TCP 協議與此不同,在改變 RemoteHost 和 RemotePort 屬性之前,必須先關閉連接。