基于Socket的游戏服务器通信框架的设计与实现

博客地址:blog.liujunliang.com.cn

开发工具:VS2017、Unity2017

本文介绍使用Socket/TCP来开发客户端与服务器端通信框架

博主使用过PhotonServer,由于其简单使用,所以本文模仿PhotonServer服务器框架来编写的

其中可以参考博主之前写的文章Unity3d与PhotonServer通信、Unity3d Socket网络编程

接下来介绍自己编写的一个基于Socket的游戏服务器通信框架的设计与实现

服务器端

客户端的连接请求与每个客户端的数据接收是通过线程来处理

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

namespace LJLNet
{
    class Program
    {
        private static LJLNet.Application.Application application;

        static void Main(string[] args)
        {            
            //主类 主入口类(可配置)
            application = new GameContext();
            application.Setup();
           
            //绑定监听消息IP和端口号(可配置ip地址和端口号)
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            EndPoint endPoint = new IPEndPoint(ip, 6000);

            //创建一个socket对象
            //寻址方式 套接字类型 协议方式  
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(endPoint);//向操作系统申请一个ip和端口号  
            Console.WriteLine("服务器端启动完成");


            //开始监听客户端的连接请求
            serverSocket.Listen(100);//最多可以接收100个客户端请求  

            //开启线程 来接收客户端请求
            BaseThread thread = new AcceptThread(application, serverSocket);
            thread.Start();

            //断开连接
        }
    }
}

主入口类(框架的启动类),框架中的唯一个入口类

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

namespace LJLNet.Application
{
    /// 
    /// 该类是主类  框架主入口(如果需要修改主入口函数 需要在Program类中修改)
    /// 
    public class GameContext : Application
    {
        /// 
        /// 客户端接入函数
        /// 
        /// 
        /// 
        public BasePeer CreatePeer(Socket socket)
        {
            BasePeer peer = new ClientPeer(socket);
            ServerMgr.GetInstance.peerList.Add(peer); 

            return peer;
        }



        /// 
        /// 框架启动函数
        /// 
        public void Setup()
        {
            Console.WriteLine("启动框架");        
        }



        /// 
        /// 框架取消函数
        /// 
        public void TearDown()
        {
            Console.WriteLine("停止框架");
        }
    }
}


当我们的一个客户端连接进服务器时候,便创建一个ClientPeer对象(客户端)

客户端支持数据的接收与响应、事件

其基类如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using LJLNet.Thread;
using LJLCommon.Helper;
using LJLCommon.Package;

namespace LJLNet.SocketTCPPeer
{
    public abstract class BasePeer
    {
        public Socket mSocket { get; set; }

        public BasePeer(Socket socket)
        {
            mSocket = socket;
            //启动socket
            InitSocket();
        }

        public abstract void OnDisconnect();
        public abstract void OnOperationRequest(Dictionary parameters);        

        protected void InitSocket()
        {
            //开启线程
            BaseThread thread = new ReceiveThread(this);
            thread.Start();
        }

        public void OnOperationResponse(Dictionary parameters)
        {
            //向客户端发送消息
            //序列化
            string message = DictToPackageHelper.Serializa(parameters);

            Package package = new Package() { type = PackageType.Response, parameters = message };
            string packageMessage = XMLHelper.Serialze(package);

            //字节转化
            var date = ASCIIEncoding.UTF8.GetBytes(packageMessage);
            mSocket.Send(date);
        }

        public void OnOperationEvent(Dictionary dict)
        {
            //向客户端发送消息
            //序列化
            string message = DictToPackageHelper.Serializa(dict);

            Package package = new Package() { type = PackageType.Event, parameters = message };
            message = XMLHelper.Serialze(package);

            //字节转化
            var date = ASCIIEncoding.UTF8.GetBytes(message);
            mSocket.Send(date);
        }
    }
}

ClientPeer.cs

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

namespace LJLNet.SocketTCPPeer
{
    public class ClientPeer : BasePeer
    {
        public ClientPeer(Socket socket) : base(socket)
        {
        }
         
        public override void OnDisconnect()
        {
            
        }

        public override void OnOperationRequest(Dictionary parameters)
        {
            Console.WriteLine(parameters.FirstOrDefault(q=>q.Key==1).Value.ToString());
            OnOperationResponse(new Dictionary() { { 0, "你好" } });
        }
    }
}

在客户端中开辟一个线程,用于接收数据请求

线程基类

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

namespace LJLNet.Thread
{
    public abstract class BaseThread
    {
        protected System.Threading.Thread mThread;

        public BaseThread()
        {
            mThread = new System.Threading.Thread(Run);
        }

        public virtual void Start()
        {
            mThread.Start();
        }
        protected abstract void Run();
        public void Stop()
        {
            mThread.Abort();
        }
    }
}

接收数据线程

using LJLNet.SocketTCPPeer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LJLCommon.Helper;
using System.Threading.Tasks;

namespace LJLNet.Thread
{
    public class ReceiveThread : BaseThread
    {
        private BasePeer mPeer;

        public ReceiveThread(BasePeer peer)
        {
            mPeer = peer;
        }

        //运行的内容
        protected override void Run()
        {
            if (mPeer.mSocket != null)
            {
                while (true)
                {
                    try
                    {
                        //从客户端接收消息
                        byte[] buffer = new byte[1024];//设置一个消息接收缓冲区  
                        mPeer.mSocket.Receive(buffer);//该状态处于一个暂停状态,知道接收到消息,并返回字节数  

                        Dictionary parameters = DictToPackageHelper.DeSerializa(ASCIIEncoding.UTF8.GetString(buffer));
                        mPeer.OnOperationRequest(parameters);                       
                    }
                    catch
                    {
                        Console.WriteLine("一个客户端断开连接");
                        //断开线程
                        this.Stop();
                        //将该客户端移除
                        mPeer.OnDisconnect();
                        ServerMgr.GetInstance.peerList.Remove(mPeer);
                        mPeer = null;
                    }
                }
            }
        }
    }
}

