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

来源:互联网 发布:js手机号正则表达式 编辑:程序博客网 时间:2024/04/30 23:07

博客地址: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{    /// <summary>    /// 该类是主类  框架主入口(如果需要修改主入口函数 需要在Program类中修改)    /// </summary>    public class GameContext : Application    {        /// <summary>        /// 客户端接入函数        /// </summary>        /// <param name="socket"></param>        /// <returns></returns>        public BasePeer CreatePeer(Socket socket)        {            BasePeer peer = new ClientPeer(socket);            ServerMgr.GetInstance.peerList.Add(peer);             return peer;        }        /// <summary>        /// 框架启动函数        /// </summary>        public void Setup()        {            Console.WriteLine("启动框架");                }        /// <summary>        /// 框架取消函数        /// </summary>        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<byte, object> parameters);                protected void InitSocket()        {            //开启线程            BaseThread thread = new ReceiveThread(this);            thread.Start();        }        public void OnOperationResponse(Dictionary<byte, object> parameters)        {            //向客户端发送消息            //序列化            string message = DictToPackageHelper.Serializa(parameters);            Package package = new Package() { type = PackageType.Response, parameters = message };            string packageMessage = XMLHelper.Serialze<Package>(package);            //字节转化            var date = ASCIIEncoding.UTF8.GetBytes(packageMessage);            mSocket.Send(date);        }        public void OnOperationEvent(Dictionary<byte, object> dict)        {            //向客户端发送消息            //序列化            string message = DictToPackageHelper.Serializa(dict);            Package package = new Package() { type = PackageType.Event, parameters = message };            message = XMLHelper.Serialze<Package>(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<byte, object> parameters)        {            Console.WriteLine(parameters.FirstOrDefault(q=>q.Key==1).Value.ToString());            OnOperationResponse(new Dictionary<byte, object>() { { 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<byte, object> 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<byte, object> 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<Package>(ASCIIEncoding.UTF8.GetString(bt));                Dictionary<byte, object> 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<byte, object>() { { 1, "你好" } });    }    private void OnDestroy()    {        if (peer != null)        {            peer.Disconnect();        }    }    public void OnOperationResponse(Dictionary<byte, object> paratemers)    {        Debug.Log(paratemers[0].ToString());    }    public void OnOperationEvent(Dictionary<byte, object> paratemers)    {            }}

其中主类需要实现ISocketListener接口

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

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

using System.Collections;using System.Collections.Generic;using UnityEngine;namespace LJLNet{    public interface ISocketListener    {        SocketTCPPeer peer { get; set; }        void OnOperationResponse(Dictionary<byte,object> paratemers);        void OnOperationEvent(Dictionary<byte, object> paratemers);    }}

检测

运行服务器端与客户端

日志显示如下


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

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

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




原创粉丝点击