C# Socket異步通信


由於平時使用Unity開發,所以相對其他的編程語言,對C#比較了解,所以寫了一個C/S的Socket異步通信程序。目前只是在C#中測試,未來會接入Unity3D,這個只是我個人用來進行學習用的,所以大家不必在意使用哪個語言做后端服務器開發更好

一些說明:

  • 使用最簡單的TCP協議來進行通信,通信數據未做任何包裝和加工(后面會接入Protobuf-net)
  • 采用異步通信結構
  • 為了提高服務器的性能,使用了連接池復用的方法

1.服務器連接池

class Conn
{
    //緩存區大小
    public const int BUFFER_SIZE = 1024;
    //socket
    public Socket socket;
    //是否被使用
    public bool isUse = false;
    //Buff
    public byte[] readBuff;
    public int buffCount = 0;

    //構造函數
    public Conn()
    {
        readBuff = new byte[BUFFER_SIZE];
    }

    //初始化
    public void Init(Socket socket)
    {
        this.socket = socket;
        isUse = true;
        buffCount = 0;
    }

    //緩存區剩余的字節
    public int BuffRemain()
    {
        return BUFFER_SIZE - buffCount;
    }

    //獲取客戶端的地址
    public string GetAddress()
    {
        if (!isUse)
            return "獲取地址失敗";

        return socket.RemoteEndPoint.ToString();
    }

    //關閉
    public void Close()
    {
        if (isUse)
            return;

        Console.WriteLine(GetAddress() + " 斷開連接");
        socket.Close();
        isUse = false;
    }
}
View Code

2.服務器Socket

class UnityServer
{
    //監聽套接字
    public Socket listenSocket;
    //客戶端連接(異步)
    public Conn[] conns;
    //最大連接數
    public int maxCount = 100;

    //獲取連接池索引
    public int NewIndex()
    {
        if (conns == null)
            return -1;

        for(int i=0;i<conns.Length;i++)
        {
            if (conns[i] == null)
            {
                conns[i] = new Conn();
                return i;
            }
            else if(conns[i].isUse == false)
            {
                return i;
            }
        }
        return -1;
    }

    //開啟服務器
    public void Start(IPEndPoint iPEndPoint)
    {
        //連接對象池
        conns = new Conn[maxCount];
        for(int i=0;i<maxCount;i++)
        {
            conns[i] = new Conn();
        }
        //socket
        listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            
        //bind
 
        listenSocket.Bind(iPEndPoint);

        //listen
        listenSocket.Listen(maxCount);

        //Accept
        listenSocket.BeginAccept(AcceptCb, null);
    }

    //Accept回調函數
    private void AcceptCb(IAsyncResult asyncResult)
    {
        try
        {
            Socket socket = listenSocket.EndAccept(asyncResult);
            int index = NewIndex();

            if(index == -1)
            {
                Console.WriteLine("Conn連接池已滿!");
            }
            else
            {
                Conn conn = conns[index];
                conn.Init(socket);
                string addr = conn.GetAddress();
                Console.WriteLine("客戶端 [" + addr + "] 連接");
                Console.WriteLine("已連接 " + index + " 個客戶端");
                conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);
                listenSocket.BeginAccept(AcceptCb, null);
            }
        }
        catch(Exception ex)
        {
            Console.WriteLine("AcceptCb 失敗:" + ex.Message);
        }
    }

    //Receive回調函數
    private void ReceiveCb(IAsyncResult asyncResult)
    {
        //獲取接收對象
        Conn conn = (Conn) asyncResult.AsyncState;
        try
        {
            int count = conn.socket.EndReceive(asyncResult);
            //關閉信號
            if(count <= 0)
            {
                Console.WriteLine("收到 [" + conn.GetAddress() + "] 斷開連接");
                conn.Close();

                return;
            }

            //數據處理
            string str = conn.socket.RemoteEndPoint.ToString() +":" + Encoding.UTF8.GetString(conn.readBuff, 0, count);
            Console.WriteLine("[" + conn.GetAddress() + "] : " + str);

            byte[] sendMsg = Encoding.UTF8.GetBytes(str);
            //將所有的消息廣播
            for(int i=0;i<maxCount;i++)
            {
                if (conns[i] == null || !conns[i].isUse)
                    continue;
                conns[i].socket.Send(sendMsg);
                        
            }

            //繼續接收
            conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);

        }
        catch (Exception ex)
        {
            Console.WriteLine("[" + conn.GetAddress() + "] 斷開連接");
            conn.Close();
        }
    }        
}
View Code

3.服務器Main函數

class Program
{
    static void Main(string[] args)
    {
        int port = 1234;
        IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port);
        Console.WriteLine("服務器開啟...");

        UnityServer unityServer = new UnityServer();
        unityServer.Start(iPEndPoint);

        while(true)
        {
            string input = Console.ReadLine();
            if (input == "Q")
                return;
        }
            
    }
}
View Code

4.客戶端Socket

class Program
{
    //客戶端套接字
    static Socket clientSocket;
    static void Main(string[] args)
    {

        string IP = "127.0.01";
        IPAddress ip = IPAddress.Parse(IP);
        int port = 1234;

        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPEndPoint endPoint = new IPEndPoint(ip, port);

        //連接服務器
        try
        {
            clientSocket.Connect(endPoint);

            //發送字節
            string str = Console.ReadLine();
            byte[] message = Encoding.UTF8.GetBytes(str);
            clientSocket.Send(message);

            //接收服務器信息
            byte[] receive = new byte[1024];
            clientSocket.Receive(receive);
            Console.WriteLine("接收消息為:" + Encoding.UTF8.GetString(receive));

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            //關閉連接
            clientSocket.Close();
        }

        Console.ReadKey();

    }

}
View Code

5.測試結果

  啟動服務器,注意端口號和IP地址

  

  啟動客戶端,服務器顯示有客戶端連接上來(0個客戶端是因為是0開始計數的)

  

  客戶端發送消息,服務器將這個消息返回到所有在線的客戶端

  

  

總結:

    這只是一個簡單的Socket測試Demo,如果要用於真正的通信,就需要考慮封裝協議、加密數據、網絡異常處理、服務器負載、高並發能力等,后續會繼續學習如何完成一個小型的Unity游戲服務器

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM