C#網(wǎng)絡(luò)編程-4_第1頁
C#網(wǎng)絡(luò)編程-4_第2頁
C#網(wǎng)絡(luò)編程-4_第3頁
C#網(wǎng)絡(luò)編程-4_第4頁
C#網(wǎng)絡(luò)編程-4_第5頁
已閱讀5頁,還剩27頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、.c#網(wǎng)絡(luò)編程(訂立協(xié)議和發(fā)送文件) - part.4文件傳輸前面兩篇文章所使用的范例都是傳輸字符串,有的時候我們可能會想在服務(wù)端和客戶端之間傳遞文件。比如,考慮這樣一種情況,假如客戶端顯示了一個菜單,當我們輸入s1、s2或s3(s為send縮寫)時,分別向服務(wù)端發(fā)送文件client01.jpg、client02.jpg、client03.jpg;當我們輸入r1、r2或r3時(r為receive縮寫),則分別從服務(wù)端接收文件server01.jpg、server02.jpg、server03.jpg。那么,我們該如何完成這件事呢?此時可能有這樣兩種做法: 類似于ftp協(xié)議,服務(wù)端開辟兩個端口,

2、并持續(xù)對這兩個端口偵聽:一個用于接收字符串,類似于ftp的控制端口,它接收各種命令(接收或發(fā)送文件);一個用于傳輸數(shù)據(jù),也就是發(fā)送和接收文件。 服務(wù)端只開辟一個端口,用于接收字符串,我們稱之為控制端口。當接到請求之后,根據(jù)請求內(nèi)容在客戶端開辟一個端口專用于文件傳輸,并在傳輸結(jié)束后關(guān)閉端口。 現(xiàn)在我們只關(guān)注于上面的數(shù)據(jù)端口,回憶一下在第二篇中我們所總結(jié)的,可以得出:當我們使用上面的方法一時,服務(wù)端的數(shù)據(jù)端口可以為多個客戶端的多次請求服務(wù);當我們使用方法二時,服務(wù)端只為一個客戶端的一次請求服務(wù),但是因為每次請求都會重新開辟端口,所以實際上還是相當于可以為多個客戶端的多次請求服務(wù)。同時,因為它只為一

3、次請求服務(wù),所以我們在數(shù)據(jù)端口上傳輸文件時無需采用異步傳輸方式。但在控制端口我們?nèi)匀恍枰褂卯惒椒绞?。從上面看出,第一種方式要好得多,但是我們將采用第二種方式。至于原因,你可以回顧一下part.1(基本概念和操作)中關(guān)于聊天程序模式的講述,因為接下來一篇文章我們將創(chuàng)建一個聊天程序,而這個聊天程序采用第三種模式,所以本文的練習(xí)實際是對下一篇的一個鋪墊。精品.1.訂立協(xié)議1.1發(fā)送文件我們先看一下發(fā)送文件的情況,如果我們想將文件client01.jpg由客戶端發(fā)往客戶端,那么流程是什么:1. 客戶端開辟數(shù)據(jù)端口用于偵聽,并獲取端口號,假設(shè)為8005。 2. 假設(shè)客戶端輸入了s1,則發(fā)送下面的控制字

4、符串到服務(wù)端:file=client01.jpg, mode=send, port=8005。 3. 服務(wù)端收到以后,根據(jù)客戶端ip和端口號與該客戶端建立連接。 4. 客戶端偵聽到服務(wù)端的連接,開始發(fā)送文件。 5. 傳送完畢后客戶端、服務(wù)端分別關(guān)閉連接。 此時,我們訂立的發(fā)送文件協(xié)議為:file=client01.jpg, mode=send, port=8005。但是,由于它是一個普通的字符串,在上一篇中,我們采用了正則表達式來獲取其中的有效值,但這顯然不是一種好辦法。因此,在本文及下一篇文章中,我們采用一種新的方式來編寫協(xié)議:xml。對于上面的語句,我們可以寫成這樣的xml:這樣我們在服務(wù)

