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頁,還剩31頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1、C#網(wǎng)絡(luò)編程(訂立協(xié)議和發(fā)送文件) - Part.4文件傳輸前面兩篇文章所使用的范例都是傳輸字符串,有的時候我們可能會想在服務(wù)端和客戶端之間傳遞文件。比如,考慮這樣一種情況,假如客戶端顯示了一個菜單,當(dāng)我們輸入S1、S2或S3(S為Send縮寫)時,分別向服務(wù)端發(fā)送文件Client01.jpg、Client02.jpg、Client03.jpg;當(dāng)我們輸入R1、R2或R3時(R為Receive縮寫),則分別從服務(wù)端接收文件Server01.jpg、Server02.jpg、Server03.jpg。那么,我們該如何完成這件事呢?此時可能有這樣兩種做法:· 類似于FTP協(xié)議,服務(wù)端開辟

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

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

4、輸入了S1,則發(fā)送下面的控制字符串到服務(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。對于上面的語句,我們可以寫

5、成這樣的XML:<protocol><file name="client01.jpg" mode="send" port="8005" /></protocol>這樣我們在服務(wù)端就會好處理得多,接下來我們來看一下接收文件的流程及其協(xié)議。NOTE:這里說發(fā)送、接收文件是站在客戶端的立場說的,當(dāng)客戶端發(fā)送文件時,對于服務(wù)器來收,則是接收文件。1.2接收文件接收文件與發(fā)送文件實際上完全類似,區(qū)別只是由客戶端向網(wǎng)絡(luò)流寫入數(shù)據(jù),還是由服務(wù)端向網(wǎng)絡(luò)流寫入數(shù)據(jù)。1. 客戶端開辟數(shù)據(jù)端口用于偵聽,假設(shè)為8006。

6、2. 假設(shè)客戶端輸入了R1,則發(fā)送控制字符串:<protocol><file name="Server01.jpg" mode="receive" port="8006" /></protocol>到服務(wù)端。 3. 服務(wù)端收到以后,根據(jù)客戶端ip和端口號與該客戶端建立連接。 4. 客戶端建立起與服務(wù)端的連接,服務(wù)端開始網(wǎng)絡(luò)流中寫入數(shù)據(jù)。 5. 傳送完畢后服務(wù)端、客戶端分別關(guān)閉連接。 2.協(xié)議處理類的實現(xiàn)和上面一章一樣,在開始編寫實際的服務(wù)端客戶端代碼之前,我們首先要編寫處理協(xié)議的類,它需要提供這樣兩

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

8、alProtocal不能共享,在協(xié)議發(fā)送不完整的情況下,這個變量用于臨時保存被截斷的字符串。public class ProtocolHandler     private string partialProtocal; / 保存不完整的協(xié)議        public ProtocolHandler()         partialProtocal = ""    

9、0;          public string GetProtocol(string input)         return GetProtocol(input, null);            / 獲得協(xié)議    private string GetProtocol(string input, List<string&

10、gt; outputList)         if (outputList = null)            outputList = new List<string>();        if (String.IsNullOrEmpty(input)      

11、0;     return outputList.ToArray();        if (!String.IsNullOrEmpty(partialProtocal)            input = partialProtocal + input;        string pattern = &

12、quot;(<protocol>.*?</protocol>)"        / 如果有匹配,說明已經(jīng)找到了,是完整的協(xié)議        if (Regex.IsMatch(input, pattern)             / 獲取匹配的值     

13、0;      string match = Regex.Match(input, pattern).Groups0.Value;            outputList.Add(match);            partialProtocal = ""    

14、        / 縮短input的長度            input = input.Substring(match.Length);            / 遞歸調(diào)用           

15、GetProtocol(input, outputList);        else             / 如果不匹配,說明協(xié)議的長度不夠,            / 那么先緩存,然后等待下一次請求        

16、0;   partialProtocal = input;                return outputList.ToArray();    因為現(xiàn)在它已經(jīng)不是本文的重點了,所以我就不演示對于它的測試了,本文所附帶的代碼中含有它的測試代碼(我在ProtocolHandler中添加了一個靜態(tài)類Test())。2.2 FileRequestType枚舉和FileProtocol結(jié)構(gòu)因為XML是以字符

17、串的形式在進行傳輸,為了方便使用,我們最好構(gòu)建一個強類型來對它們進行操作,這樣會方便很多。我們首先可以定義FileRequestMode枚舉,它代表是發(fā)送還是接收文件:public enum FileRequestMode     Send = 0,    Receive接下來我們再定義一個FileProtocol結(jié)構(gòu),用來為整個協(xié)議字符串提供強類型的訪問,注意這里覆蓋了基類的ToString()方法,這樣在客戶端我們就不需要再手工去編寫XML,只要在結(jié)構(gòu)值上調(diào)用ToString()就OK了,會方便很多。public struct F

18、ileProtocol     private readonly FileRequestMode mode;    private readonly int port;    private readonly string fileName;    public FileProtocol        (FileRequestMode mode, int port, string fileName) 

19、60;       this.mode = mode;        this.port = port;        this.fileName = fileName;        public FileRequestMode Mode         get re

20、turn mode;         public int Port         get return port;         public string FileName         get return fileName;         public overr

21、ide string ToString()         return String.Format("<protocol><file name="0" mode="1" port="2" /></protocol>", fileName, mode, port);    2.3 ProtocolHelper輔助類這個類專用于將XML格式的協(xié)議映射為我們上面定義的強類型對象,這里我

22、沒有加入try/catch異常處理,因為協(xié)議對用戶來說是不可見的,而且客戶端應(yīng)該總是發(fā)送正確的協(xié)議,我覺得這樣可以讓代碼更加清晰:public class ProtocolHelper     private XmlNode fileNode;    private XmlNode root;        public ProtocolHelper(string protocol)         Xml

23、Document doc = new XmlDocument();        doc.LoadXml(protocol);        root = doc.DocumentElement;        fileNode = root.SelectSingleNode("file");       

24、/ 此時的protocal一定為單條完整protocal    private FileRequestMode GetFileMode()         string mode = fileNode.Attributes"mode".Value;        mode = mode.ToLower();        if (mode

25、 = "send")            return FileRequestMode.Send;        else            return FileRequestMode.Receive;        /

26、獲取單條協(xié)議包含的信息    public FileProtocol GetProtocol()         FileRequestMode mode = GetFileMode();        string fileName = ""        int port = 0;     &#

27、160;  fileName = fileNode.Attributes"name".Value;        port = Convert.ToInt32(fileNode.Attributes"port".Value);        return new FileProtocol(mode, port, fileName);    OK,我們又耽誤了點時間,下面就讓我們

28、進入正題吧。3.客戶端發(fā)送數(shù)據(jù)3.1 服務(wù)端的實現(xiàn)我們還是將一個問題分成兩部分來處理,先是發(fā)送數(shù)據(jù),然后是接收數(shù)據(jù)。我們先看發(fā)送數(shù)據(jù)部分的服務(wù)端。如果你從第一篇文章看到了現(xiàn)在,那么我覺得更多的不是技術(shù)上的問題而是思路,所以我們不再將重點放到代碼上,這些應(yīng)該很容易就看懂了。class Server     static void Main(string args)         Console.WriteLine("Server is running . "); &

29、#160;      IPAddress ip = IPAddress.Parse("");        TcpListener listener = new TcpListener(ip, 8500);        listener.Start();         

30、0; / 開啟對控制端口 8500 的偵聽        Console.WriteLine("Start Listening .");        while (true)             / 獲取一個連接,同步方法,在此處中斷       

31、60;    TcpClient client = listener.AcceptTcpClient();                          RemoteClient wapper = new RemoteClient(client);      

32、60;     wapper.BeginRead();            public class RemoteClient     private TcpClient client;    private NetworkStream streamToClient;    private const int BufferSize = 8192; 

33、0;  private byte buffer;    private ProtocolHandler handler;        public RemoteClient(TcpClient client)         this.client = client;        / 打印連接到的客戶端信息    

34、    Console.WriteLine("nClient Connected!0 <- 1",            client.Client.LocalEndPoint, client.Client.RemoteEndPoint);        / 獲得流        stream

35、ToClient = client.GetStream();        buffer = new byteBufferSize;        handler = new ProtocolHandler();        / 開始進行讀取    public void BeginRead()       

36、;         AsyncCallback callBack = new AsyncCallback(OnReadComplete);        streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);        / 再讀取完成時進行回調(diào)    private void OnReadC

37、omplete(IAsyncResult ar)         int bytesRead = 0;        try             lock (streamToClient)              

38、   bytesRead = streamToClient.EndRead(ar);                Console.WriteLine("Reading data, 0 bytes .", bytesRead);                

39、        if (bytesRead = 0) throw new Exception("讀取到0字節(jié)");            string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);            Ar

40、ray.Clear(buffer,0,buffer.Length);        / 清空緩存,避免臟讀            / 獲取protocol數(shù)組            string protocolArray = handler.GetProtocol(msg);  

41、0;         foreach (string pro in protocolArray)                 / 這里異步調(diào)用,不然這里可能會比較耗時                Parameter

42、izedThreadStart start =                    new ParameterizedThreadStart(handleProtocol);                start.BeginInvoke(pro, nul

43、l, null);                        / 再次調(diào)用BeginRead(),完成時調(diào)用自身,形成無限循環(huán)            lock (streamToClient)       

44、          AsyncCallback callBack = new AsyncCallback(OnReadComplete);                streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);     &

45、#160;              catch(Exception ex)             if(streamToClient!=null)                streamToClient.Disp

46、ose();            client.Close();            Console.WriteLine(ex.Message);      / 捕獲異常時退出程序              

47、;  / 處理protocol    private void handleProtocol(object obj)         string pro = obj as string;        ProtocolHelper helper = new ProtocolHelper(pro);        FileProtocol prot

48、ocol = helper.GetProtocol();        if (protocol.Mode = FileRequestMode.Send)             / 客戶端發(fā)送文件,對服務(wù)端來說則是接收文件            receiveFile(protocol); 

49、60;      else if (protocol.Mode = FileRequestMode.Receive)             / 客戶端接收文件,對服務(wù)端來說則是發(fā)送文件            / sendFile(protocol);      

50、60;         private void receiveFile(FileProtocol protocol)         / 獲取遠程客戶端的位置        IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;        IPAd

51、dress ip = endpoint.Address;                / 使用新端口號,獲得遠程用于接收文件的端口        endpoint = new IPEndPoint(ip, protocol.Port);        / 連接到遠程客戶端    

52、;    TcpClient localClient;        try             localClient = new TcpClient();            localClient.Connect(endpoint);   

53、;     catch             Console.WriteLine("無法連接到客戶端 -> 0", endpoint);            return;            &#

54、160;   / 獲取發(fā)送文件的流        NetworkStream streamToClient = localClient.GetStream();        / 隨機生成一個在當(dāng)前目錄下的文件名稱        string path =          &#

55、160;  Environment.CurrentDirectory + "/" + generateFileName(protocol.FileName);        byte fileBuffer = new byte1024; / 每次收1KB        FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write); 

56、;       / 從緩存buffer中讀入到文件流中        int bytesRead;        int totalBytes = 0;        do             bytesRead =

57、 streamToClient.Read(buffer, 0, BufferSize);                         fs.Write(buffer, 0, bytesRead);            totalBytes += byte

58、sRead;            Console.WriteLine("Receiving 0 bytes .", totalBytes);        while (bytesRead > 0);        Console.WriteLine("Total 0 bytes received, Done!

59、", totalBytes);        streamToClient.Dispose();        fs.Dispose();        localClient.Close();        / 隨機獲取一個圖片名稱    private string generateF

60、ileName(string fileName)         DateTime now = DateTime.Now;        return String.Format(            "0_1_2_3", now.Minute, now.Second, now.Millisecond, fileName

61、60;       );    這里應(yīng)該沒有什么新知識,需要注意的地方有這么幾個:· 在OnReadComplete()回調(diào)方法中的foreach循環(huán),我們使用委托異步調(diào)用了handleProtocol()方法,這是因為handleProtocol即將執(zhí)行的是一個讀取或接收文件的操作,也就是一個相對耗時的操作。 · 在handleProtocol()方法中,我們深切體會了定義ProtocolHelper類和FileProtocol結(jié)構(gòu)的好處。如果沒有定義它們,這里將是不堪入目的處理XM

62、L以及類型轉(zhuǎn)換的代碼。 · handleProtocol()方法中進行了一個條件判斷,注意sendFile()方法我屏蔽掉了,這個還沒有實現(xiàn),但是我想你已經(jīng)猜到它將是后面要實現(xiàn)的內(nèi)容。 · receiveFile()方法是實際接收客戶端發(fā)來文件的方法,這里沒有什么特別之處。需要注意的是文件存儲的路徑,它保存在了當(dāng)前程序執(zhí)行的目錄下,文件的名稱我使用generateFileName()生成了一個與時間有關(guān)的隨機名稱。 3.2客戶端的實現(xiàn)我們現(xiàn)在先不著急實現(xiàn)客戶端S1、R1等用戶菜單,首先完成發(fā)送文件這一功能,實際上,就是為上一節(jié)SendMessage()加一個姐妹方法Send

63、File()。class Client     static void Main(string args)         ConsoleKey key;        ServerClient client = new ServerClient();        string filePath = Environment.CurrentDirectory

64、 + "/" + "Client01.jpg"        if(File.Exists(filePath)            client.BeginSendFile(filePath);                Consol

65、e.WriteLine("nn輸入"Q"鍵退出。");        do             key = Console.ReadKey(true).Key;        while (key != ConsoleKey.Q);    public class ServerClie

66、nt     private const int BufferSize = 8192;    private byte buffer;    private TcpClient client;    private NetworkStream streamToServer;    public ServerClient()         try    &#

67、160;        client = new TcpClient();            client.Connect("localhost", 8500);      / 與服務(wù)器連接        catch (Exception ex)   &#

68、160;         Console.WriteLine(ex.Message);            return;                buffer = new byteBufferSize;     

69、;   / 打印連接到的服務(wù)端信息        Console.WriteLine("Server Connected!0 -> 1",            client.Client.LocalEndPoint, client.Client.RemoteEndPoint);        stre

70、amToServer = client.GetStream();        / 發(fā)送消息到服務(wù)端    public void SendMessage(string msg)         byte temp = Encoding.Unicode.GetBytes(msg);   / 獲得緩存        try   &

71、#160;         lock (streamToServer)                 streamToServer.Write(temp, 0, temp.Length); / 發(fā)往服務(wù)器              

72、60;         Console.WriteLine("Sent: 0", msg);        catch (Exception ex)             Console.WriteLine(ex.Message);       &

73、#160;    return;                / 發(fā)送文件 - 異步方法    public void BeginSendFile(string filePath)         ParameterizedThreadStart start =      &#

74、160;     new ParameterizedThreadStart(BeginSendFile);        start.BeginInvoke(filePath, null, null);        private void BeginSendFile(object obj)         string filePath = obj as

75、 string;        SendFile(filePath);        / 發(fā)送文件 - 同步方法    public void SendFile(string filePath)         IPAddress ip = IPAddress.Parse("");    &

76、#160;   TcpListener listener = new TcpListener(ip, 0);        listener.Start();        / 獲取本地偵聽的端口號        IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;   &

77、#160;    int listeningPort = endPoint.Port;        / 獲取發(fā)送的協(xié)議字符串        string fileName = Path.GetFileName(filePath);        FileProtocol protocol =     

78、60;      new FileProtocol(FileRequestMode.Send, listeningPort, fileName);        string pro = protocol.ToString();        SendMessage(pro);       / 發(fā)送協(xié)議到服務(wù)端   &

79、#160;    / 中斷,等待遠程連接        TcpClient localClient = listener.AcceptTcpClient();        Console.WriteLine("Start sending file.");        NetworkStream stream = localClient

80、.GetStream();        / 創(chuàng)建文件流        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);                   byte fileBuffer =

81、 new byte1024;     / 每次傳1KB        int bytesRead;        int totalBytes = 0;        / 創(chuàng)建獲取文件發(fā)送狀態(tài)的類        SendStatus status = new SendStatus

82、(filePath);        / 將文件流轉(zhuǎn)寫入網(wǎng)絡(luò)流        try             do                 Thread.Sleep(10); 

83、60;         / 為了更好的視覺效果,暫停10毫秒:-)                bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);             

84、0;                    stream.Write(fileBuffer, 0, bytesRead);                totalBytes += bytesRead;     

85、0;      / 發(fā)送了的字節(jié)數(shù)                status.PrintStatus(totalBytes); / 打印發(fā)送狀態(tài)            while (bytesRead > 0);         &#

溫馨提示

  • 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)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論