揭開Winsock的神祕面紗

揭開Winsock的神祕面紗
2007-03-09 21:21
在今天 TCP/IP 處理所有 Internet 上的通信流. 在局域網上也可以運行 TCP/IP. 我們可以利用這一點, 並在諸如 FTP, IRC, e-mail, WWW 或其它任何 Internet 標準類型的通信. 要達到這樣的目的, 需要使用包含在 Windows 中的一個 DLL, 也就是所謂的 WSOCK32.DLL 或相似的名字.
     在使用 DLL 中的唯一問題, 直接的, 是我們將不得不控制和處理每一個函數的反應而且這將會造成額外的和不必要的開銷.
     Windows 已經有一個 ActiveX 叫做 WINSOCK.OCX. 它使用與我們所需要的相同的 DLL, 並已經用方法和事件控制和處理了整個反應, 使它易於使用.

初識 ActiveX Winsock 控件

方法 說明
Accept(requestID) 該方法用於接收一個特寫的連接. 它作爲一個參數傳遞來處理請求.
Bind(LocalPort,LocalIP) 爲 CDP 連接指定端口和本地 IP.
Close() 關閉服務器和客戶之間的活動的連接.
GetData(Data,Type,maxLen) 用緩衝中的內容填充變量, 使其爲空.
Listen() 對象等等連接.
PeekData(Data,Type,maxLen) 用緩衝中的內容填充變量, 但不清空緩衝.
SendData(Data) 發送數據到遠程計算機.

事件 說明
Close 當遠程計算機關閉當前連接時發生
Connect(Error) 與服務器的連接成功後.
ConnectionRequest(requestID) 當遠程計算機發出一個請示時.
DataArrival(BytesTotal) 在從遠程計算機上接收到新數據時發生.
Error(number, Description, Scode, Source, HelpFile, HelpContext, CancelDisplay) 當發生後臺處理錯誤時.
SendComplete 數據發送完畢時發生
SendProgress(bytesSent, bytesRemaining) 在發送數據時.

屬性 說明
BytesReceived 返回到緩衝中的字節數
LocaHostName 返回本地機器的名字
LocalIP 本地計算機的 IP 地址
LocalPort 數據傳送的端口 (客戶) 或代表一個連接 (服務器)
Name 對象名
Object 運行時自動創建, 僅在 VFP 中.
Protocol 使用的協議 TCP (0) 或 UDP (1)

屬性 說明
RemoteHost 返回遠程計算機的端口
RemoteHostIP 返回遠程計算機 IP
RemotePort 返回遠程計算機上的連接端口
SocketHandle 返回控件當前連接的句柄
State 返回控件的狀態:
0 = 已關閉
1 = 打開
2 = 等待連接
3 = 正在連接
4 = 決定主機
5 = 主機已決定
6 = 正在連接
7 = 已連接
8 = 連接被遠程計算機關閉
9 = 錯誤

在 VFP 中使用它
在 VFP 中使用 ActiveX Winsock 時你必須牢牢記住:
在運行任何 ActiveX 方法前, 必須添加 .F. 到應用程序的 AutoYeld 屬性 (application.autoyield = .f.);
所有與 Windsock 相關的方法, 事件或屬性必須加上 object 前綴(thisFORM.wsock1.OBJECT.connect)
VFP 不能處理字符串中的 CHR(0) 字符. 因此, 如果你要處理帶有這些內容的數據就需要一字節一字節地讀緩衝中接收到的數據. 例如, 就象連接到 Unix 服務器一樣.
讓我們看看示例 1 來開始測試我們的過程:
它由兩個表單組成: 1 個客戶表單和一個服務器表單. 它的功能是從客戶傳送一個文本信息到服務器. 另外服務器以小寫方式返回相同的信息.
請注意在兩個表單的 INIT 方法中的命令 Application.AutoYield = .f.. 這意味着我們告訴 VFP 不處理方法中的每一個代碼行, 讓 ActiveX 自己控制它的事件. 在表單的 CLOSE 方法中我們包括了 Application.AutoYield = .t. 來允許 VFP 按一般方法控制事件.