5、端就會好處理得多,接下來我們來看一下接收文件的流程及其協(xié)議。note:這里說發(fā)送、接收文件是站在客戶端的立場說的,當客戶端發(fā)送文件時,對于服務(wù)器來收,則是接收文件。1.2接收文件接收文件與發(fā)送文件實際上完全類似,區(qū)別只是由客戶端向網(wǎng)絡(luò)流寫入數(shù)據(jù),還是由服務(wù)端向網(wǎng)絡(luò)流寫入數(shù)據(jù)。1. 客戶端開辟數(shù)據(jù)端口用于偵聽,假設(shè)為8006。 精品.2. 假設(shè)客戶端輸入了r1,則發(fā)送控制字符串:到服務(wù)端。 3. 服務(wù)端收到以后,根據(jù)客戶端ip和端口號與該客戶端建立連接。 4. 客戶端建立起與服務(wù)端的連接,服務(wù)端開始網(wǎng)絡(luò)流中寫入數(shù)據(jù)。 5. 傳送完畢后服務(wù)端、客戶端分別關(guān)閉連接。 2.協(xié)議處理類的實現(xiàn)和上面一章一

6、樣,在開始編寫實際的服務(wù)端客戶端代碼之前,我們首先要編寫處理協(xié)議的類,它需要提供這樣兩個功能:1、方便地幫我們獲取完整的協(xié)議信息,因為前面我們說過,服務(wù)端可能將客戶端的多次獨立請求拆分或合并。比如,客戶端連續(xù)發(fā)送了兩條控制信息到服務(wù)端,而服務(wù)端將它們合并了,那么則需要先拆開再分別處理。2、方便地獲取我們所想要的屬性信息,因為協(xié)議是xml格式,所以還需要一個類專門對xml進行處理,獲得字符串的屬性值。2.1 protocalhandler輔助類我們先看下protocalhandler,它與上一篇中的requesthandler作用相同。需要注意的是必須將它聲明為實例的,而非靜態(tài)的,這是因為每個t

7、cpclient都需要對應(yīng)一個protocalhandler,因為它內(nèi)部維護的patialprotocal不能共享,在協(xié)議發(fā)送不完整的情況下,這個變量用于臨時保存被截斷的字符串。public class protocolhandler private string partialprotocal; / 保存不完整的協(xié)議 public protocolhandler() partialprotocal = ; 精品. public string getprotocol(string input) return getprotocol(input, null); / 獲得協(xié)議 private st

