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

C# 網路程式設計 Part 2:同步傳輸字串與伺服器架構演進

     編輯:關於C#

與服務端連線建立後,可透過連線發送接收資料。連接埠間以串流形式傳輸資料,幾乎任何物件都可儲存至串流,故可傳輸任何類型資料。對客戶端,寫入串流即傳送資料;讀取串流即接收資料。對服務端,寫入串流即發送資料;讀取串流即接收資料。

同步傳輸字串

任務:客戶端列印字串發送服務端,服務端輸出並轉大寫回發,客戶端接收後再列印。分兩部分:客戶端發送/服務端接收、服務端回發/客戶端接收。

1. 客戶端發送,服務端接收並輸出

1.1 服務端程式

class Server {
    static void Main(string[] args) {
        const int BufferSize = 8192;
        Console.WriteLine("Server is running ... ");
        IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
        TcpListener listener = new TcpListener(ip, 8500);
        listener.Start();
        Console.WriteLine("Start Listening ...");
        TcpClient remoteClient = listener.AcceptTcpClient();
        Console.WriteLine("Client Connected! {0} <-- {1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
        NetworkStream streamToClient = remoteClient.GetStream();
        byte[] buffer = new byte[BufferSize];
        int bytesRead = streamToClient.Read(buffer, 0, BufferSize);
        Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
        string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
        Console.WriteLine("Received: {0}", msg);
    }
}

注意:可讀字串總位元組數大於 BufferSize 會截斷。大物件需分次讀取轉存:

byte[] buffer = new byte[BufferSize];
int bytesRead;
MemoryStream msStream = new MemoryStream();
do {
    bytesRead = streamToClient.Read(buffer, 0, BufferSize);
    msStream.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
buffer = msStream.GetBuffer();
string msg = Encoding.Unicode.GetString(buffer);

1.2 客戶端程式

class Client {
    static void Main(string[] args) {
        Console.WriteLine("Client Running ...");
        TcpClient client;
        try {
            client = new TcpClient();
            client.Connect("localhost", 8500);
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return;
        }
        Console.WriteLine("Server Connected! {0} --> {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
        string msg = "\"Welcome To TraceFact.Net\"";
        NetworkStream streamToServer = client.GetStream();
        byte[] buffer = Encoding.Unicode.GetBytes(msg);
        streamToServer.Write(buffer, 0, buffer.Length);
        Console.WriteLine("Sent: {0}", msg);
    }
}

執行結果:服務端阻塞於 AcceptTcpClient,客戶端連線後服務端讀取資料並輸出。

伺服器架構四種服務模式

  1. 無迴圈:單客戶端單請求
  2. 單層 do/while 包雙方法:多客戶端單請求
  3. 外層 AcceptTcpClient、內層 Read:單客戶端多請求
  4. 雙層 do/while 巢狀 + 多執行緒:多客戶端多請求(真正伺服器模式)

模式二:多客戶端單請求

do {
    TcpClient remoteClient = listener.AcceptTcpClient();
    // ... 讀取一次資料 ...
} while (true);

模式三:單客戶端多請求


TcpClient remoteClient = listener.AcceptTcpClient();
NetworkStream streamToClient = remoteClient.GetStream();
do {
    // ... 重複讀取資料 ...
} while (true);

客戶端修改為選單模式:按 S 輸入字串發送,按 X 退出。測試可見服務端連續接收多條訊息。

異常處理注意

客戶端呼叫 Close() 或 Dispose(),服務端 Read() 持續回傳 0 產生無限迴圈;直接關閉客戶端,服務端阻塞於 Read() 拋出「遠端主機強制關閉連線」異常。讀寫操作必須包在 try/catch 中。

2. 服務端回發,客戶端接收並輸出

角色對調:服務端寫入串流,客戶端讀取串流。加上 lock 保護串流讀寫。

服務端完整程式

class Server {
    static void Main(string[] args) {
        const int BufferSize = 8192;
        ConsoleKey key;
        Console.WriteLine("Server is running ... ");
        IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
        TcpListener listener = new TcpListener(ip, 8500);
        listener.Start();
        Console.WriteLine("Start Listening ...");
        TcpClient remoteClient = listener.AcceptTcpClient();
        Console.WriteLine("Client Connected! {0} <-- {1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
        NetworkStream streamToClient = remoteClient.GetStream();
        do {
            byte[] buffer = new byte[BufferSize];
            int bytesRead;
            try {
                lock (streamToClient) {
                    bytesRead = streamToClient.Read(buffer, 0, BufferSize);
                }
                if (bytesRead == 0) throw new Exception("讀取到 0 位元組");
                Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
                string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
                Console.WriteLine("Received: {0}", msg);
                msg = msg.ToUpper();
                buffer = Encoding.Unicode.GetBytes(msg);
                lock (streamToClient) {
                    streamToClient.Write(buffer, 0, buffer.Length);
                }
                Console.WriteLine("Sent: {0}", msg);
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
                break;
            }
        } while (true);
        streamToClient.Dispose();
        remoteClient.Close();
        Console.WriteLine("\n\n輸入 \"Q\" 鍵退出。");
        do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q);
    }
}

客戶端完整程式

class Client {
    static void Main(string[] args) {
        Console.WriteLine("Client Running ...");
        TcpClient client;
        ConsoleKey key;
        const int BufferSize = 8192;
        try {
            client = new TcpClient();
            client.Connect("localhost", 8500);
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return;
        }
        Console.WriteLine("Server Connected! {0} --> {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
        NetworkStream streamToServer = client.GetStream();
        Console.WriteLine("Menu: S - Send, X - Exit");
        do {
            key = Console.ReadKey(true).Key;
            if (key == ConsoleKey.S) {
                Console.Write("Input the message: ");
                string msg = Console.ReadLine();
                byte[] buffer = Encoding.Unicode.GetBytes(msg);
                try {
                    lock (streamToServer) {
                        streamToServer.Write(buffer, 0, buffer.Length);
                    }
                    Console.WriteLine("Sent: {0}", msg);
                    int bytesRead;
                    buffer = new byte[BufferSize];
                    lock (streamToServer) {
                        bytesRead = streamToServer.Read(buffer, 0, BufferSize);
                    }
                    msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
                    Console.WriteLine("Received: {0}", msg);
                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                    break;
                }
            }
        } while (key != ConsoleKey.X);
        streamToServer.Dispose();
        client.Close();
        Console.WriteLine("\n\n輸入 \"Q\" 鍵退出。");
        do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q);
    }
}

測試輸出

客戶端發送 "Hello, I'm jimmy zhang.",服務端回應 "HELLO, I'M JIMMY ZHANG."。

附錄:編碼方式比較

ASCII 無法儲存中文;UTF-8 變長編碼,ASCII 字元 1 位元組,中文 3 位元組;Unicode 固定 2 位元組,ASCII 字元第 2 位元組補零,中文 2 位元組。

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