C# 實作 Socket 客戶端和伺服器端 詳細教學

下方實作範例的功能為:
  • 客戶端連接伺服器後傳送「嗨,我是 Ruyut」並接收伺服器端傳送的訊息
  • 伺服器等待客戶端連接,接收訊息後回傳 「嗨,我是伺服器」,中斷客戶端連線,重新等待客戶端連接
客戶端和伺服器端的發送和接收都使用 UTF8 ,所以可以正常顯示中文,不會出現亂碼問題

伺服器端完整程式碼

SocketService.cs
    
using System.Net;
using System.Net.Sockets;
using System.Text;

public class SocketService
{
    private TcpListener? _tcpListener;

    public void Start(string ip, int port)
    {
        _tcpListener = new TcpListener(IPAddress.Parse(ip), port);
        _tcpListener.Start();
        _tcpListener.BeginAcceptTcpClient(AsyncCallback, _tcpListener);
        Console.WriteLine("伺服器已啟動,開始監聽");
    }

    /// <summary>
    /// 非同步接收客戶端連線
    /// </summary>
    private void AsyncCallback(IAsyncResult asyncResult)
    {
        if (asyncResult.AsyncState is not TcpListener listener) return;
        TcpClient client = listener.EndAcceptTcpClient(asyncResult);
        Console.WriteLine("客戶端已連線");

        // 開始接收客戶端資料
        NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[1024];
        var read = stream.Read(buffer, 0, buffer.Length);
        var receive = Encoding.UTF8.GetString(buffer, 0, read);
        Console.WriteLine($"接收到客戶端訊息:{receive}");

        // 開始傳送資料給客戶端
        string message = "嗨,我是伺服器";
        byte[] bytes = Encoding.UTF8.GetBytes(message);
        stream.Write(bytes, 0, bytes.Length);
        Console.WriteLine($"發送給客戶端的訊息: {message}");

        // 關閉連線
        stream.Close();
        client.Close();

        // 接收下一個訊息
        listener.BeginAcceptTcpClient(AsyncCallback, listener);
    }

    public void Close()
    {
        _tcpListener?.Stop();
    }
}
    

使用:
    
SocketService socketService = new SocketService();
socketService.Start("127.0.0.1", 8000);

// 關閉
// socketService.Stop();
    

客戶端完整程式碼

    
using System.Net.Sockets;
using System.Text;


string ip = "127.0.0.1";
int port = 8000;

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.Connect(ip, port);
Console.WriteLine($"已連伺服器");


// 發送訊息
var message = "嗨,我是 Ruyut";
byte[] data = Encoding.UTF8.GetBytes(message);
socket.Send(data);
Console.WriteLine($"發送給伺服器的訊息: {message}");


// 接收訊息
byte[] buffer = new byte[1024];
int length = socket.Receive(buffer);
string receive = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine($"接收到伺服器回傳的訊息: {receive}");
    

接收訊息時超出緩衝區處理方式

在本示範中未提及接收訊息大小超過 byte[1024] 時的處理方式,若傳送超過時發送端會收到下列錯誤訊息:
  
System.Net.Sockets.SocketException (10054): 遠端主機已強制關閉一個現存的連線。


1. 較為簡易的處理方式為先確認伺服器和客戶端單次傳送的最大長度,依此長度設定 buffer 大小(廢話)

2. 或是使用下列程式碼在超過緩衝區大小時接續讀取: (為了方便讀者查找位置,前六行註解的部分為上方程式碼已提供之內容)
  

        // 開始接收客戶端資料
        // NetworkStream stream = client.GetStream();
        // byte[] buffer = new byte[1024];
        // var read = stream.Read(buffer, 0, buffer.Length);
        // var receive = Encoding.UTF8.GetString(buffer, 0, read);
        // Console.WriteLine($"接收到客戶端訊息:{receive}");
        
        NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[1024];
        StringBuilder stringBuilder = new StringBuilder();
        do
        {
            var count = stream.Read(buffer, 0, buffer.Length);
            stringBuilder.AppendFormat("{0}", Encoding.UTF8.GetString(buffer, 0, count));
        }
        while(stream.DataAvailable);
        var receive = stringBuilder.ToString();
        Console.WriteLine($"接收到客戶端資料:{receive}");


注意: 使用此方式在純英文內容時可完整讀取,但如果是中文內容則在結尾和開頭的部分可能會出現亂碼,且無法以簡易的方式處理,經筆者研究,最好的傳輸方式為客戶端和伺服器端皆使用 base64 傳送和接收訊息

使用 base64 傳送和接收訊息

發送訊息:
  
// 發送訊息
string message = "嗨,我是 Ruyut"; // 這裡請替換為很長很長的文章做測試
string messageBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(message));
byte[] data = Encoding.ASCII.GetBytes(messageBase64);
socket.Send(data);
Console.WriteLine($"發送給伺服器的訊息: {message}");


接收訊息:(一樣保留前六行註解的部分,方便讀者查找位置)
  
        // 開始接收客戶端資料
        // NetworkStream stream = client.GetStream();
        // byte[] buffer = new byte[1024];
        // var read = stream.Read(buffer, 0, buffer.Length);
        // var receive = Encoding.UTF8.GetString(buffer, 0, read);
        // Console.WriteLine($"接收到客戶端訊息:{receive}");

        NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[1024];
        StringBuilder stringBuilder = new StringBuilder();
        do
        {
            var count = stream.Read(buffer, 0, buffer.Length);
            stringBuilder.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, count));
        } while (stream.DataAvailable);

        string receive = stringBuilder.ToString();
        byte[] base64 = Convert.FromBase64String(receive);
        string result = Encoding.UTF8.GetString(base64);
        Console.WriteLine($"接收到客戶端資料:{result}");


注:伺服器發出回應和客戶端接收的部分也需要更改

希望大家都能輕易的使用 Socket!

參考資料:
TcpListener Class
NetworkStream.Read Method
TcpListener.BeginAcceptTcpClient(AsyncCallback, Object) Method

留言