首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 網路程式設計 Part 4:自定義協定與檔案傳輸實作

     編輯:關於C#

前兩篇文章範例都傳輸字串,有時想在服務端與客戶端間傳遞檔案。假設客戶端顯示選單,輸入 S1、S2、S3 分別發送 Client01.jpg、Client02.jpg、Client03.jpg 到服務端;輸入 R1、R2、R3 則從服務端接收 Server01.jpg、Server02.jpg、Server03.jpg。如何完成?有兩種做法:

  1. 類 FTP 協定:服務端開闢兩個連接埠持續偵聽:一個接收字串(控制連接埠),接收各種命令;一個傳輸資料(檔案收發)。
  2. 單一控制連接埠 + 動態資料連接埠:服務端只開一個連接埠接收字串(控制連接埠)。收到請求後,根據請求內容在客戶端開闢連接埠專用檔案傳輸,傳輸結束後關閉。

第一種方式較好,但採用第二種方式。原因可回顧 Part 1 聊天程式模式講述,下一篇將建立採用第三種模式的聊天程式,本文練習為下一篇鋪墊。

1. 訂立協定(採用 XML 格式)

1.1 發送檔案

流程:客戶端開闢資料連接埠偵聽(假設 8005)→ 發送控制字串 <protocol><file name="client01.jpg" mode="send" port="8005" /></protocol> → 服務端依客戶端 IP 與連接埠建立連線 → 客戶端偵聽到連線開始發送檔案 → 傳送完畢雙方關閉連線。

1.2 接收檔案

流程類似,差別在資料流寫入方向。客戶端開闢資料連接埠(假設 8006)→ 發送 <protocol><file name="Server01.jpg" mode="receive" port="8006" /></protocol> → 服務端連線 → 客戶端建立連線,服務端寫入資料流 → 傳送完畢雙方關閉連線。

2. 協定處理類別實作

2.1 ProtocolHandler 輔助類別

功能:獲取完整協定資訊(服務端可能將多請求合併或拆分),方便獲取屬性資訊(XML 解析)。必須宣告為實例而非靜態,因每個 TcpClient 對應一個 ProtocolHandler,內部 partialProtocol 不能共享,用於暫存被截斷字串。

public class ProtocolHandler {
    private string partialProtocol;
    public ProtocolHandler() { partialProtocol = ""; }
    public string[] GetProtocol(string input) { return GetProtocol(input, null); }
    private string[] GetProtocol(string input, List outputList) {
        if (outputList == null) outputList = new List();
        if (String.IsNullOrEmpty(input)) return outputList.ToArray();
        if (!String.IsNullOrEmpty(partialProtocol)) input = partialProtocol + input;
        string pattern = "(^.*?)";
        if (Regex.IsMatch(input, pattern)) {
            string match = Regex.Match(input, pattern).Groups[0].Value;
            outputList.Add(match);
            partialProtocol = "";
            input = input.Substring(match.Length);
            GetProtocol(input, outputList);
        } else {
            partialProtocol = input;
        }
        return outputList.ToArray();
    }
}

2.2 FileRequestMode 列舉與 FileProtocol 結構

public enum FileRequestMode { Send = 0, Receive }
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 = 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 輔助類別

public class ProtocolHelper {
    private XmlNode fileNode, root;
    public ProtocolHelper(string protocol) {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(protocol);
        root = doc.DocumentElement;
        fileNode = root.SelectSingleNode("file");
    }
    private FileRequestMode GetFileMode() {
        string mode = fileNode.Attributes["mode"].Value.ToLower();
        return mode == "send" ? FileRequestMode.Send : FileRequestMode.Receive;
    }
    public FileProtocol GetProtocol() {
        FileRequestMode mode = GetFileMode();
        string fileName = fileNode.Attributes["name"].Value;
        int port = Convert.ToInt32(fileNode.Attributes["port"].Value);
        return new FileProtocol(mode, port, fileName);
    }
}

3. 客戶端發送資料實作

3.1 服務端實作

class Server {
    static void Main(string[] args) {
        Console.WriteLine("Server is running ... ");
        IPAddress ip = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(ip, 8500);
        listener.Start();
        Console.WriteLine("Start Listening ...");
        while (true) {
            TcpClient client = listener.AcceptTcpClient();
            RemoteClient wrapper = new RemoteClient(client);
            wrapper.BeginRead();
        }
    }
}

public class RemoteClient {
    private TcpClient client;
    private NetworkStream streamToClient;
    private const int BufferSize = 8192;
    private byte[] buffer;
    private ProtocolHandler handler;

    public RemoteClient(TcpClient client) {
        this.client = client;
        Console.WriteLine("\nClient Connected! {0} <-- {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
        streamToClient = client.GetStream();
        buffer = new byte[BufferSize];
        handler = new ProtocolHandler();
    }

    public void BeginRead() {
        AsyncCallback callBack = new AsyncCallback(OnReadComplete);
        streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);
    }