客户端

使用Unity3d作为游戏客户端

当连接服务器时创建一个SocketTCPPeer对象(客户端),客户端中主要处理数据的发送和响应与事件的接收

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using LJLCommon.Helper;


namespace LJLNet
{
    public class SocketTCPPeer
    {
        private BaseThread mBaseThread;

        public ISocketListener mMono;

        public Socket mTcpSocket;

        public SocketTCPPeer(ISocketListener mono)
        {
            //创建socket  
            mTcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            mMono = mono;
        }

        //连接服务器  
        public void Connect(string ip, int point)
        {
            if (mTcpSocket != null)
            {
                mTcpSocket.Connect(IPAddress.Parse(ip), point);
                if (mTcpSocket.Connected)
                {
                    mBaseThread = new ReceiveThread(this);
                    mBaseThread.Start();
                }
            }
        }

        //发送数据
        public void OnOperationRequest(byte operationType, Dictionary dataDict)
        {
            if (mTcpSocket != null && mTcpSocket.Connected)
            {
                string parameters = null;
                try
                {
                    parameters = DictToPackageHelper.Serializa(dataDict);
                }
                catch
                {
                    Debug.LogError("字典容器序列化失败");
                }

                if (!string.IsNullOrEmpty(parameters))
                {
                    mTcpSocket.Send(ASCIIEncoding.UTF8.GetBytes(parameters));
                }
            }
            else
            {
                Debug.Log("与服务器断开连接");
            }
        }

        //断开连接
        public void Disconnect()
        {
            if (mBaseThread != null)
            {
                mBaseThread.Stop();
            }
            if (mTcpSocket != null)
            {
                mTcpSocket.Close();
            }            
        }
    }
}


其中在客户端中开辟一个线程用于对数据的接收(与服务器端类似)

线程基类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.Net.Sockets;

public abstract class BaseThread
{
    protected Thread mThread;

    public BaseThread()
    {
        mThread = new Thread(Run);
    }

    public virtual void Start()
    {
        mThread.Start();
    }
    public abstract void Run();
    public void Stop()
    {
        mThread.Abort();
    }
}

接收响应线程类

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using LJLNet;
using LJLCommon.Package;
using LJLCommon.Helper;
using System.Text;

public class ReceiveThread : BaseThread
{
    private SocketTCPPeer mSocketTCPPeer;

    public ReceiveThread(SocketTCPPeer socketTCPPeer)
    {
        mSocketTCPPeer = socketTCPPeer;
    }

    public override void Run()
    {
        if ( mThread != null && mSocketTCPPeer.mTcpSocket != null&& mSocketTCPPeer.mTcpSocket.Connected )
        {
            try
            {
                //socket接收消息  
                byte[] bt = new byte[1024];
                mSocketTCPPeer.mTcpSocket.Receive(bt);
                Debug.Log("接收响应");
                //对数据进行处理
                Package package = XMLHelper.Deserialze(ASCIIEncoding.UTF8.GetString(bt));
                Dictionary parameters = DictToPackageHelper.DeSerializa(package.parameters);
                //回调给主类处理           
                switch (package.type)
                {
                    case PackageType.Response:
                        mSocketTCPPeer.mMono.OnOperationResponse(parameters);
                        break;
                    case PackageType.Event:
                        mSocketTCPPeer.mMono.OnOperationEvent(parameters);
                        break;
                    default:
                        break;
                }
            }
            catch
            {
                //断开线程
                Stop();
                //断开连接
                Debug.Log("与服务器断开连接");
                mSocketTCPPeer.Disconnect();
            }
        }
    }
}

启动客户端-------创建一个Monobehavior脚本挂载到Camera上

在客户端启动时候创建SocketTCPPeer与服务器端进行连接并发送数据

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LJLNet;
using System.Text;
using System;

public class GameContext : MonoBehaviour, ISocketListener
{
    public SocketTCPPeer peer { get; set; }

    private void Start()
    {
        peer = new SocketTCPPeer(this);
        peer.Connect("127.0.0.1", 6000);

        peer.OnOperationRequest(1, new Dictionary() { { 1, "你好" } });
    }

    private void OnDestroy()
    {
        if (peer != null)
        {
            peer.Disconnect();
        }
    }

    public void OnOperationResponse(Dictionary paratemers)
    {
        Debug.Log(paratemers[0].ToString());
    }

    public void OnOperationEvent(Dictionary paratemers)
    {
        
    }
}

其中主类需要实现ISocketListener接口

将该接口传入到客户端SocketTCPPeer类中,当处理数据的响应与事件时候调用

方便框架的管理与简单使用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace LJLNet
{
    public interface ISocketListener
    {
        SocketTCPPeer peer { get; set; }

        void OnOperationResponse(Dictionary paratemers);
        void OnOperationEvent(Dictionary paratemers);
    }
}


检测

运行服务器端与客户端

日志显示如下

基于Socket的游戏服务器通信框架的设计与实现_第1张图片

该框架支持多人在线游戏的开发

可以参考博主编写的PhotonServer服务器MMO多人在线游戏开发一文

博客地址:blog.liujunliang.com.cn



你可能感兴趣的:(游戏服务器)