8、ring getprotocol(string input, list outputlist) if (outputlist = null) outputlist = new list(); if (string.isnullorempty(input) return outputlist.toarray(); if (!string.isnullorempty(partialprotocal) input = partialprotocal + input; string pattern = (.*?); / 如果有匹配,說明已經(jīng)找到了,是完整的協(xié)議 if (regex.ismatch(in

9、put, pattern) / 獲取匹配的值 string match = regex.match(input, pattern).groups0.value; outputlist.add(match); partialprotocal = ; / 縮短input的長度 input = input.substring(match.length);精品. / 遞歸調(diào)用 getprotocol(input, outputlist); else / 如果不匹配,說明協(xié)議的長度不夠, / 那么先緩存,然后等待下一次請求 partialprotocal = input; return outputli

10、st.toarray(); 因為現(xiàn)在它已經(jīng)不是本文的重點了,所以我就不演示對于它的測試了,本文所附帶的代碼中含有它的測試代碼(我在protocolhandler中添加了一個靜態(tài)類test())。2.2 filerequesttype枚舉和fileprotocol結(jié)構(gòu)因為xml是以字符串的形式在進行傳輸,為了方便使用,我們最好構(gòu)建一個強類型來對它們進行操作,這樣會方便很多。我們首先可以定義filerequestmode枚舉,它代表是發(fā)送還是接收文件:public enum filerequestmode send = 0, receive接下來我們再定義一個fileprotocol結(jié)構(gòu),用來為整

11、個協(xié)議字符串提供強類型的訪問,注意這里覆蓋了基類的tostring()方法,這樣在客戶端我們就不需要再手工去編寫xml,只要在結(jié)構(gòu)值上調(diào)用tostring()就ok了,會方便很多。精品.public struct fileprotocol private readonly filerequestmode mode; private readonly int port; private readonly string filename; public fileprotocol (filerequestmode mode, int port, string filename) this.mode

12、= mode; this.port = port; this.filename = filename; public filerequestmode mode get return mode; public int port get return port; public string filename get return filename; public override string tostring() return string.format(, filename, mode, port);精品. 2.3 protocolhelper輔助類這個類專用于將xml格式的協(xié)議映射為我們上面

13、定義的強類型對象,這里我沒有加入try/catch異常處理,因為協(xié)議對用戶來說是不可見的,而且客戶端應(yīng)該總是發(fā)送正確的協(xié)議,我覺得這樣可以讓代碼更加清晰:public class protocolhelper private xmlnode filenode; private xmlnode root; public protocolhelper(string protocol) xmldocument doc = new xmldocument(); doc.loadxml(protocol); root = doc.documentelement; filenode = root.sele

14、ctsinglenode(file); / 此時的protocal一定為單條完整protocal private filerequestmode getfilemode() string mode = filenode.attributesmode.value; mode = mode.tolower(); if (mode = send) return filerequestmode.send; else return filerequestmode.receive;精品. / 獲取單條協(xié)議包含的信息 public fileprotocol getprotocol() filerequest

15、mode mode = getfilemode(); string filename = ; int port = 0; filename = filenode.attributesname.value; port = convert.toint32(filenode.attributesport.value); return new fileprotocol(mode, port, filename); ok,我們又耽誤了點時間,下面就讓我們進入正題吧。3.客戶端發(fā)送數(shù)據(jù)3.1 服務(wù)端的實現(xiàn)我們還是將一個問題分成兩部分來處理,先是發(fā)送數(shù)據(jù),然后是接收數(shù)據(jù)。我們先看發(fā)送數(shù)據(jù)部分的服務(wù)端。如果你

16、從第一篇文章看到了現(xiàn)在,那么我覺得更多的不是技術(shù)上的問題而是思路,所以我們不再將重點放到代碼上,這些應(yīng)該很容易就看懂了。class server static void main(string args) console.writeline(server is running . ); ipaddress ip = ipaddress.parse();精品. tcplistener listener = new tcplistener(ip, 8500); listener.start(); / 開啟對控制端口 8500 的偵聽 console.writeline(start

17、 listening .); while (true) / 獲取一個連接,同步方法,在此處中斷 tcpclient client = listener.accepttcpclient(); remoteclient wapper = new remoteclient(client); wapper.beginread(); public class remoteclient private tcpclient client; private networkstream streamtoclient; private const int buffersize = 8192; private by

18、te buffer; private protocolhandler handler; public remoteclient(tcpclient client) this.client = client; / 打印連接到的客戶端信息 console.writeline(nclient connected!0 0, endpoint); return; / 獲取發(fā)送文件的流 networkstream streamtoclient = localclient.getstream(); / 隨機生成一個在當前目錄下的文件名稱 string path = 精品. environment.curre

19、ntdirectory + / + generatefilename(protocol.filename); byte filebuffer = new byte1024; / 每次收1kb filestream fs = new filestream(path, filemode.createnew, fileaccess.write); / 從緩存buffer中讀入到文件流中 int bytesread; int totalbytes = 0; do bytesread = streamtoclient.read(buffer, 0, buffersize); fs.write(buffe

20、r, 0, bytesread); totalbytes += bytesread; console.writeline(receiving 0 bytes ., totalbytes); while (bytesread 0); console.writeline(total 0 bytes received, done!, totalbytes); streamtoclient.dispose(); fs.dispose(); localclient.close(); / 隨機獲取一個圖片名稱 private string generatefilename(string filename)

21、 datetime now = datetime.now; return string.format(精品. 0_1_2_3, now.minute, now.second, now.millisecond, filename ); 這里應(yīng)該沒有什么新知識,需要注意的地方有這么幾個: 在onreadcomplete()回調(diào)方法中的foreach循環(huán),我們使用委托異步調(diào)用了handleprotocol()方法,這是因為handleprotocol即將執(zhí)行的是一個讀取或接收文件的操作,也就是一個相對耗時的操作。 在handleprotocol()方法中,我們深切體會了定義protocolhelpe

22、r類和fileprotocol結(jié)構(gòu)的好處。如果沒有定義它們,這里將是不堪入目的處理xml以及類型轉(zhuǎn)換的代碼。 handleprotocol()方法中進行了一個條件判斷,注意sendfile()方法我屏蔽掉了,這個還沒有實現(xiàn),但是我想你已經(jīng)猜到它將是后面要實現(xiàn)的內(nèi)容。 receivefile()方法是實際接收客戶端發(fā)來文件的方法,這里沒有什么特別之處。需要注意的是文件存儲的路徑,它保存在了當前程序執(zhí)行的目錄下,文件的名稱我使用generatefilename()生成了一個與時間有關(guān)的隨機名稱。 3.2客戶端的實現(xiàn)我們現(xiàn)在先不著急實現(xiàn)客戶端s1、r1等用戶菜單,首先完成發(fā)送文件這一功能,實際上,就

23、是為上一節(jié)sendmessage()加一個姐妹方法sendfile()。class client static void main(string args) consolekey key;精品. serverclient client = new serverclient(); string filepath = environment.currentdirectory + / + client01.jpg; if(file.exists(filepath) client.beginsendfile(filepath); console.writeline(nn輸入q鍵退出。); do key

24、 = console.readkey(true).key; while (key != consolekey.q); public class serverclient private const int buffersize = 8192; private byte buffer; private tcpclient client; private networkstream streamtoserver; public serverclient() try client = new tcpclient(); client.connect(localhost, 8500); / 與服務(wù)器連接

25、 catch (exception ex) console.writeline(ex.message); return; 精品. buffer = new bytebuffersize; / 打印連接到的服務(wù)端信息 console.writeline(server connected!0 - 1, client.client.localendpoint, client.client.remoteendpoint); streamtoserver = client.getstream(); / 發(fā)送消息到服務(wù)端 public void sendmessage(string msg) byte t

26、emp = encoding.unicode.getbytes(msg); / 獲得緩存 try lock (streamtoserver) streamtoserver.write(temp, 0, temp.length); / 發(fā)往服務(wù)器 console.writeline(sent: 0, msg); catch (exception ex) console.writeline(ex.message); return; / 發(fā)送文件 - 異步方法 public void beginsendfile(string filepath) parameterizedthreadstart st

27、art = new parameterizedthreadstart(beginsendfile); start.begininvoke(filepath, null, null);精品. private void beginsendfile(object obj) string filepath = obj as string; sendfile(filepath); / 發(fā)送文件 - 同步方法 public void sendfile(string filepath) ipaddress ip = ipaddress.parse(); tcplistener listen

28、er = new tcplistener(ip, 0); listener.start(); / 獲取本地偵聽的端口號 ipendpoint endpoint = listener.localendpoint as ipendpoint; int listeningport = endpoint.port; / 獲取發(fā)送的協(xié)議字符串 string filename = path.getfilename(filepath); fileprotocol protocol = new fileprotocol(filerequestmode.send, listeningport, filename

29、); string pro = protocol.tostring(); sendmessage(pro); / 發(fā)送協(xié)議到服務(wù)端 / 中斷,等待遠程連接 tcpclient localclient = listener.accepttcpclient();精品. console.writeline(start sending file.); networkstream stream = localclient.getstream(); / 創(chuàng)建文件流 filestream fs = new filestream(filepath, filemode.open, fileaccess.read

30、); byte filebuffer = new byte1024; / 每次傳1kb int bytesread; int totalbytes = 0; / 創(chuàng)建獲取文件發(fā)送狀態(tài)的類 sendstatus status = new sendstatus(filepath); / 將文件流轉(zhuǎn)寫入網(wǎng)絡(luò)流 try do thread.sleep(10); / 為了更好的視覺效果,暫停10毫秒:-) bytesread = fs.read(filebuffer, 0, filebuffer.length); stream.write(filebuffer, 0, bytesread); total

31、bytes += bytesread; / 發(fā)送了的字節(jié)數(shù) status.printstatus(totalbytes); / 打印發(fā)送狀態(tài) while (bytesread 0); console.writeline(total 0 bytes sent, done!, totalbytes); catch console.writeline(server has lost.); 精品. stream.dispose(); fs.dispose(); localclient.close(); listener.stop(); 接下來我們來看下這段代碼,有這么兩點需要注意一下: 在main()

32、方法中可以看到,圖片的位置為應(yīng)用程序所在的目錄,如果你跟我一樣處于調(diào)試模式,那么就在解決方案的bin目錄下的debug目錄中放置三張圖片client01.jpg、client02.jpg、client03.jpg,用來發(fā)往服務(wù)端。 我在客戶端提供了兩個sendfile()方法,和一個beginsendfile()方法,分別用于同步和異步傳輸,其中私有的sendfile()方法只是一個輔助方法。實際上對于發(fā)送文件這樣的操作我們幾乎總是需要使用異步操作。 sendmessage()方法中給streamtoserver加鎖很重要,因為sendfile()方法是多線程訪問的,而在sendfile()方法中又調(diào)用了sendmessage

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論