天天看點

Zjh遊戲(一)伺服器連接配接、接收消息

使用VS2017建立一個類庫。

伺服器端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MyServer
{
   public class SocketPeer
    {
        private Socket serverSocket;
        /// <summary>
        /// 信号技術量,線程的擁塞控制
        /// </summary>
        private Semaphore semaphore;
        private ClientPeerPool clientPeerPool;//用戶端連接配接對象池
        public void StartServer(string ip,int port,int maxClient)
        {
            try
            {
                semaphore = new Semaphore(maxClient, maxClient);
                clientPeerPool = new ClientPeerPool(maxClient);
                for (int i = 0; i < maxClient; i++)
                {
                    ClientPeer client = new ClientPeer();
                    client.ReceiveArgs.Completed += ReceiveArgs_Completed;
                    clientPeerPool.Enqueue(client);
                }
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                serverSocket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
                serverSocket.Listen(maxClient);
                Console.WriteLine("伺服器啟動成功");
                //開始接收用戶端的連接配接
                StartAccept(null);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }


        /// <summary>
        /// 開始等待用戶端的連接配接
        /// </summary>
        private void StartAccept(SocketAsyncEventArgs e)
        {
            if (e==null)
            {
                e = new SocketAsyncEventArgs();
                e.Completed += E_Completed;
            }
            //result 為true 代表正在接收連接配接,連接配接成功以後會調用E_Completed事件
            //為false 代表接收成功
            bool result = serverSocket.AcceptAsync(e);
            if (result==false)
            {
                ProcessAccept(e);
            }
        }
        /// <summary>
        /// 異步接收連接配接完成後的回調
        /// </summary>
        private void E_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }
        /// <summary>
        /// 出來用戶端的連接配接
        /// </summary>
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            semaphore.WaitOne();//阻止目前線程,直到有用戶端釋放
            ClientPeer client = clientPeerPool.Dequeue();
            client.clientSocket = e.AcceptSocket;//得到Client  e.AcceptSocket 可以擷取目前連接配接的用戶端Sockt 
            Console.WriteLine(client.clientSocket.RemoteEndPoint+"用戶端連接配接成功");
            //接收消息
            StartReceive(client);
            e.AcceptSocket = null;
            StartAccept(e);//僞遞歸
        }

        /// <summary>
        /// 開始接收消息
        /// </summary>
        private void StartReceive(ClientPeer client)
        {
            try
            {
                //這裡需要一個異步套接字 在ClientPeer中定義一個,在構造方法中指派
                bool result = client.clientSocket.ReceiveAsync(client.ReceiveArgs);
                if (result == false)
                {
                    ProcessReceive(client.ReceiveArgs);
                }
            }
            catch (Exception e)
            {

                Console.WriteLine(e.Message);
            }
          

        }
        /// <summary>
        ///異步接收消息完畢後的回調
        /// </summary>
        private void ReceiveArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessReceive(e);
        }
        /// <summary>
        /// 處理資料的接收
        /// </summary>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            //UserToken 可以得到與這個套接字綁定的程式對象,在ClientPeer進行了綁定設定,這裡得到ClientPeer
            ClientPeer client = e.UserToken as ClientPeer;
            //判斷資料是否接收成功 SocketError.Success是否連接配接 BytesTransferred 獲得收到資料的長度
            if (client.ReceiveArgs.SocketError == SocketError.Success && client.ReceiveArgs.BytesTransferred > 0)
            {
                byte[] packet = new byte[client.ReceiveArgs.BytesTransferred];//資料的存儲
                //client.ReceiveArgs.Buffer 資料緩沖區,接收到的資料存放到這裡
                Buffer.BlockCopy(client.ReceiveArgs.Buffer, 0, packet, 0, client.ReceiveArgs.BytesTransferred);
                //讓Client自身處理接收到的消息
                client.ProcessReceive(packet);
                StartReceive(client);//僞遞歸
            }
            else//資料接收不成功
            {
                //接收的資料為0 代表斷開連接配接了
                if (client.ReceiveArgs.BytesTransferred==0)
                {
                    //用戶端主動斷開連接配接
                    if (client.ReceiveArgs.SocketError==SocketError.Success)
                    {
                        DisConnected(client,"用戶端主動斷開連接配接");
                    }
                    else//因為網絡原因,被動斷開連接配接
                    {
                        DisConnected(client, e.SocketError.ToString());
                    }
                }
            }
        }
        //斷開連接配接
        private void DisConnected(ClientPeer client,string reason)
        {
            try
            {
                if (client==null)
                {
                    throw new Exception("用戶端為空,無法斷開連接配接!");
                }
                Console.WriteLine(client.clientSocket.RemoteEndPoint + "用戶端斷開連接配接,原因:" + reason);
                //用戶端自己處理斷開連接配接
                client.DisConnected();

                clientPeerPool.Enqueue(client);//斷開連接配接的Client放到用戶端連接配接池中
                semaphore.Release();//釋放一個Client
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

           

用戶端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace MyServer
{
    public class ClientPeer
    {
        public Socket clientSocket { get; set; }
        public SocketAsyncEventArgs ReceiveArgs { get; set; }
        public ClientPeer()
        {
            ReceiveArgs = new SocketAsyncEventArgs();
            ReceiveArgs.UserToken = this;//綁定UserToken 通過UserToken獲得目前的ClientPeer
            ReceiveArgs.SetBuffer(new byte[2048],0,2048);//設定資料緩沖區,資料緩沖區在使用時,需要進行設定
        }

        /// <summary>
        /// 處理資料的接收
        /// </summary>
        /// <param name="packet"></param>
        public void ProcessReceive(byte[] packet)
        {

        }
        /// <summary>
        /// 斷開連接配接
        /// </summary>
        public void DisConnected()
        {

        }
    }
}
           

用戶端連接配接池

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyServer
{
  public  class ClientPeerPool
    {
        public Queue<ClientPeer> clientPeers;

        public ClientPeerPool(int maxCount)
        {
            clientPeers = new Queue<ClientPeer>(maxCount);
        }
        public void Enqueue(ClientPeer client)
        {
            clientPeers.Enqueue(client);
        }
        public ClientPeer Dequeue()
        {
            return clientPeers.Dequeue();
        }
    }
}

           

構造類

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyServer
{
    /// <summary>
    /// 構造類,避免粘包問題
    /// </summary>
    public class EncodeTools
    {
        /// <summary>
        /// 構造包 標頭加包尾
        /// </summary>
        public static byte[] EncodePacket(byte[] packet)
        {
            //using 不需要手動釋放 會自動釋放
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    //寫入標頭(資料長度,前四個位元組)
                    bw.Write(packet.Length);
                    //寫入包尾(資料) 解析時:先解析出標頭 得到資料長度 資料長度在和剩下資料長度進行比較
                    bw.Write(packet);
                    byte[] data = new byte[ms.Length];//擷取所有寫入流中的總長度
                    //ms.GetBuffer(); 傳回流中所有建立的無符号位元組的數組
                    Buffer.BlockCopy(ms.GetBuffer(), 0, data, 0, (int)ms.Length);
                    return data;
                }
            }
        }
        /// <summary>
        /// 解析包 這裡修改了緩沖區的位址,需要加引用參數ref 
        /// </summary>
        public static byte[] DecodePacket(ref List<byte> cache)
        {
            if (cache.Count < 4)//前4位為資料長度,不夠4位傳回null
            {
                return null;
            }

            using (MemoryStream ms = new MemoryStream(cache.ToArray()))
            {
                using (BinaryReader br = new BinaryReader(ms))
                {
                    int length = br.ReadInt32();//ReadInt32 讀取前四個位元組 得到資料長度
                    //Position擷取目前在流中的所在位置  ReadInt32讀取以後會讓遊标向後移4位 目前的總長度-減去遊标 
                    //得到剩下的長度
                    int remainLength = (int)(ms.Length - ms.Position);
                    if (length > remainLength)
                    {
                        return null;
                    }
                    byte[] data = br.ReadBytes(length);//讀取一個數組
                    cache.Clear();//更新資料緩存
                    //得到讀取後的位置
                    int remainLengthAngin = (int)(ms.Length - ms.Position);
                    //把後面的資料填充到緩沖區 解析後的清除掉
                    cache.AddRange(br.ReadBytes(remainLengthAngin));
                    return data;
                }
            }
        }
    }
}

           
Zjh遊戲(一)伺服器連接配接、接收消息

有需要學習視訊歡迎關注微信公衆号