    private void OnReadComplete(IAsyncResult ar) {
        int bytesRead = 0;
        try {
            lock (streamToClient) {
                bytesRead = streamToClient.EndRead(ar);
                Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
            }
            if (bytesRead == 0) throw new Exception("讀取到 0 位元組");
            string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
            Array.Clear(buffer, 0, buffer.Length);
            string[] protocolArray = handler.GetProtocol(msg);
            foreach (string pro in protocolArray) {
                ParameterizedThreadStart start = new ParameterizedThreadStart(handleProtocol);
                start.BeginInvoke(pro, null, null);
            }
            lock (streamToClient) {
                AsyncCallback callBack = new AsyncCallback(OnReadComplete);
                streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);
            }
        } catch (Exception ex) {
            if (streamToClient != null) streamToClient.Dispose();
            client.Close();
            Console.WriteLine(ex.Message);
        }
    }

    private void handleProtocol(object obj) {
        string pro = obj as string;
        ProtocolHelper helper = new ProtocolHelper(pro);
        FileProtocol protocol = helper.GetProtocol();
        if (protocol.Mode == FileRequestMode.Send) {
            receiveFile(protocol);
        } else if (protocol.Mode == FileRequestMode.Receive) {
            // sendFile(protocol); // 下篇實作
        }
    }

    private void receiveFile(FileProtocol protocol) {
        IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;
        IPAddress ip = endpoint.Address;
        endpoint = new IPEndPoint(ip, protocol.Port);
        TcpClient localClient;
        try {
            localClient = new TcpClient();
            localClient.Connect(endpoint);
        } catch {
            Console.WriteLine("無法連接到客戶端 --> {0}", endpoint);
            return;
        }
        NetworkStream streamToClient = localClient.GetStream();
        string path = Environment.CurrentDirectory + "/" + generateFileName(protocol.FileName);
        byte[] fileBuffer = new byte[1024];
        FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write);
        int bytesRead, totalBytes = 0;
        do {
            bytesRead = streamToClient.Read(buffer, 0, BufferSize);
            fs.Write(buffer, 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) {
        DateTime now = DateTime.Now;
        return String.Format("{0}_{1}_{2}_{3}", now.Minute, now.Second, now.Millisecond, fileName);
    }
}

3.2 客戶端實作

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("\n\n輸入 \"Q\" 鍵退出。");
        do { key = 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);
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return;
        }
        buffer = new byte[BufferSize];
        Console.WriteLine("Server Connected! {0} --> {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
        streamToServer = client.GetStream();
    }

    public void SendMessage(string msg) {
        byte[] temp = Encoding.Unicode.GetBytes(msg);
        try {
            lock (streamToServer) {
                streamToServer.Write(temp, 0, temp.Length);
            }
            Console.WriteLine("Sent: {0}", msg);
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
        }
    }

    public void BeginSendFile(string filePath) {
        ParameterizedThreadStart start = new ParameterizedThreadStart(BeginSendFile);
        start.BeginInvoke(filePath, null, null);
    }

    private void BeginSendFile(object obj) {
        SendFile(obj as string);
    }

    public void SendFile(string filePath) {
        IPAddress ip = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(ip, 0);
        listener.Start();
        IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;
        int listeningPort = endPoint.Port;
        string fileName = Path.GetFileName(filePath);
        FileProtocol protocol = new FileProtocol(FileRequestMode.Send, listeningPort, fileName);
        string pro = protocol.ToString();
        SendMessage(pro);
        TcpClient localClient = listener.AcceptTcpClient();
        Console.WriteLine("Start sending file...");
        NetworkStream stream = localClient.GetStream();
        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
        byte[] fileBuffer = new byte[1024];
        int bytesRead, totalBytes = 0;
        SendStatus status = new SendStatus(filePath);
        try {
            do {
                Thread.Sleep(10);
                bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);
                stream.Write(fileBuffer, 0, bytesRead);
                totalBytes += bytesRead;
                status.PrintStatus(totalBytes);
            } 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();
    }
}

SendStatus 狀態顯示類別

public class SendStatus {
    private FileInfo info;
    private long fileBytes;
    public SendStatus(string filePath) {
        info = new FileInfo(filePath);
        fileBytes = info.Length;
    }
    public void PrintStatus(int sent) {
        string percent = GetPercent(sent);
        Console.WriteLine("Sending {0} bytes, {1}% ...", sent, percent);
    }
    public string GetPercent(int sent) {
        decimal allBytes = Convert.ToDecimal(fileBytes);
        decimal currentSent = Convert.ToDecimal(sent);
        decimal percent = (currentSent / allBytes) * 100;
        percent = Math.Round(percent, 1);
        return percent.ToString() == "100.0" ? "100" : percent.ToString();
    }
}

3.3 程式測試結果

服務端顯示接收進度與總位元組數,客戶端顯示發送進度百分比,服務端 Bin\Debug 目錄可見接收到的圖片檔案。本文完成發送檔案部分,接收檔案將在下篇完成。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved