天天看點

C#網絡程式設計(接收檔案) - Part.5C#網絡程式設計(接收檔案) - Part.5

C#網絡程式設計(接收檔案) - Part.5

這篇文章将完成 Part.4 中剩餘的部分,它們本來是一篇完整的文章,但是因為上一篇比較長,合并起來頁數太多,浏覽起來可能會比較不友善,我就将它拆為兩篇了,本文便是它的後半部分。我們繼續進行上一篇沒有完成的步驟:用戶端接收來自服務端的檔案。

4.用戶端接收檔案

4.1服務端的實作

對于服務端,我們隻需要實作上一章遺留的sendFile()方法就可以了,它起初在handleProtocol中是注釋掉的。另外,由于建立連接配接、擷取流等操作與receiveFile()是沒有差別的,是以我們将它提出來作為一個公共方法getStreamToClient()。下面是服務端的代碼,隻包含新增改過的代碼,對于原有方法我隻給出了簽名:

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();           // 開啟對控制端口 8500 的偵聽

        Console.WriteLine("Start Listening ...");

        while (true) {

            // 擷取一個連接配接,同步方法,在此處中斷

            TcpClient client = listener.AcceptTcpClient(); 

            RemoteClient wapper = new RemoteClient(client);

            wapper.BeginRead();

        }

    }

}

public class RemoteClient {

    // 字段 略

    public RemoteClient(TcpClient client) {}

    // 開始進行讀取

    public void BeginRead() { }

    // 再讀取完成時進行回調

    private void OnReadComplete(IAsyncResult ar) { }

    // 處理protocol

    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 sendFile(FileProtocol protocol) {

        TcpClient localClient;

        NetworkStream streamToClient = getStreamToClient(protocol, out localClient);

        // 獲得檔案的路徑

        string filePath = Environment.CurrentDirectory + "/" + protocol.FileName;

        // 建立檔案流

        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);

        byte[] fileBuffer = new byte[1024];     // 每次傳1KB

        int bytesRead;

        int totalBytes = 0;

        // 建立擷取檔案發送狀态的類

        SendStatus status = new SendStatus(filePath);

        // 将檔案流轉寫入網絡流

        try {

            do {

                Thread.Sleep(10);           // 為了更好的視覺效果,暫停10毫秒:-)

                bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);

                streamToClient.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...");

        streamToClient.Dispose();

        fs.Dispose();

        localClient.Close();

    // 接收檔案

    private void receiveFile(FileProtocol protocol) { }

    // 擷取連接配接到遠端的流 -- 公共方法

    private NetworkStream getStreamToClient(FileProtocol protocol, out TcpClient localClient) {

        // 擷取遠端用戶端的位置

        IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;

        IPAddress ip = endpoint.Address;

        // 使用新端口号,獲得遠端用于接收檔案的端口

        endpoint = new IPEndPoint(ip, protocol.Port);

        // 連接配接到遠端用戶端

            localClient = new TcpClient();

            localClient.Connect(endpoint);

            Console.WriteLine("無法連接配接到用戶端 --> {0}", endpoint);

            localClient = null;

            return null;

        // 擷取發送檔案的流

        NetworkStream streamToClient = localClient.GetStream();

        return streamToClient;

    // 随機擷取一個圖檔名稱

    private string generateFileName(string fileName) {}

服務端的sendFile方法和用戶端的SendFile()方法完全類似,上面的代碼幾乎是一次編寫成功的。另外注意我将用戶端使用的SendStatus類也拷貝到了服務端。接下來我們看下用戶端。

4.2用戶端的實作

首先要注意的是用戶端的SendFile()接收的參數是檔案全路徑,但是在寫入到協定時隻擷取了路徑中的檔案名稱。這是因為服務端不需要知道檔案在用戶端的路徑,是以協定中隻寫檔案名;而為了使用戶端的SendFile()方法更通用,是以它接收本地檔案的全路徑。

用戶端的ReceiveFile()的實作也和服務端的receiveFile()方法類似,同樣,由于要儲存到本地,為了避免檔案名重複,我将服務端的generateFileName()方法複制了過來。

public class ServerClient :IDisposable {

    // 字段略

    public ServerClient() {}

    // 發送消息到服務端

    public void SendMessage(string msg) {}

    // 發送檔案 - 異步方法

    public void BeginSendFile(string filePath) {    }

    private void SendFile(object obj) { }

    // 發送檔案 -- 同步方法

    public void SendFile(string filePath) {}

    // 接收檔案 -- 異步方法

    public void BeginReceiveFile(string fileName) {

        ParameterizedThreadStart start =

            new ParameterizedThreadStart(ReceiveFile);

        start.BeginInvoke(fileName, null, null);

    public void ReceiveFile(object obj) {

        string fileName = obj as string;

        ReceiveFile(fileName);

    // 接收檔案 -- 同步方法

    public void ReceiveFile(string fileName) {

        TcpListener listener = new TcpListener(ip, 0);

        listener.Start();

        // 擷取本地偵聽的端口号

        IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;

        int listeningPort = endPoint.Port;

        // 擷取發送的協定字元串

        FileProtocol protocol =

            new FileProtocol(FileRequestMode.Receive, listeningPort, fileName);

        string pro = protocol.ToString();

        SendMessage(pro);       // 發送協定到服務端

        // 中斷,等待遠端連接配接

        TcpClient localClient = listener.AcceptTcpClient();

        Console.WriteLine("Start sending file...");

        NetworkStream stream = localClient.GetStream();

        // 擷取檔案儲存的路勁

        string filePath =

            Environment.CurrentDirectory + "/" + generateFileName(fileName);

        FileStream fs = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write);

        // 從緩存buffer中讀入到檔案流中

        do {

            bytesRead = stream.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);

        fs.Dispose();          

        stream.Dispose();

        listener.Stop();

    public void Dispose() {

        if (streamToServer != null)

            streamToServer.Dispose();

        if (client != null)

            client.Close();

上面關鍵的一句就是建立協定那句,注意到将mode由Send改為了Receive,同時傳去了想要接收的服務端的檔案名稱。

4.3程式測試

現在我們已經完成了所有收發檔案的步驟,可以看到服務端的所有操作都是被動的,接下來我們修改用戶端的Main()程式,建立一個菜單,然後根據使用者輸入發送或者接收檔案。

class Program {

        ServerClient client = new ServerClient();

        string input;

        string path = Environment.CurrentDirectory + "/";

            Console.WriteLine("Send File:    S1 - Client01.jpg, S2 - Client02.jpg, S3 - Client03.jpg");

            Console.WriteLine("Receive File: R1 - Server01.jpg, R1 - Server02.jpg, R3- Server03.jpg");

            Console.WriteLine("Press 'Q' to exit. \n");

            Console.Write("Enter your choice: ");

            input = Console.ReadLine();

            switch(input.ToUpper()){

                case "S1":

                    client.BeginSendFile(path + "Client01.jpg");

                    break;

                case "S2":

                    client.BeginSendFile(path + "Client02.jpg");

                case "S3":

                case "R1":

                    client.BeginReceiveFile("Server01.jpg");

                case "R2":

                case "R3":

            }              

        } while (input.ToUpper() != "Q");

        client.Dispose();

由于這是一個控制台應用程式,并且采用了異步操作,是以這個菜單的出現順序有點混亂。我這裡描述起來比較困難,你将代碼下載下傳下來後運作一下就知道了:-)

程式的運作結果和上一節類似,這裡我就不再貼圖了。接下來是本系列的最後一篇,将發送字元串與傳輸檔案的功能結合起來,建立一個可以發送消息并能收發檔案的聊天程式,至于語音聊天嘛...等我學習了再告訴你 >_<、