开发工具: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);
}
}
检测
运行服务器端与客户端
日志显示如下
该框架支持多人在线游戏的开发