在 SERVER 表單的 INIT 方法中有兩個重要的命令:
thisFORM.sock1.object.LocalPort = 3001
使用以上命令, ActiveX Winsock 在端口 3001上接收連接.
thisFORM.sock1.object.Listen()

該命令告訴 Winsock to 等待連接. 從此時開始, 上面的命令中定義的端口將打開並等待一個連接.
當檢測到連接時, 將觸發 ConnectionRequest 事件, 發送這個新連接的句柄作爲一個參數. 查看示例中的該方法的詳細情況:
This.object.close()
確信沒有其它未決的連接並停止等待.
This.object.Accept(requestid)
運行 Accept 方法, 發送連接句柄作爲參數. 僅現在連接是設置了的.
現在, 讓我們到 CLIENTE 表單, 在發送按鈕的 CLICK 方法上:

lc_local_IP = thisFORM.sock1.object.LocalIP
我們用服務器的 IP 地址定義一個變量. 在此情況下, 我們從 Winsock 的 LocalIP 屬性來獲得當前計算機的地址.
lc_local_Port = 3001
我們定義了一個將連接到服務器端口的變量. 請確保該端口對於客戶和服務器是相同的.
thisFORM.sock1.object.close()
確信沒有未決的連接, 並不再等待另一個連接.
thisFORM.sock1.object.Connect(lc_local_IP,lc_local_Port)
用 IP 地址和端口爲參數運行方法來連接服務器..
在這一點上, 我們可以看到一個循環. 它的主要功能是檢查 Winsock 的狀態和等待連接的確認.

定義一個期限是重要的, 否則此處的循環將有可能使系統崩潰.

另一個重要的東西是注意循環中的 inkey(0.1), 用於避免不必要的使用 CPU 資源.
在連接成功後並確認後, 我們可以看到以下命令:
thisFORM.sock1.object.SendData(trim(thisFORM.texto.value))
運行該方法來傳送數據. 在我們的特寫場合, 是發送文本框 TEXTO 中的內容.
在下一階斷, 我們再次看到一個與前面非常相似的循環. 它檢查 ENVIADO_OK 變量的內容. 當數據完成後, Winsock 的 SendComplete 事件將自動運行, 且它將包含一個 .T. 到變量中.

現在我們可以看到另一個等待服務器 (SERVIDOR) 迴應的循環.
現在讓我們回到 SERVIDOR 表單, 在 Winsock' 的 DataArrival 事件中. 該事件在每次從遠程計算機上接收到數據時執行. 緩衝的字節數作爲參數給出.
LPARAMETERS bytestotal
lc_buffer = space(bytestotal)
現在我們用 Winsock 的緩衝大小定義一個變量作爲緩存.
this.object.GetData(@lc_buffer)
我們運行 GetData 方法, 給它一個要接收緩衝中的數據的變量名引用 (@). 該方法也會清除 Winsock 的緩衝.
thisFORM.retorno.value = lc_buffer
在這一點上, 我們展示已被 GetData 方法填充了的變量的內容.
this.object.SendData(lower(lc_buffer))

作爲一個對客戶 (CLIENTE) 的迴應我們以小寫方式發送緩衝中的內容.
回到 CLIENTE 表單, 在 Winsock 的 DataArrival 方法中, 我們可以看到它獲得了 Winsock 的緩衝中的內容並將它放入到文本框 RETORNO 中, 處理結束.
繼續捍代碼, 我們用 thisFORM.sock1.object.close() 命令關閉連接, 觸發服務器 (SERVIDOR) 的 CLOSE 事件:
this.object.Close()
當前連接已經關閉. 無論是在 SERVIDOR 表單還是在 CLIENTE 表單中, 在 CLOSE 事件中的該命令是非常重要的, 否則 Winsock 將耗盡機器的所有資源, 因爲它會在有些地方進行循環.
this.object.Listen()
現在我們運行方法, 這樣它可以等待下一個連接.
讓我們到使用 Winsock 的示例 2:

創建一個聊天
我們通常希望在我們的程序內部進行聊天. 好吧, 該示例就是關於該功能的. 爲了讓它更簡單些, 該資源以只在局域網中運行的方式創建, 因爲它使用一個表來保存一些重要的信息. 因此它不能在 Internet 連接上工作.
該功能以非常簡單的方式運行. 表 USER_ONLINE.DBF 是關鍵. 在該表中有以下字段:
字段 類型 大小 說明
USUARIO 字符 15 保存登錄的用戶名
IP_USER 字符 15 保存計算機的 IP
PORT_USER 數值 5 保存由計算機生成的端口
ON_CHAT 邏輯 定義用戶是否在進行聊天
在運行 CHAT 表單集時, 出現的第一個表單是用戶表單 (USUARIO). 在該表單上, 我們選擇或輸入想與之聊天的用戶. 在得到確認後, 生成的註冊用 RLOCK 鎖住, 因此, 沒有其它終端可以再使用它. 然後, 讓表單不可見並顯示 ON_LINE 表單.
在 ON_LINE 表單的 TIMER1 中計時器 5 秒鐘觸發一次, 運行 USERS 方法. 最後的運行遍歷表試圖鎖定每一個註冊. 在它不能鎖住一個註冊時, 就意味着該用戶在線, 且必須放入列表框 LISTA 中.
準備好了! 在程序的這一點上, 我們可以控制用戶訪問並知道誰在線誰不在線.
雙擊列表框中的用戶名, 程序將試圖打開一個聊天, 在檢查了用戶真的在線後 (試着鎖註冊) 或檢查 ON_CHAT = .T. 變量看其是否正在與另一個用戶聊天. 在聊天被設置後, 他會得到一個 IP 號並從表中選擇用戶端口並試着連接. 這就是 Winsock 什麼時候參與進來的.
一但連接成功, 將打開一個新的表單: CHAT 表單. ON_CHAT 變量被設置爲 .T. 因此在終端可以與另一個有關的終端進行聊天.
在這一點上, 我們的連接已經成功且 CHAT 窗口已經打開. 現在可以進行通信了.
在打開的 CHAT 表單上, 我們用 CommandButton 命令按鈕來發送寫到編輯框 MSG 中的文本. 該過程使用了 Send 方法.
要關閉當前的對話, 要做的所有事情就是關閉 CHAT 窗口. 另一個機器上的窗口也會自動關閉. 在這一點上, ON_CHAT 變量已經包含了 .F. 值, 並且可以接收新的聊天請求.
分析該示例中使用的各個方法是重要的, 因爲所有便於理解 Winsock 動作的解釋都在代碼中.

Internet
所有 Internet 通信遵循一些預定的標準 (RFC) 如 HTTP, FTP, POP, SMTP, IRC, 等.
在已有的定義中, 默認情況下, 每一個服務將有一個命令組, 各命令組在接收到該命令後將有一個 reply-code 發送自服務器. 這回復通信命令是否成功地接收了. 因此, 要使用任何採用 RFC 約定的應用程序, 我們必須預先知道它的命令和回覆.
我建議你訪問 www.networksorcery.com/enp/default0401.htm 網站來熟悉所有標準. 你也可以用其它搜索引擎來搜索 RFC 標準.

FTP 與 Winsock
在示例 3 中我們將看看它是如何連接到 FTP, 列出 FTP 上的文件名和從 FTP 下載文件的.
FTP 協議使用兩個 Winsocks: 其中一個用於管理髮送命令到服務器; 另一個接收數據 (文件, 目錄等) 傳送. 第二個 Winsock 是一個被動連接, 意思是服務器要連接到它. 對於每一個數據傳送它需要通知服務器將要連接的 IP 和端口. 這些數據將由我們的 Gera_Porta() 方法生成.
在命令按鈕 Conectar 中我們設置連接. 重要地注意是在 Winsock 的 .Connect 方法被調用後我們必須運行 Wait("220") 方法. 該方法的主要目的是等待服務器的回覆, 其中的 220 意思是連接成功.
然後, 我們運行方法

