使用Winsock控件,實(shí)現(xiàn)網(wǎng)絡(luò)點(diǎn)對點(diǎn)通信
2024-07-21 02:24:12
供稿:網(wǎng)友
網(wǎng)絡(luò)的階梯第二話:
使用winsock控件,實(shí)現(xiàn)網(wǎng)絡(luò)點(diǎn)對點(diǎn)通信
blog出現(xiàn)在csdn,也就blog將不blog也。你會問為什么吧?無論你心里有沒有這個問號,但在我心里這是個句號!你又會問為什么嗎?好,不管你問不問。我決定,現(xiàn)在作答。但那種長篇大論的前因后果,請恕我無法一一羅列。我只能直接而又間接地說明,blog出現(xiàn)在csdn,也就blog將不blog也。
使用vb,寫過網(wǎng)絡(luò)程序,沒試過winsock這個控件實(shí)在是遺憾(api高手除外)。我想沒有朋友有這種遺憾的...呵呵!因為,通過winsock控件,你可以把網(wǎng)絡(luò)通信簡化簡化再簡化。那是什么程度?可能就是10行代碼以內(nèi)就行了那種(ide生成的隨外)!因為那只是網(wǎng)絡(luò)通信,而通信,僅僅就發(fā)送一條信息,對方收到了,顯示出來。可以算了吧?來看看:
首先,窗口加載過程,我們寫上:
'設(shè)置了第一個winsock控件進(jìn)入等待
winsock1.localport = 5052
winsock1.listen
'再來把第二個winsock控件連向第一個
winsock2.connect "127.0.0.1", 5052
好了,這時winsock1控件的connectionrequest事件觸發(fā)。我們寫上:
if winsock1.state <> sckclosed then winsock1.close
winsock1.accept requestid '接受連接
就這樣就連上啦!簡單得很。再來:
winsock1.senddata text1.text '把text1中的文本傳給對方
當(dāng)然了,你傳了數(shù)據(jù)給winsock2,那它的dataarrival事件也觸發(fā)了。
dim strdat as string
winsock2.getdata strdat '取得數(shù)據(jù)
text2.text = strdat '在text2中顯示出來
至此,一個使用winsock控件,實(shí)現(xiàn)網(wǎng)絡(luò)通信程序就完成了!哈哈哈,是不是覺得上當(dāng)受騙了?這種點(diǎn)子,msdn里早有出賣了!蓋茲可真不是蓋的,竟然早早想到用msdn斷我財路!唉.難怪他比我富!
我完成任務(wù)了?沒有,我要作的是《使用winsock控件,實(shí)現(xiàn)網(wǎng)絡(luò)點(diǎn)對點(diǎn)通信》。通信實(shí)現(xiàn)了,也是一個點(diǎn)對另一個點(diǎn)。可是,在普遍認(rèn)識里,點(diǎn)對點(diǎn)并不是這么解釋的。怎么解?就是雙向通信吧!呵呵,但蓋茲始終比我富。這點(diǎn)子在msdn里又已經(jīng)出賣了!
不耐煩了吧?但要寫程序,就不能怕煩。如果你決定走這條路,那還有更煩更煩的等著你。喂,煩人,你能看得下去嗎?哈哈~!既然好點(diǎn)子全給蓋茲拿去賣了,那還有什么好說的?就如,比爾賣你一個空的數(shù)據(jù)表,可是不填上數(shù)據(jù)。它還是一個空的數(shù)據(jù)表,而不是數(shù)據(jù)表!但比爾是不能幫你填上適合你的數(shù)據(jù)的,所以,你要照著你現(xiàn)有的數(shù)據(jù)表,向空的數(shù)據(jù)表填上數(shù)據(jù),再把源數(shù)據(jù)表刪了,那它就成了一個獨(dú)一無二的數(shù)據(jù)表了!
我看你真的不耐煩了...你餓嗎?把鼠標(biāo)蒸熟吃了頂一會吧!
好,現(xiàn)在就讓我說一說,點(diǎn)對點(diǎn)通信應(yīng)用里的一個重要程序...但這次,不會再像上面那樣,列出一行一行的代碼,然后告訴你添加到那里,然后怎樣...現(xiàn)在我要就“授人以漁”的政策,堅持“編程重思想”的方針;向“由淺入深”的路線,從“理解到認(rèn)識”的哲學(xué)角度出發(fā)。解釋一下,通過winsock控件,在網(wǎng)絡(luò)上傳送文件的vb程序是怎么寫的!
正如前面的例子一樣,用winsock控件通信,得分開客戶端與服務(wù)器,即:client、server(c/s)。但大家都知道,這僅是使用tcp協(xié)議必需要上演的一幕。而在udp里,并沒有明確的把c/s分開!但這樣概念要變模糊了。這在以后的話題里,我會再作解釋。今天我們的主角是tcp。
上例中,那個循序漸進(jìn)的步驟,大家體會到嗎?沒關(guān)系,現(xiàn)在來回顧一下。
服務(wù)器監(jiān)聽5052端口。
客端連接到服務(wù)器的5052端口,就是服務(wù)器程序監(jiān)聽的端口了!
由于有客戶連接,服務(wù)器程序那的winsock控件就觸發(fā)connectionrequest事件(說明見msdn)。
服務(wù)器接受了連接,客端程序的winsock控件會觸發(fā)connect事件,以表示連接上(例省略)。
連接上后,由服務(wù)器程序向客戶端程序發(fā)送數(shù)據(jù)。
數(shù)據(jù)到達(dá)客端,winsock控件觸發(fā)起客端程序的dataarrival事件提示,取得數(shù)據(jù),顯示!
整個流程就這樣,顯然很簡單了,不對嗎?呵呵!然后,我們得到這么一個流程表后。就要開始填空了。打開我們想發(fā)送的文件讀取數(shù)據(jù)給服務(wù)器發(fā)送。對方收到數(shù)據(jù)后,再寫回到文件!過程就這么個樣了!具體實(shí)現(xiàn)起來,還得要思考思考。好,現(xiàn)在我們就從一個用戶的角度出發(fā)去想吧!
兩個winsock怎么連上我就不說了,反正蓋茲都已經(jīng)比我富!那我們要傳文件,先得讓用戶選擇一個文件吧。然后就開始傳啰!好傳,慢著!客端怎么知道服務(wù)器要傳的是什么類型的文件呢?好!有了這個想法,我們在用戶點(diǎn)擊服務(wù)器程序傳送文件按扭的時候,獲取剛才用戶選擇的文件的名字吧。要傳過去了?還是不行,如果我們這個程序是聊天+傳文件的話,那不是很混亂了?嗯,給這個信息一個名字吧!就叫:“send”怎樣?好,是個很有霸氣的名字~大家用過qq之類的聊天軟件傳文件都知道,qq會先詢問接收方是否接收xxxx.xxx的文件!那我們就模仿一下吧,把傳送命令和文件名設(shè)置成:"send 文件名",這樣的一個格式。然后用winsock的senddata方法傳給對方。
那就去了客戶端編寫代碼啰:
好,當(dāng)收到這條命令,我們從前4個字得出,對方要傳文件過來了!也沒什么好寫的,就彈個msgbox出來說,對方要傳xxxx.xxx給你,你愿意接收嗎?好,當(dāng)用戶把保存路徑確定下來了,然后在這樣的路徑里,創(chuàng)建一個這樣的文件,以備接收到數(shù)據(jù)時寫入。我們現(xiàn)在就得返回一條信息給服務(wù)器那,就叫:"iliefly "吧!表示確認(rèn)接收。
又轉(zhuǎn)回來服務(wù)器那寫代碼了:
收到接收確認(rèn)信息,我們開始進(jìn)行讀文件操作了!把文件打開,聲明一個byte數(shù)組。把打開的文件數(shù)據(jù)全讀入到這個數(shù)組里。哈哈~不就成了嗎?那傳過去試試...
客戶端開始接收到數(shù)據(jù)了:
嘿,糟糕,怎么會有數(shù)據(jù)類型錯誤呢?原來每當(dāng)有數(shù)據(jù)到達(dá)客戶端,我們在dataarrival事件寫的代碼都執(zhí)行了。還以為是命令或聊天內(nèi)容呢!不行,要給它設(shè)置一個標(biāo)記,用作記錄什么時候傳過來的數(shù)據(jù)是文件的的數(shù)據(jù)吧。用一個static的語句(見msdn)聲明一個靜態(tài)布爾的變量,當(dāng)false的時候是我們命令或聊天內(nèi)容,而true的時候就是文件數(shù)據(jù)吧!再改一改上一次在客戶端那寫的源碼,在答應(yīng)接收之后,把這個布爾變量設(shè)為true!啊?問題又出來了!這時我想到,那什么時候才能把布爾變量設(shè)回false呢?倒,還是有問題!目光集中到服務(wù)器那里去...
再回到服務(wù)器的代碼編輯框:
有什么辦法讓客戶端知道什么時候傳完呢?發(fā)送命令?不行,那里會把命令寫入到文件的。連接關(guān)閉?更加不行,我還要聊天...嗯,要是讓客戶端知道傳過去的文件長度,不就可以確定什么時候是接收完了嗎?哈哈!行動,改一改傳送文件的命令為:"send 文件名 文件長度"。哦?太多空格啦,那樣對方解釋起來相當(dāng)麻煩嘛,用instr?不行,要是文件名里有空格怎辦?不就又出錯了...還好,我們有split嘛(見msdn)。好,那得把命令用個特別的字符分開了!就vbnullchar吧(見msdn)!把命令改成:["send" & vbnullchar & 文件名 & vbnullchar & 文件長度]。傳給對方。(注:filelen函數(shù))
客戶端要修改代碼了:
聲明一個動態(tài)字串?dāng)?shù)組,把split函數(shù)的返回值附給它,哈哈!下標(biāo)0是命令,下標(biāo)1是文件名,下標(biāo)2是文件長度!爽了~把長度用模塊域變量保存著,以備計算什么時候收完吧!(注:val函數(shù))
繼續(xù)在客戶端那寫我們沒完的代碼:
布爾變量為true,有數(shù)據(jù)到達(dá)。那就取得它的數(shù)據(jù)吧!可要注意接收到的數(shù)據(jù)類型哦,對面?zhèn)鬟^來的不是字符串了,我們要指定類型為"vbarray + vbbyte"(見msnd關(guān)于getdata的說明),意思就是字節(jié)數(shù)組啦。當(dāng)然,我們要定義一個字節(jié)數(shù)組才能接收嘛 dim bytearray() as byte。好,那剛才保存下來的長度就有用啦,總長度-當(dāng)次收到的字節(jié)數(shù)=剩余字節(jié)數(shù)。當(dāng)剩余字節(jié)數(shù)為0的時候,不就接收完了嗎?哈哈!不急,先來組織一下思路:數(shù)據(jù)到達(dá),取得,保存到字節(jié)數(shù)組。用ubound函數(shù)(見msdn)得到字節(jié)數(shù)組的最大下標(biāo),但由于數(shù)組下標(biāo)0也存著數(shù)據(jù),但文件不是這么算啊,所以要算就得+1算!總長度 = 總長度 - ubound(bytearray) + 1 嘿,行了!
趕快運(yùn)行試試,找個50多mb的傳。呵呵,突然,鐺!“內(nèi)存不足”暈!搞什么鬼啊,再看一下進(jìn)程列表,哇!我的程序占用了60mb左右的內(nèi)存!暈,什么回事啊?是不是那里出錯了?再重組一下思路:選擇文件,發(fā)送命令,當(dāng)收到返回確認(rèn)信息,打開文件,讀...讀???不會吧,我把文件整個讀入內(nèi)存了,難怪難怪(其實(shí)不會發(fā)生這個錯誤的,因為windows有虛擬內(nèi)存嘛!但的確,要把整個文件讀入內(nèi)存再發(fā)送出去,有點(diǎn)讓系統(tǒng)難為啊)!得優(yōu)化優(yōu)化了。
回到服務(wù)器代碼里進(jìn)行優(yōu)化:
不能一次讀入這么多,那...分開來吧!一次傳送:8192個字節(jié)如何?好!就這樣吧。但不是每個文件的字節(jié)數(shù)都為8192的倍數(shù),怎辦呢?嗯,作個計算。之前,我們不是得到了要傳的文件的長度?既然對方都可以計算什么時候收完,那我也可以計算什么時候傳完嘛!首先,確定一下,要傳的文件長度是不是比8192大。如果是,那我們就把文件長度-8192。然后定義個下標(biāo)是8191的字節(jié)數(shù)組(因為數(shù)組下標(biāo)0也可以存數(shù)據(jù)嘛,所以要-1),用get方法,讀取打開的文件數(shù)據(jù)來填充這個字節(jié)數(shù)組。然后傳過去...嗯?對方好像會收了啊,未達(dá)到文件總長度時,客戶端不會把布爾數(shù)組設(shè)回來的。但,這邊怎么知道什么時候可以傳下一筆數(shù)據(jù)呢?嗯~sendcomplete 事件——在完成一個發(fā)送操作時出現(xiàn)。好,就是這個了!我們在這邊也用文件長度為0時作為結(jié)束傳送的條件。如果不為0,那就再看看還沒傳的文件剩下多少,因為上面的計算已經(jīng)-去了上次傳送的字節(jié)了,那現(xiàn)在的文件長度變量就是剩下字節(jié)的數(shù)量啦,再比較一下,是否比8192大。哈哈~不是就定義一個這么大小的字節(jié)數(shù)組,讀文件,送出!完成~[msgbox "傳輸完畢!", vbinformation, "⊙_⌒γ - server"]...
但突然發(fā)現(xiàn),原來sendcomplete事件,在我們把傳文件命令發(fā)出去之后也會觸發(fā)。那沒法子了,只好再定義一個布爾變量來確定什么時候是傳文件吧...
至此,一個用winsock控件傳送文件的vb程序?qū)懲炅耍『呛牵瑳]睡著吧?不過我快睡著了!好困,上一次睡覺的時間只有3小時!現(xiàn)在-_-#了...但得說明一下,上面提到的8192,并不是我隨便安上去的。這個在以后再作詳細(xì)說明吧!
嗯,要做的事還沒完!好像我沒說為什么:“blog出現(xiàn)在csdn,也就blog將不blog也”哈哈~其實(shí)這篇東東已經(jīng)說得很清楚了!