版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、C#網(wǎng)絡(luò)編程(訂立協(xié)議和發(fā)送文件) - Part.4文件傳輸前面兩篇文章所使用的范例都是傳輸字符串,有的時(shí)候我們可能會(huì)想在服務(wù)端和客戶端之間傳遞文件。比如,考慮這樣一種情況,假如客戶端顯示了一個(gè)菜單,當(dāng)我們輸入S1、S2或S3(S為Send縮寫)時(shí),分別向服務(wù)端發(fā)送文件Client01.jpg、Client02.jpg、Client03.jpg;當(dāng)我們輸入R1、R2或R3時(shí)(R為Receive縮寫),則分別從服務(wù)端接收文件Server01.jpg、Server02.jpg、Server03.jpg。那么,我們?cè)撊绾瓮瓿蛇@件事呢?此時(shí)可能有這樣兩種做法: 類似于FTP協(xié)議,服務(wù)端開辟兩個(gè)端口,并
2、持續(xù)對(duì)這兩個(gè)端口偵聽:一個(gè)用于接收字符串,類似于FTP的控制端口,它接收各種命令(接收或發(fā)送文件);一個(gè)用于傳輸數(shù)據(jù),也就是發(fā)送和接收文件。 服務(wù)端只開辟一個(gè)端口,用于接收字符串,我們稱之為控制端口。當(dāng)接到請(qǐng)求之后,根據(jù)請(qǐng)求內(nèi)容在客戶端開辟一個(gè)端口專用于文件傳輸,并在傳輸結(jié)束后關(guān)閉端口。 現(xiàn)在我們只關(guān)注于上面的數(shù)據(jù)端口,回憶一下在第二篇中我們所總結(jié)的,可以得出:當(dāng)我們使用上面的方法一時(shí),服務(wù)端的數(shù)據(jù)端口可以為多個(gè)客戶端的多次請(qǐng)求服務(wù);當(dāng)我們使用方法二時(shí),服務(wù)端只為一個(gè)客戶端的一次請(qǐng)求服務(wù),但是因?yàn)槊看握?qǐng)求都會(huì)重新開辟端口,所以實(shí)際上還是相當(dāng)于可以為多個(gè)客戶端的多次請(qǐng)求服務(wù)。同時(shí),因?yàn)樗粸橐淮?/p>
3、請(qǐng)求服務(wù),所以我們?cè)跀?shù)據(jù)端口上傳輸文件時(shí)無(wú)需采用異步傳輸方式。但在控制端口我們?nèi)匀恍枰褂卯惒椒绞?。從上面看出,第一種方式要好得多,但是我們將采用第二種方式。至于原因,你可以回顧一下Part.1(基本概念和操作)中關(guān)于聊天程序模式的講述,因?yàn)榻酉聛?lái)一篇文章我們將創(chuàng)建一個(gè)聊天程序,而這個(gè)聊天程序采用第三種模式,所以本文的練習(xí)實(shí)際是對(duì)下一篇的一個(gè)鋪墊。1.訂立協(xié)議1.1發(fā)送文件我們先看一下發(fā)送文件的情況,如果我們想將文件client01.jpg由客戶端發(fā)往客戶端,那么流程是什么:1. 客戶端開辟數(shù)據(jù)端口用于偵聽,并獲取端口號(hào),假設(shè)為8005。 2. 假設(shè)客戶端輸入了S1,則發(fā)送下面的控制字符串到服
4、務(wù)端:file=Client01.jpg, mode=send, port=8005。 3. 服務(wù)端收到以后,根據(jù)客戶端ip和端口號(hào)與該客戶端建立連接。 4. 客戶端偵聽到服務(wù)端的連接,開始發(fā)送文件。 5. 傳送完畢后客戶端、服務(wù)端分別關(guān)閉連接。 此時(shí),我們訂立的發(fā)送文件協(xié)議為:file=Client01.jpg, mode=send, port=8005。但是,由于它是一個(gè)普通的字符串,在上一篇中,我們采用了正則表達(dá)式來(lái)獲取其中的有效值,但這顯然不是一種好辦法。因此,在本文及下一篇文章中,我們采用一種新的方式來(lái)編寫協(xié)議:XML。對(duì)于上面的語(yǔ)句,我們可以寫成這樣的XML:這樣我們?cè)诜?wù)端就會(huì)好
5、處理得多,接下來(lái)我們來(lái)看一下接收文件的流程及其協(xié)議。NOTE:這里說(shuō)發(fā)送、接收文件是站在客戶端的立場(chǎng)說(shuō)的,當(dāng)客戶端發(fā)送文件時(shí),對(duì)于服務(wù)器來(lái)收,則是接收文件。1.2接收文件接收文件與發(fā)送文件實(shí)際上完全類似,區(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和端口號(hào)與該客戶端建立連接。 4. 客戶端建立起與服務(wù)端的連接,服務(wù)端開始網(wǎng)絡(luò)流中寫入數(shù)據(jù)。 5. 傳送完畢后服務(wù)端、客戶端分別關(guān)閉連接。 2.協(xié)議處理類的實(shí)現(xiàn)和上面一章一樣,在開始編寫
6、實(shí)際的服務(wù)端客戶端代碼之前,我們首先要編寫處理協(xié)議的類,它需要提供這樣兩個(gè)功能:1、方便地幫我們獲取完整的協(xié)議信息,因?yàn)榍懊嫖覀冋f(shuō)過(guò),服務(wù)端可能將客戶端的多次獨(dú)立請(qǐng)求拆分或合并。比如,客戶端連續(xù)發(fā)送了兩條控制信息到服務(wù)端,而服務(wù)端將它們合并了,那么則需要先拆開再分別處理。2、方便地獲取我們所想要的屬性信息,因?yàn)閰f(xié)議是XML格式,所以還需要一個(gè)類專門對(duì)XML進(jìn)行處理,獲得字符串的屬性值。2.1 ProtocalHandler輔助類我們先看下ProtocalHandler,它與上一篇中的RequestHandler作用相同。需要注意的是必須將它聲明為實(shí)例的,而非靜態(tài)的,這是因?yàn)槊總€(gè)TcpClien
7、t都需要對(duì)應(yīng)一個(gè)ProtocalHandler,因?yàn)樗鼉?nèi)部維護(hù)的patialProtocal不能共享,在協(xié)議發(fā)送不完整的情況下,這個(gè)變量用于臨時(shí)保存被截?cái)嗟淖址?。public class ProtocolHandler private string partialProtocal; / 保存不完整的協(xié)議 public ProtocolHandler() partialProtocal = ; public string GetProtocol(string input) return GetProtocol(input, null); / 獲得協(xié)議 private string GetPro
8、tocol(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 = (.*?); / 如果有匹配,說(shuō)明已經(jīng)找到了,是完整的協(xié)議 if (Regex.IsMatch(input, patter
9、n) / 獲取匹配的值 string match = Regex.Match(input, pattern).Groups0.Value; outputList.Add(match); partialProtocal = ; / 縮短input的長(zhǎng)度 input = input.Substring(match.Length); / 遞歸調(diào)用 GetProtocol(input, outputList); else / 如果不匹配,說(shuō)明協(xié)議的長(zhǎng)度不夠, / 那么先緩存,然后等待下一次請(qǐng)求 partialProtocal = input; return outputList.ToArray();
10、因?yàn)楝F(xiàn)在它已經(jīng)不是本文的重點(diǎn)了,所以我就不演示對(duì)于它的測(cè)試了,本文所附帶的代碼中含有它的測(cè)試代碼(我在ProtocolHandler中添加了一個(gè)靜態(tài)類Test())。2.2 FileRequestType枚舉和FileProtocol結(jié)構(gòu)因?yàn)閄ML是以字符串的形式在進(jìn)行傳輸,為了方便使用,我們最好構(gòu)建一個(gè)強(qiáng)類型來(lái)對(duì)它們進(jìn)行操作,這樣會(huì)方便很多。我們首先可以定義FileRequestMode枚舉,它代表是發(fā)送還是接收文件:public enum FileRequestMode Send = 0, Receive接下來(lái)我們?cè)俣x一個(gè)FileProtocol結(jié)構(gòu),用來(lái)為整個(gè)協(xié)議字符串提供強(qiáng)類型的訪問(wèn)
11、,注意這里覆蓋了基類的ToString()方法,這樣在客戶端我們就不需要再手工去編寫XML,只要在結(jié)構(gòu)值上調(diào)用ToString()就OK了,會(huì)方便很多。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 = mode; this.port
12、 = 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輔助類這個(gè)類專用于將XML格式的協(xié)議映射為我們上面定義的強(qiáng)類型對(duì)象,這里我沒(méi)有加入try/
13、catch異常處理,因?yàn)閰f(xié)議對(duì)用戶來(lái)說(shuō)是不可見的,而且客戶端應(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.SelectSingleNode(file);
14、/ 此時(shí)的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() FileRequestMode mode = GetFileMode
15、(); string fileName = ; int port = 0; fileName = fileNode.Attributesname.Value; port = Convert.ToInt32(fileNode.Attributesport.Value); return new FileProtocol(mode, port, fileName); OK,我們又耽誤了點(diǎn)時(shí)間,下面就讓我們進(jìn)入正題吧。3.客戶端發(fā)送數(shù)據(jù)3.1 服務(wù)端的實(shí)現(xiàn)我們還是將一個(gè)問(wèn)題分成兩部分來(lái)處理,先是發(fā)送數(shù)據(jù),然后是接收數(shù)據(jù)。我們先看發(fā)送數(shù)據(jù)部分的服務(wù)端。如果你從第一篇文章看到了現(xiàn)在,那么我覺得更多的不是技
16、術(shù)上的問(wèn)題而是思路,所以我們不再將重點(diǎn)放到代碼上,這些應(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(); / 開啟對(duì)控制端口 8500 的偵聽 Console.WriteLine(Start Listening .); while (true
17、) / 獲取一個(gè)連接,同步方法,在此處中斷 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 byte buffer; private Protoco
18、lHandler handler; public RemoteClient(TcpClient client) this.client = client; / 打印連接到的客戶端信息 Console.WriteLine(nClient Connected!0 0, endpoint); return; / 獲取發(fā)送文件的流 NetworkStream streamToClient = localClient.GetStream(); / 隨機(jī)生成一個(gè)在當(dāng)前目錄下的文件名稱 string path = Environment.CurrentDirectory + / + generateFile
19、Name(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(buffer, 0, bytesRead); totalBytes +
20、= bytesRead; Console.WriteLine(Receiving 0 bytes ., totalBytes); while (bytesRead 0); Console.WriteLine(Total 0 bytes received, Done!, totalBytes); streamToClient.Dispose(); fs.Dispose(); localClient.Close(); / 隨機(jī)獲取一個(gè)圖片名稱 private string generateFileName(string fileName) DateTime now = DateTime.Now;
21、return String.Format( 0_1_2_3, now.Minute, now.Second, now.Millisecond, fileName ); 這里應(yīng)該沒(méi)有什么新知識(shí),需要注意的地方有這么幾個(gè): 在OnReadComplete()回調(diào)方法中的foreach循環(huán),我們使用委托異步調(diào)用了handleProtocol()方法,這是因?yàn)閔andleProtocol即將執(zhí)行的是一個(gè)讀取或接收文件的操作,也就是一個(gè)相對(duì)耗時(shí)的操作。 在handleProtocol()方法中,我們深切體會(huì)了定義ProtocolHelper類和FileProtocol結(jié)構(gòu)的好處。如果沒(méi)有定義它們,這里將
22、是不堪入目的處理XML以及類型轉(zhuǎn)換的代碼。 handleProtocol()方法中進(jìn)行了一個(gè)條件判斷,注意sendFile()方法我屏蔽掉了,這個(gè)還沒(méi)有實(shí)現(xiàn),但是我想你已經(jīng)猜到它將是后面要實(shí)現(xiàn)的內(nèi)容。 receiveFile()方法是實(shí)際接收客戶端發(fā)來(lái)文件的方法,這里沒(méi)有什么特別之處。需要注意的是文件存儲(chǔ)的路徑,它保存在了當(dāng)前程序執(zhí)行的目錄下,文件的名稱我使用generateFileName()生成了一個(gè)與時(shí)間有關(guān)的隨機(jī)名稱。 3.2客戶端的實(shí)現(xiàn)我們現(xiàn)在先不著急實(shí)現(xiàn)客戶端S1、R1等用戶菜單,首先完成發(fā)送文件這一功能,實(shí)際上,就是為上一節(jié)SendMessage()加一個(gè)姐妹方法SendFile
23、()。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 = Console.ReadKey(true).Key; while
24、(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ù)器連接 catch (Exception ex) Console.WriteL
25、ine(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 temp = Encoding.Unicode.GetBytes(msg); /
26、獲得緩存 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 start = new ParameterizedThreadStart(Begin
27、SendFile); 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 listener = new TcpListener(ip, 0); listener.Start
28、(); / 獲取本地偵聽的端口號(hào) 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); string pro = protocol.ToString(); SendMe
29、ssage(pro); / 發(fā)送協(xié)議到服務(wù)端 / 中斷,等待遠(yuǎn)程連接 TcpClient localClient = listener.AcceptTcpClient(); Console.WriteLine(Start sending file.); NetworkStream stream = localClient.GetStream(); / 創(chuàng)建文件流 FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); byte fileBuffer = new byte1024; / 每次傳1KB in
30、t 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); totalBytes += bytesRead; / 發(fā)送了的字節(jié)數(shù) status.PrintStat
31、us(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(); 接下來(lái)我們來(lái)看下這段代碼,有這么兩點(diǎn)需要注意一下: 在Main()方法中可以看到,圖片的位置為應(yīng)用程序所在的目錄,如果你跟我一樣處于調(diào)試模式,那么就在解決方案的Bin目錄下的Debug目錄中放置三張圖片Client01.jpg、Client02.jpg、Client03.jpg,用來(lái)發(fā)往服務(wù)端。 我在客戶端提供了兩個(gè)SendFile()方法,和一個(gè)BeginSendFile()方法,分別用于同步和異步傳輸,其中私有的SendFile()方法只是一個(gè)輔助方法。實(shí)際上對(duì)于發(fā)送文件這樣的操作我們幾乎總是需要使用異步操作。 SendMessage()方法中給streamToServer加鎖很重要,因?yàn)镾endFile()方法是多線程訪問(wèn)的,而在SendF
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025官地引水發(fā)電合同條件
- 2025住房公積金合同模板
- 碼頭工程施工組織設(shè)計(jì)
- 榜樣報(bào)告心得體會(huì)(10篇)
- 科技醫(yī)療下的新突破-尿檢血檢在慢性病管理中的應(yīng)用研究
- 課題申報(bào)參考:馬克思主義經(jīng)典作家文化理論研究
- 課題申報(bào)參考:考慮質(zhì)量信息披露的退役動(dòng)力電池梯級(jí)利用與再生利用運(yùn)營(yíng)決策研究
- 2024年硬質(zhì)合金噴焊粉項(xiàng)目資金需求報(bào)告
- 未來(lái)工控網(wǎng)絡(luò)的多元化發(fā)展趨勢(shì)及機(jī)遇挑戰(zhàn)
- 網(wǎng)絡(luò)安全在學(xué)校商業(yè)活動(dòng)中的保障
- 2025-2030年中國(guó)陶瓷電容器行業(yè)運(yùn)營(yíng)狀況與發(fā)展前景分析報(bào)告
- 2025年山西國(guó)際能源集團(tuán)限公司所屬企業(yè)招聘43人高頻重點(diǎn)提升(共500題)附帶答案詳解
- 二零二五年倉(cāng)儲(chǔ)配送中心物業(yè)管理與優(yōu)化升級(jí)合同3篇
- 2025屆廈門高三1月質(zhì)檢期末聯(lián)考數(shù)學(xué)答案
- 音樂(lè)作品錄制許可
- 江蘇省無(wú)錫市2023-2024學(xué)年高三上學(xué)期期終教學(xué)質(zhì)量調(diào)研測(cè)試語(yǔ)文試題(解析版)
- 拉薩市2025屆高三第一次聯(lián)考(一模)英語(yǔ)試卷(含答案解析)
- 開題報(bào)告:AIGC背景下大學(xué)英語(yǔ)教學(xué)設(shè)計(jì)重構(gòu)研究
- 師德標(biāo)兵先進(jìn)事跡材料師德標(biāo)兵個(gè)人主要事跡
- 連鎖商務(wù)酒店述職報(bào)告
- 2024年山東省煙臺(tái)市初中學(xué)業(yè)水平考試地理試卷含答案
評(píng)論
0/150
提交評(píng)論