.Enviar ("User " + alltrim(thisFORM.user.value),"331")
該方法發送用戶到服務器, 並等待來自服務器的 331 迴應.
在用戶名和口令被接收後, 運行方法 lista_dir("*.zip"). 該方法詢問 FTP 服務器上的所有 *.zip 文件. 可能它找不到任何 zip 文件, 程序將詢問 *.exe.
現在我們有了一個服務器的文件清單, 我們將獲取最後的文件並用 .Download(). 方法下載它. 在所有數據都接收完後, 服務器將發送一個 226 迴應, 通知進程結束. 但是, 緩衝仍然保持可用, 因此我們必須等待到下載的結束.
在下載完成後, 程序詢問要發送到服務器的文件. 在 .upload() 方法中我們可以觀察到代碼. 這些代碼用 32 Kbytes 緩衝發送文件.
認真查看上面示例中的每一個方法中的代碼是重要的, 因爲所有的說明都在其中.

用 Winsock 發送的接收 e-mail
在寫程序時, 許多開發者需要從程序發送或接收 e-mail. 最流行的方案是通過 Outlook Express. 但有可能計算機中沒有該 e-mail 工具. 咋個辦?
在示例 4 中我們將分析一個簡單的允許從程序內發送 e-mail 的方法.
從 Conectar 命令按鈕中, 運行方法 .Conecta_Pop(). 該方法連接到 POP 服務器交等待一個服務器的 +OK 迴應. 服務器處理身份鑑定, 發送用戶名和口令, 並回到命令按鈕.
現在調用 .checa_msg() 方法. 它發送 STAT 命令到服務器, 並等待服務器的 +OK 信息. 稍過片刻, 收件箱中的 e-mail 被髮送.
使用命令 RETR n, 其中 n 是希望的信息號, 我們必須處理下載各個信息(譯者注: 一個信息就是一個郵件).
你可以向該方法傳遞一個 .t. 參數, 它將刪除用 DELE n 命令刪除信息 (再次重申 n 是要處理的信息號).
再次回到命令按鈕, 我們可以看到 .Conecta_Smtp(). 方法被調用. 該方法連接到 SMTP 服務器並等待迴應. 接着, 它用 HELO nome_da_esta玢o 命令來處理 SMTP 服務器上的身份識別.
現在我們調用 .envia_msg() 方法. 在該方法中, 顯示 e-mail 頭並且我們必須發送 RSET 命令來開始發送 e-mail 到服務器. 各收件人用 RCPT TO: endere鏾_email 命令發送. 服務器將不檢查該地址.
在所有地址都發送後, 爲了準備服務器接收 e-mail, 另一個命令 DATA 將被髮送. 該過程以 8K 的包發送.
最後, 我們發送命令 CHR(13)+CHR(10) +"." + CHR(13)+CHR(10) 到服務器, 通知信息發送完畢.
看看要發送和接收一個 e-mail 有多容易吧?

一些 SMTP 服務器要求身份鑑定. 身份鑑定可以用兩種方法進行:
作爲一個用戶用他的口令連接到 POP 服務器, 然後立即斷開. (我們在檢查新的郵件時已經這樣做了).
用 UUCODE Base 64 加密口令識別你自己到 SMTP 服務器. 我們將不討論這個細節, 因爲我們必須寫一個算法這樣我們的示例將太複雜. 但是, 對於使用 Visual FoxPro 7 的開發者, 這隻需用一個 VFP 自身的函數: STRCONV (dados,13) 或 STRCONV (dados,14).
我們建議你查看上面示例中的每一個方法, 因爲所有解釋都在其中.
結論
從以上的示例中, 我們可以注意到在 VFP 中使用 Winsock 是非常簡單的任務. 我們所需要做的只是知道命令集和迴應, 我們可以處理 Internet 標準 (RFC) 或象在聊天示例中一樣開發一個唯一的標準.
我們也可以觀察所有示例中的常用方法. 這意味着可以開發一個符合需要標準的單一的單化我們的更多的工作的類.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章