IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 《Unity3D网络游戏实战》第6章 -> 正文阅读

[游戏开发]《Unity3D网络游戏实战》第6章

网络模块设计

对外接口

public static class NetManager {
	//连接Connect("127.0.0.1", 8888)
	public static void Connect(string ip, int port) {}

	//关闭连接
	public static void Close() {} 
	
	//发送数据,自动将MsgBase转换为二进制数据发送
	public static void Send(MsgBase msg) {}
	
	//Update,外部调用,用于驱动NetManager
	public static void Update() {}
	
	//添加消息事件监听
	public static void AddMsgListener(string msgName, MsgListener listener) {}
	
	//添加网络事件监听
	public static void AddEventListener(NetEvent netEvent, EventListener listener) {}
	
	//事件
	public enum NetEvent {
		ConnectSucc = 1,
		ConnectFail = 2,
		Close = 3,
	}
}

内部设计

public static class NetManager {
	//异步Socket回调函数将收到的消息按序存入msgList中
	//消息列表
	static List<MsgBase> msgList = new List<MsgBase>();
	
	//Update依次读取消息,根据监听表和协议名,调用相应的处理方法
	public static void Update() {
		MsgUpdate();
		PingUpdate();
	}
}
framework/
	NetManager.cs
	ByteArray.cs
	MsgBase.cs
proto/
	BattleMsg
	SysMsg
	......
public static class NetManager {
	//定义套接字
	static Socket socket;
	//接收缓冲区
	static ByteArray readBuff;
	//写入队列
	static Queue<ByteArray> writeQueue;
}

网络事件

事件类型

public enum NetEvent {
	ConnectSucc = 1,
	ConnectFail = 2,
	Close = 3,
}

监听列表

//事件委托类型
public delegate void EventListener(String err);

//事件监听列表
private static Dictionary<NetEvent, EventListener> eventListeners = new Dictionary<NetEvent, EventListener>();

//添加事件监听
public static void AddEventListener(NetEvent netEvent, EventListener listener) {
	if(eventListeners.ContainsKey(netEvent)) {//添加事件
		eventListeners[netEvent] += listener;
	} else {//新增事件
		eventListeners[netEvent] = listener;
	}
}

//删除事件监听
public static void RemoveEventListener(NetEvent netEvent, EventListener listener){
	if (eventListeners.ContainsKey(netEvent)){
		eventListeners[netEvent] -= listener;
	}
}

分发事件

private static void FireEvent(NetEvent netEvent, String err){
	if(eventListeners.ContainsKey(netEvent)){
		eventListeners[netEvent](err);
	}
}

连接服务端

Connect

//是否正在连接
static bool isConnecting = false;

//连接
public static void Connect(string ip, int port) {
	//状态判断
	if(socket!=null && socket.Connected){
		Console.WriteLine("Connect fail, already connected!");
		return;
	}
	if(isConnecting){
		Console.WriteLine("Connect fail, isConnecting");
		return;
	}
	//初始化成员
	InitState();
	//参数设置
	socket.NoDelay = true;
	//Connect
	isConnecting = true;
	socket.BeginConnect(ip, port, ConnectCallback, socket);
}

//初始化状态
private static void InitState(){
	//Socket
	socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	
	//接收缓冲区
	readBuff = new ByteArray();
	
	//写入队列
	writeQueue = new Queue<ByteArray>();
	
	//是否正在连接
	isConnecting = false;
}

ConnectCallback

//Connect回调
private static void ConnectCallback(IAsyncResult ar) {
	try{
		Socket socket = (Socket) ar.AsyncState;
		socket.EndConnect(ar);
		Console.WriteLine("Socket Connect Succ ");
		FireEvent(NetEvent.ConnectSucc,"");
	}catch (SocketException ex){
		Console.WriteLine("Socket Connect fail " + ex.ToString());
		FireEvent(NetEvent.ConnectFail, ex.ToString());
	}
	isConnecting = false;
} 

测试

public class Client {
	public static void Start() {
		NetManager.AddEventListener(NetManager.NetEvent.ConnectSucc, OnConnectSucc);
		NetManager.AddEventListener(NetManager.NetEvent.ConnectFail, OnConnectFail);
		NetManager.AddEventListener(NetManager.NetEvent.Close, OnConnectClose);
	}
	
	public static void Connect() {
		NetManager.Connect("127.0.0.1", 8888);
	}
	
	//连接成功回调
	public static void OnConnectSucc(string err){
		Console.WriteLine("OnConnectSucc");
		//TODO:进入游戏
	}
		
	//连接失败回调
	public static void OnConnectFail(string err){
		Console.WriteLine("OnConnectFail " + err);
		//TODO:弹出提示框(连接失败,请重试)
	}

	//关闭连接
	public static void OnConnectClose(string err){
		Console.WriteLine("OnConnectClose");
		//TODO:弹出提示框(网络断开)
		//TODO:弹出按钮(重新连接)
	}
	
	public static void Main(string[] args) {
		while(true) {
			string sendStr = Console.ReadLine();
			if(sendStr.Length==0)
				continue;
			else if(sendStr=="quit")
				break;
			else if(sendStr=="connect")
				Connect;
		}
	}
}

关闭连接

isClosing

static bool isClosing = false;
private static void InitState() {
	......
	//是否正在关闭
	isClosing = false;
}

Close

//关闭连接
public static void Close(){
	//状态判断
	if(socket==null || !socket.Connected){
		return;
	}
	if(isConnecting){
		return;
	}
	
	if(writeQueue.Count > 0) {//还有数据在发送
		isClosing = true;
	} else {//没有数据在发送
		socket.Close();
		FireEvent(NetEvent.Close, "");
	} 
} 

测试

public static void Close() {
	NetManager.Close();
}
public static void Main(string[] args) {
	while(true) {
		string sendStr = Console.ReadLine();
		if(sendStr.Length==0)
			continue;
		else if(sendStr=="quit")
			break;
		else if(sendStr=="close")
			Close();
	}
}

Json协议

协议类

协议类的核心功能,将协议对象转换为二进制数据(编码)和将二进制数据转换为协议对象(解码)。
Json协议,将协议对象转换为类似{“x”:100,“y”:200,“z”:300}的字符串,和将相应字符串转换为协议对象。

使用JsonUtility

public class MsgMove {
	public int x = 0;
	public int y = 0;
	public int z = 0;
}
MsgMove msgMove = new MsgMove();
msgMove.x = 100;
msgMove.y = *20;
string s = JsonUtility.ToJson(msgMove);
Console.WriteLine(s);

public class MsgAttack{
	public string desc = "127.0.0.1:6543";
}
string s = "{\"desc\":\"\127.0.0.1:1289"}";
MsgAttack msgAttack = JsonUtility.FromJson(s, Type.GetType("MsgAttack"));
Console.WriteLine(msgAttack.desc);


string s = "{\"x\":\"hehe"}";
MsgMove msgMove = new MsgMove();
JsonUtility.FromJsonOverwrite(s, msgMove);
Console.WriteLine(msgMove.x); //hehe无法解码,x设置为0

协议格式

//16 = 2 + 7 + 7
消息长度(2字节,比如16)+协议名长度(2字节,比如7)+协议名(比如MsgMove)+协议体(比如{“x”=1})

协议文件

定义MsgBase类为了实现处理消息的统一接口(OnMove(MsgBase msgBase)),方便实现Send方法(send(MsgBase msgBase))。

public class MsgBase {
	public string protoName = "";
}

public class MsgMove:MsgBase {
    public MsgMove() {protoName = "MsgMove";}

    public int x = 0;
    public int y = 0;
    public int z = 0;
}

public class MsgAttack:MsgBase {
    public MsgAttack() {protoName = "MsgAttack";}

    public string desc = "127.0.0.1:6543";
}

协议体的编码解码

using System;
using UnityEngine;

public class MsgBase{
	public string protoName = "";
	//编码
	public static byte[] Encode(MsgBase msgBase){
		string s = JsonUtility.ToJson(msgBase); 
		return System.Text.Encoding.UTF8.GetBytes(s);
	}

	//解码
	public static MsgBase Decode(string protoName, byte[] bytes, int offset, int count){
		string s = System.Text.Encoding.UTF8.GetString(bytes, offset, count);
		MsgBase msgBase = (MsgBase)JsonUtility.FromJson(s, Type.GetType(protoName));
		return msgBase;
	}
}

//编码函数调用

MsgMove msgMove = new MsgMove();
msgMove.x = 100;
msgMove.y = -20;
byte[] bytes = MsgBase.Encode(msgMove);
string s = System.Text.Encoding.UTF8.GetString(bytes);
Console.WriteLine(s);

//解码函数调用

string s = "{\"protoName\":\"MsgMove\",\"x\":100,\"y\":-20,\"z\":0}";
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(s);
MsgMove m = (MsgMove)MsgBase.Decode("MsgMove", bytes, 0, bytes.Length);
Console.WriteLine(m.x);
Console.WriteLine(m.y);
Console.WriteLine(m.z);

协议名的编码解码

using System;
using UnityEngine;

public class MsgBase{
	//编码协议名(2字节长度+字符串)
	public static byte[] EncodeName(MsgBase msgBase){
		//名字bytes和长度
		byte[] nameBytes = System.Text.Encoding.UTF8.GetBytes(msgBase.protoName);
		Int16 len = (Int16)nameBytes.Length;
		//申请bytes数值
		byte[] bytes = new byte[2+len];
		//组装2字节的长度信息
		bytes[0] = (byte)(len%256);
		bytes[1] = (byte)(len/256);
		//组装名字bytes
		Array.Copy(nameBytes, 0, bytes, 2, len);

		return bytes;
	}

	//解码协议名(2字节长度+字符串)
	public static string DecodeName(byte[] bytes, int offset, out int count){
		count = 0;
		//必须大于2字节
		if(offset + 2 > bytes.Length){
			return "";
		}
		//读取长度
		Int16 len = (Int16)((bytes[offset+1] << 8 )| bytes[offset] );
		//长度必须足够
		if(offset + 2 + len > bytes.Length){
			return "";
		}
		//解析
		count = 2+len;
		string name = System.Text.Encoding.UTF8.GetString(bytes, offset+2, len);
		return name;
	}
}

//协议名的编码解码函数调用

MsgMove msgMove = new MsgMove();
byte[] bs = MsgBase.EncodeName(msgMove);

int count;
string name = MsgBase.DecodeName(bs, 0, out count);
Console.WriteLine(name);
Console.WriteLine(count);

发送数据

Send

public static class NetManager {
	//发送数据
	public static void Send(MsgBase msg) {
		//状态判断
		if(socket==null || !socket.Connected){
			return;
		}
		if(isConnecting){
			return;
		}
		if(isClosing){
			return;
		} 
		//数据编码
		byte[] nameBytes = MsgBase.EncodeName(msg);
		byte[] bodyBytes = MsgBase.Encode(msg);
		int len = nameBytes.Length + bodyBytes.Length;
		byte[] sendBytes = new byte[2+len];
		//组装长度
		sendBytes[0] = (byte)(len%256);
		sendBytes[1] = (byte)(len/256);
		//组装名字
		Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
		//组装消息体
		Array.Copy(bodyBytes, 0, sendBytes, 2+nameBytes.Length, bodyBytes.Length);
		//写入队列
		ByteArray ba = new ByteArray(sendBytes);
		lock(writeQueue) {
			writeQueue.Enqueue(ba);
			if(writeQueue.Count==1) {//writeQueue的长度
				//send
				socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
			}
		}
	}
}

SendCallback

public static class NetManager {
	//Send回调
	public static void SendCallback(IAsyncResult ar) {
		//获取state、EndSend的处理
		Socket socket = (Socket) ar.AsyncState;
		//状态判断
		if(socket == null || !socket.Connected){
			return;
		}
		//EndSend
		int count = socket.EndSend(ar);

		ByteArray ba;
		lock(writeQueue) {
			ba = writeQueue.First();//调用BeginSend时Queue中至少存在一个元素
		}

		ba.readIdx += count;//会影响Queue队列中ByteArray的属性值
        if(ba.length == 0){//队列首端bytes已发送完毕
			lock(writeQueue){
				writeQueue.Dequeue();//删除首端
				if(writeQueue.Count>0) {//队列存在元素时继续发送
					ba = writeQueue.First();
					socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
				} else if(isClosing) {//队列不存在元素且正在关闭
					socket.Close();
				} 
			}
		} else {//继续发送Queue队列中ByteArray剩余字节
			socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
		}
	} 
}

测试

public static void Send(MsgBase msg) {
	NetManager.Send(msg);
}
public static void Main(string[] args) {
	while(true) {
		string sendStr = Console.ReadLine();
		if(sendStr.Length==0)
			continue;
		else if(sendStr=="quit")
			break;
		else if(sendStr=="send") {
			MsgMove msg = new MsgMove();
			msg.x = 120;
			msg.y = 123;
			msg.z = -6;
			NetManager.Send(msg);
		}
	}
}

消息事件

给不同的协议添加不同的回调方法。

//消息委托类型
public delegate void MsgListener(MsgBase msgBase);
//消息监听列表
private static Dictionary<string, MsgListener> msgListeners = new Dictionary<string, MsgListener>();
//添加消息监听
public static void AddMsgListener(string msgName, MsgListener listener) {
	if (msgListeners.ContainsKey(msgName)) {
		msgListeners[msgName] += listener;//添加
	} else {
		msgListeners[msgName] = listener;//新增
	}
}

//删除消息监听
public static void RemoveMsgListener(string msgName, MsgListener listener){
	if (msgListeners.ContainsKey(msgName)){
		msgListeners[msgName] -= listener;
	}
}
//分发消息
private static void FireMsg(string msgName, MsgBase msgBase) {
	if(msgListeners.ContainsKey(msgName)) {
		msgListeners[msgName](msgBase);
	}
}
NetManager.AddMsgListener("MsgMove", OnMsgMove);

//收到MsgMove协议
public void OnMsgMove (MsgBase msgBase) {
	MsgMove msg = (MsgMove)msgBase;
	//消息处理
	Console.WriteLine("OnMsgMove msg.x = " + msg.x);
	Console.WriteLine("OnMsgMove msg.y = " + msg.y);
	Console.WriteLine("OnMsgMove msg.z = " + msg.z);
}

接收数据

回调函数ReceiveCallback将消息存放到消息队列msgList中,主线程Update读取消息队列,一条条处理。

  • (1)每次Update处理多条数据,每一帧执行一次Update,处理多条数据。
  • (2)添加粘包半包、大小端判断等处理。
  • (3)使用Json协议。

新成员

//消息列表
static List<MsgBase> msgList = new List<MsgBase>();
//消息列表长度
static int msgCount = 0;
//每一次Update处理的消息量
readonly static int MAX_MESSAGE_FIRE = 10;

//初始化状态
private static void InitState(){
	//......
	//消息列表
	msgList = new List<MsgBase>();
	//消息列表长度
	msgCount = 0;
	//......
}

ConnectCallback

//Connect回调
private static void ConnectCallback(IAsyncResult ar) {
	try {
		//......
		//开始接收
		socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
	} catch (SocketException ex) {
		//......
	}
} 

ReceiveCallback

//Receive回调
public static void ReceiveCallback(IAsyncResult ar){
	try {
		Socket socket = (Socket) ar.AsyncState;
		//获取接收数据长度
		int count = socket.EndReceive(ar);
		if(count==0) {
			Close();
			return;
		}
		readBuff.writeIdx += count;
		//处理二进制消息
		OnReceiveData();
		//继续接收数据
		if(readBuff.remain < 8){
			//readBuff.MoveBytes();
			readBuff.ReSize(readBuff.length*2);
		}
		socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
	} catch (SocketException ex){
		Console.WriteLine("Socket Receive fail" + ex.ToString());
	}
}

OnReceiveData

//数据处理
public static void OnReceiveData(){
	//消息长度
	if(readBuff.length <= 2) {
		return;
	}
	//获取消息体长度
	int readIdx = readBuff.readIdx;
	byte[] bytes = readBuff.bytes; 
	Int16 bodyLength = (Int16)((bytes[readIdx+1] << 8 )| bytes[readIdx]);
	if(readBuff.length < bodyLength+2)
		return;
	readBuff.readIdx += 2; 
	//解析协议名
	int nameCount = 0;
	string protoName = MsgBase.DecodeName(readBuff.bytes, readBuff.readIdx, out nameCount);
	if(protoName == ""){
		Console.WriteLine("OnReceiveData MsgBase.DecodeName fail");
		return;
	}
	readBuff.readIdx += nameCount;
	//解析协议体
	int bodyCount = bodyLength - nameCount;
	MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
	readBuff.readIdx += bodyCount;
	readBuff.CheckAndMoveBytes();
	//添加到消息队列
	lock(msgList){
		msgList.Add(msgBase);
		msgCount++;
	}
	//继续读取消息
	if(readBuff.length > 2){
		OnReceiveData();
	}
}

Update

NetManage.Update实现每帧处理MAX_MESSAGE_FIRE(10)条消息。
(1)根据msgCount是否为0判断是否需要处理消息。
(2)循环中读取多条消息并处理。

//Update
public static void Update(){
	MsgUpdate();
	//PingUpdate();
}

//更新消息
public static void MsgUpdate(){
	//初步判断,提升效率
	if(msgCount == 0){
		return;
	}
	//重复处理消息
	for(int i = 0; i< MAX_MESSAGE_FIRE; i++){
		//获取第一条消息
		MsgBase msgBase = null;
		lock(msgList){
			if(msgList.Count > 0){
				msgBase = msgList[0];
				msgList.RemoveAt(0);
				msgCount--;
			}
		}
		if(msgBase != null){
			FireMsg(msgBase.protoName, msgBase); //分发消息
		} else {
			break;//没有消息了
		}
	}
}

测试

//开始
void Start(){
	NetManager.AddEventListener(NetManager.NetEvent.ConnectSucc, OnConnectSucc);
	NetManager.AddEventListener(NetManager.NetEvent.ConnectFail, OnConnectFail);
	NetManager.AddEventListener(NetManager.NetEvent.Close, OnConnectClose);
	NetManager.AddMsgListener("MsgMove", OnMsgMove);
}

//收到MsgMove协议
public void OnMsgMove (MsgBase msgBase) {
	MsgMove msg = (MsgMove)msgBase;
	//消息处理
	Console.WriteLine("OnMsgMove msg.x = " + msg.x);
	Console.WriteLine("OnMsgMove msg.y = " + msg.y);
	Console.WriteLine("OnMsgMove msg.z = " + msg.z);
}

// Update is called once per frame
void Update() {
	NetManager.Update();
}

心跳机制

客户端定时(如30秒)向服务端发送PING协议,服务端收到后回应PONG协议。
服务端长时间(如120秒)未收到PING协议,很可能是网络不通畅或客户端挂掉,服务端可以释放Socket资源。

PING和PONG协议

public class MsgPing:MsgBase {
    public MsgPing() {protoName = "MsgPing";}
}

public class MsgPong:MsgBase {
    public MsgPong() {protoName = "MsgPong";}
} 

成员变量

//是否启用心跳
public static bool isUsePing = true;
//心跳间隔时间
public static int pingInterval = 30;
//上一次发送PING的时间
static float lastPingTime = 0;
//上一次收到PONG的时间
static float lastPongTime = 0;

//初始化状态
private static void InitState(){
	//......
	//上一次发送PING的时间
	lastPingTime = Time.time;
	//上一次收到PONG的时间
	lastPongTime = Time.time;
	//......
}

发送PING协议

//发送PING协议
private static void PingUpdate(){
	//是否启用
	if(!isUsePing){
		return;
	}
	//发送PING
	if(Time.time - lastPingTime > pingInterval){
		MsgPing msgPing = new MsgPing();
		Send(msgPing);
		lastPingTime = Time.time;
	}
	//检测PONG时间
	if(Time.time - lastPongTime > pingInterval*4){
		Close();
	}
}

//Update
public static void Update(){
	MsgUpdate();
	PingUpdate();
}

监听PONG协议

//初始化状态
private static void InitState(){
	//......
	if(!msgListeners.ContainsKey("MsgPong")){
		AddMsgListener("MsgPong", OnMsgPong);
	}
}

//监听PONG协议
private static void OnMsgPong(MsgBase msgBase){
	lastPongTime = Time.time;
}

完整代码

proto

proto/
	MsgBase.cs
	BattleMsg.cs
	SysMsg.cs
csc proto\*.cs -t:library -out:proto.dll

MsgBase.cs

using System;
using System.Linq;
using System.Web.Script.Serialization;

public class MsgBase{
	public string protoName = "null";
	
	//编码器
	static JavaScriptSerializer Js = new JavaScriptSerializer();
	
	//编码
	public static byte[] Encode(MsgBase msgBase){
		string s = Js.Serialize(msgBase); 
		return System.Text.Encoding.UTF8.GetBytes(s);
	}

	//解码
	public static MsgBase Decode(string protoName, byte[] bytes, int offset, int count){
		string s = System.Text.Encoding.UTF8.GetString(bytes, offset, count);
		MsgBase msgBase = (MsgBase)Js.Deserialize(s, Type.GetType(protoName));
		return msgBase;
	}

	//编码协议名(2字节长度+字符串)
	public static byte[] EncodeName(MsgBase msgBase){
		//名字bytes和长度
		byte[] nameBytes = System.Text.Encoding.UTF8.GetBytes(msgBase.protoName);
		Int16 len = (Int16)nameBytes.Length;
		//申请bytes数值
		byte[] bytes = new byte[2+len];
		//组装2字节的长度信息
		bytes[0] = (byte)(len%256);
		bytes[1] = (byte)(len/256);
		//组装名字bytes
		Array.Copy(nameBytes, 0, bytes, 2, len);

		return bytes;
	}

	//解码协议名(2字节长度+字符串)
	public static string DecodeName(byte[] bytes, int offset, out int count){
		count = 0;
		//必须大于2字节
		if(offset + 2 > bytes.Length){
			return "";
		}
		//读取长度
		Int16 len = (Int16)((bytes[offset+1] << 8 )| bytes[offset] );
		//长度必须足够
		if(offset + 2 + len > bytes.Length){
			return "";
		}
		//解析
		count = 2+len;
		string name = System.Text.Encoding.UTF8.GetString(bytes, offset+2, len);
		return name;
	}
}

BattleMsg.cs

public class MsgMove:MsgBase {
    public MsgMove() {protoName = "MsgMove";}

    public int x = 0;
    public int y = 0;
    public int z = 0;
}


public class MsgAttack:MsgBase {
    public MsgAttack() {protoName = "MsgAttack";}

    public string desc = "127.0.0.1:6543";
} 

SysMsg.cs

public class MsgPing:MsgBase {
    public MsgPing() {protoName = "MsgPing";}
}

public class MsgPong:MsgBase {
    public MsgPong() {protoName = "MsgPong";}
} 

framework

framework/
	ByteArray.cs
	NetManager.cs
csc framework\*.cs -t:library -out:framework.dll -reference:proto.dll

ByteArray.cs

using System;

public class ByteArray  {
	//默认大小
	const int DEFAULT_SIZE = 1024;
	//缓冲区
	public byte[] bytes;
	//读写位置
	public int readIdx = 0;
	public int writeIdx = 0;
	//容量
	private int capacity = 0;
	//剩余空间
	public int remain { get { return capacity-writeIdx; }}
	//数据长度
	public int length { get { return writeIdx-readIdx; }}

	//构造函数
	public ByteArray(int size = DEFAULT_SIZE){
		bytes = new byte[size];
		capacity = size;
		readIdx = 0;
		writeIdx = 0;
	}

	//构造函数
	public ByteArray(byte[] defaultBytes){
		bytes = defaultBytes;
		capacity = defaultBytes.Length;
		readIdx = 0;
		writeIdx = defaultBytes.Length;
	}

	//重设尺寸
	public void ReSize(int size){
		if(size < capacity) 
			return;
		
		capacity = 1;
		while(capacity<size) 
			capacity *= 2;
		
		byte[] newBytes = new byte[capacity];
		Array.Copy(bytes, readIdx, newBytes, 0, length);
		bytes = newBytes;
		writeIdx = length;
		readIdx = 0;
	}

	//写入数据
	public int Write(byte[] bs, int offset, int count){
		ReSize(length + count);//容量不够才会扩容
		
		Array.Copy(bs, offset, bytes, writeIdx, count);
		writeIdx += count;
		return count;
	}

	//读取数据
	public int Read(byte[] bs, int offset, int count){
		count = Math.Min(count, length);
		Array.Copy(bytes, 0, bs, offset, count);
		readIdx += count;
		CheckAndMoveBytes();
		return count;
	}

	//检查并移动数据
	public void CheckAndMoveBytes(){
		if(length < 8){
			MoveBytes();
		}
	}

	//移动数据
	public void MoveBytes(){
		if(length>0) {
			Array.Copy(bytes, readIdx, bytes, 0, length);
		}
		writeIdx = length;
		readIdx = 0;
	} 

	//读取Int16
	public Int16 ReadInt16(){
		if(length < 2) 
			return 0;
		Int16 ret = BitConverter.ToInt16(bytes, readIdx);
		readIdx += 2;
		CheckAndMoveBytes();
		return ret;
	}

	//读取Int32
	public Int32 ReadInt32(){
		if(length < 4) 
			return 0;
		Int32 ret = BitConverter.ToInt32(bytes, readIdx);
		readIdx += 4;
		CheckAndMoveBytes();
		return ret;
	}
	
	//打印缓冲区
	public override string ToString(){
		return BitConverter.ToString(bytes, readIdx, length);//所有可读字节
	}

	//打印调试信息
	public string Debug(){
		return string.Format("readIdx({0}) writeIdx({1}) bytes({2})",//读序号,写序号,所有字节
			readIdx,
			writeIdx,
			BitConverter.ToString(bytes, 0, capacity)
		);
	}
}

NetManager.cs

using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System;
using System.Linq;

public static class NetManager {
	//定义套接字
	static Socket socket;
	
	//接收缓冲区
	static ByteArray readBuff;
	
	//写入队列
	static Queue<ByteArray> writeQueue;
	
	//是否正在连接
	static bool isConnecting = false;
	
	//是否正在关闭
	static bool isClosing = false;
	
	//消息列表
	static List<MsgBase> msgList = new List<MsgBase>();
	
	//消息列表长度
	static int msgCount = 0;
	
	//每一次Update处理的消息量
	readonly static int MAX_MESSAGE_FIRE = 10;
	
	//是否启用心跳
	public static bool isUsePing = true;
	
	//心跳间隔时间
	public static int pingInterval = 30;
	
	//上一次发送PING的时间
	static DateTime lastPingTime = DateTime.Now;
	
	//上一次收到PONG的时间
	static DateTime lastPongTime = DateTime.Now;

	//事件
	public enum NetEvent {
		ConnectSucc = 1,
		ConnectFail = 2,
		Close = 3,
	}
	
	//事件委托类型
	public delegate void EventListener(String err);
	
	//事件监听列表
	private static Dictionary<NetEvent, EventListener> eventListeners = new Dictionary<NetEvent, EventListener>();
	
	//添加事件监听
	public static void AddEventListener(NetEvent netEvent, EventListener listener) {
		if(eventListeners.ContainsKey(netEvent)) {//添加事件
			eventListeners[netEvent] += listener;
		} else {
			eventListeners[netEvent] = listener;//新增事件
		}
	}
	
	//删除事件监听
	public static void RemoveEventListener(NetEvent netEvent, EventListener listener) {
		if(eventListeners.ContainsKey(netEvent)) {
			eventListeners[netEvent] -= listener;
		}
	}
	
	//分发事件
	private static void FireEvent(NetEvent netEvent, String err) {
		if(eventListeners.ContainsKey(netEvent)) {
			eventListeners[netEvent](err);
		}
	}

	//消息委托类型
	public delegate void MsgListener(MsgBase msgBase);
	
	//消息监听列表
	private static Dictionary<string, MsgListener> msgListeners = new Dictionary<string, MsgListener>();
	
	//添加消息监听
	public static void AddMsgListener(string msgName, MsgListener listener) {	
		if(msgListeners.ContainsKey(msgName)) {
			msgListeners[msgName] += listener;//添加
		} else {
			msgListeners[msgName] = listener;//新增
		}
	}
	
	//删除消息监听
	public static void RemoveMsgListener(string msgName, MsgListener listener) {
		if(msgListeners.ContainsKey(msgName)) {
			msgListeners[msgName] -= listener;
		}
	}
	
	//分发消息
	private static void FireMsg(string msgName, MsgBase msgBase) {
		if(msgListeners.ContainsKey(msgName)) {
			msgListeners[msgName](msgBase);
		}
	}

	//连接
	public static void Connect(string ip, int port) {
		//状态判断
		if(socket!=null && socket.Connected){
			Console.WriteLine("Connect fail, already connected!");
			return;
		}
		if(isConnecting){
			Console.WriteLine("Connect fail, isConnecting");
			return;
		}
		
		//初始化成员
		InitState();
		
		//参数设置
		socket.NoDelay = true;
		
		//Connect
		isConnecting = true;
		socket.BeginConnect(ip, port, ConnectCallback, socket);
	}

	//初始化状态
	private static void InitState(){
		//Socket
		socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		//接收缓冲区
		readBuff = new ByteArray();
		//写入队列
		writeQueue = new Queue<ByteArray>();
		//是否正在连接
		isConnecting = false;
		//是否正在关闭
		isClosing = false;
		//消息列表
		msgList = new List<MsgBase>();
		//消息列表长度
		msgCount = 0;
		//上一次发送PING的时间
		lastPingTime = DateTime.Now;
		//上一次收到PONG的时间
		lastPongTime = DateTime.Now;
		//监听PONG协议
		if(!msgListeners.ContainsKey("MsgPong")){
			AddMsgListener("MsgPong", OnMsgPong);
		}
	}

	//Connect回调
	private static void ConnectCallback(IAsyncResult ar){
		try{
			Socket socket = (Socket) ar.AsyncState;
			socket.EndConnect(ar);
			Console.WriteLine("Socket Connect Succ ");
			FireEvent(NetEvent.ConnectSucc,"");
			//开始接收
			socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
		} catch (SocketException ex){
			Console.WriteLine("Socket Connect fail " + ex.ToString());
			FireEvent(NetEvent.ConnectFail, ex.ToString());
		}
		isConnecting = false;
	} 

	//关闭连接
	public static void Close(){
		//状态判断
		if(socket==null || !socket.Connected){
			return;
		}
		if(isConnecting){
			return;
		}
		
		if(writeQueue.Count > 0){//还有数据在发送
			isClosing = true;
		} else {//没有数据在发送
			socket.Close();
			FireEvent(NetEvent.Close, "");
		} 
	} 

	//发送数据
	public static void Send(MsgBase msg) {
		//状态判断
		if(socket==null || !socket.Connected){
			return;
		}
		if(isConnecting){
			return;
		}
		if(isClosing){
			return;
		} 
		//数据编码
		byte[] nameBytes = MsgBase.EncodeName(msg);
		byte[] bodyBytes = MsgBase.Encode(msg);
		int len = nameBytes.Length + bodyBytes.Length;
		byte[] sendBytes = new byte[2+len];
		//组装长度
		sendBytes[0] = (byte)(len%256);
		sendBytes[1] = (byte)(len/256);
		//组装名字
		Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
		//组装消息体
		Array.Copy(bodyBytes, 0, sendBytes, 2+nameBytes.Length, bodyBytes.Length);
		//写入队列
		ByteArray ba = new ByteArray(sendBytes);
		lock(writeQueue){
			writeQueue.Enqueue(ba);
			//send
			if(writeQueue.Count == 1){//writeQueue的长度
				socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
			}
		}
	}

	//Send回调
	public static void SendCallback(IAsyncResult ar){
		//获取state、EndSend的处理
		Socket socket = (Socket) ar.AsyncState;
		//状态判断
		if(socket == null || !socket.Connected){
			return;
		}
		//EndSend
		int count = socket.EndSend(ar); 
		ByteArray ba;
		lock(writeQueue){
			ba = writeQueue.First();//调用BeginSend时Queue中至少存在一个元素
		}
		//完整发送
		ba.readIdx += count;
		if(ba.length == 0){//队列首端bytes已发送完毕
			lock(writeQueue){
				writeQueue.Dequeue();//删除首端
				if(writeQueue.Count>0) {//队列存在元素时继续发送
					ba = writeQueue.First();
					socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
				} else if(isClosing) {//正在关闭
					socket.Close();
				} 
			}
		} else {//继续发送Queue队列中ByteArray剩余字节
			socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
		}
	} 

	//Receive回调
	public static void ReceiveCallback(IAsyncResult ar){
		try {
			Socket socket = (Socket) ar.AsyncState;
			//获取接收数据长度
			int count = socket.EndReceive(ar);
			readBuff.writeIdx += count;
			//处理二进制消息
			OnReceiveData();
			//继续接收数据
			if(readBuff.remain < 8){
				//readBuff.MoveBytes();//多余
				readBuff.ReSize(readBuff.length*2);
			}
			socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
		} catch (SocketException ex){
			Console.WriteLine("Socket Receive fail" + ex.ToString());
		}
	}

	//数据处理
	public static void OnReceiveData(){
		//消息长度
		if(readBuff.length <= 2) {
			return;
		}
		//获取消息体长度
		int readIdx = readBuff.readIdx;
		byte[] bytes = readBuff.bytes; 
		Int16 bodyLength = (Int16)((bytes[readIdx+1] << 8 )| bytes[readIdx]);
		if(readBuff.length < bodyLength+2)
			return;
		readBuff.readIdx += 2; 
		//解析协议名
		int nameCount = 0;
		string protoName = MsgBase.DecodeName(readBuff.bytes, readBuff.readIdx, out nameCount);
		if(protoName == ""){
			Console.WriteLine("OnReceiveData MsgBase.DecodeName fail");
			return;
		}
		readBuff.readIdx += nameCount;
		//解析协议体
		int bodyCount = bodyLength - nameCount;
		MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
		readBuff.readIdx += bodyCount;
		readBuff.CheckAndMoveBytes();
		//添加到消息队列
		lock(msgList){
			msgList.Add(msgBase);
			msgCount++;
		}
		//继续读取消息
		if(readBuff.length > 2){
			OnReceiveData();
		}
	}

	//Update
	public static void Update(){
		MsgUpdate();
		PingUpdate();
	}

	//更新消息
	public static void MsgUpdate(){
		//初步判断,提升效率
		if(msgCount == 0){
			return;
		}
		//重复处理消息
		for(int i = 0; i< MAX_MESSAGE_FIRE; i++){
			//获取第一条消息
			MsgBase msgBase = null;
			lock(msgList){
				if(msgList.Count > 0){
					msgBase = msgList[0];
					msgList.RemoveAt(0);
					msgCount--;
				}
			}
			
			if(msgBase != null){
				FireMsg(msgBase.protoName, msgBase);//分发消息
			}else{//没有消息了
				break;
			}
		}
	}

	//发送PING协议
	private static void PingUpdate(){
		//是否启用
		if(!isUsePing){
			return;
		}
		//发送PING
		if((DateTime.Now - lastPingTime).Seconds > pingInterval){
			MsgPing msgPing = new MsgPing();
			Send(msgPing);
			lastPingTime = DateTime.Now;
		}
		//检测PONG时间
		if((DateTime.Now - lastPongTime).Seconds > pingInterval*4){
			Close();
		}
	}

	//监听PONG协议
	private static void OnMsgPong(MsgBase msgBase){
		lastPongTime = DateTime.Now;
	}
}

Server

Server.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;

class ClientState {
	public Socket socket;
	public ByteArray readBuff = new ByteArray();
}

class Server {
	//监听Socket
	static Socket listenfd;
	
	//客户端Socket及状态信息
	static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();

	public static void Main(string[] args) {
		//Socket
		listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		
		//Bind
		IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
		IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
		listenfd.Bind(ipEp);
		
		//Listen
		listenfd.Listen(0);
		
		Console.WriteLine("[服务器]启动成功");
		//主循环
		while(true){
			//检查listenfd
			if(listenfd.Poll(0, SelectMode.SelectRead)){
				ReadListenfd(listenfd);
			}
			//检查clientfd
			foreach(ClientState s in clients.Values){
				Socket clientfd = s.socket;
				if(clientfd.Poll(0, SelectMode.SelectRead)){
					if(!ReadClientfd(clientfd)){
						break;
					}
				}
			}
			//防止cpu占用过高
			System.Threading.Thread.Sleep(1);
		}
	}

	//读取Listenfd
	public static void ReadListenfd(Socket listenfd) {
		Console.WriteLine("[Accept]");
		Socket clientfd = listenfd.Accept();
		ClientState state = new ClientState();
		state.socket = clientfd;
		clients.Add(clientfd, state);
	}

	//读取Clientfd
	public static bool ReadClientfd(Socket clientfd) {
		ClientState state = clients[clientfd];
		ByteArray ba = state.readBuff;
		if(ba.remain<8){
			ba.ReSize(ba.length*2);
        }
		int count = clientfd.Receive(ba.bytes, ba.writeIdx, ba.remain, 0);
		//客户端关闭
		if(count == 0){
			clientfd.Close();
			clients.Remove(clientfd);
			Console.WriteLine("Socket Close");
			return false;
		}
		ba.writeIdx += count;
        if (ba.length <= 2){
			return true;
		}
		Int16 bodyLength = BitConverter.ToInt16(ba.bytes, 0);
		if(ba.length < 2 + bodyLength)
			return true;
		
		//解析协议名
		int nameCount = 0;
		string protoName = MsgBase.DecodeName(ba.bytes, 2, out nameCount);
		if(protoName == ""){
			Console.WriteLine("MsgBase.DecodeName fail");
			return true;
		}
		Console.WriteLine("[ProtoName]" + protoName);
		
		//解析协议体
		int bodyCount = bodyLength - nameCount;
		MsgBase msgBase = MsgBase.Decode(protoName, ba.bytes, 2+nameCount, bodyCount);
		Console.WriteLine("[MsgBase]" + msgBase);
		
		byte[] sendBytes = new byte[ba.length];
		Array.Copy(ba.bytes, 0, sendBytes, 0, ba.length);
		ba.readIdx = 0;
		ba.writeIdx = 0;
		//广播
		foreach(ClientState cs in clients.Values) {
			cs.socket.Send(sendBytes);
		}
		return true;
	}
}
csc Server.cs -reference:proto.dll,framework.dll
Server

Client

Client.cs

using System.Collections.Generic;
using System.Net.Sockets;
using System;
using System.Linq;

public class Client {
	//开始
	static void Start(){
		NetManager.AddEventListener(NetManager.NetEvent.ConnectSucc, OnConnectSucc);
		NetManager.AddEventListener(NetManager.NetEvent.ConnectFail, OnConnectFail);
		NetManager.AddEventListener(NetManager.NetEvent.Close, OnConnectClose);

		NetManager.AddMsgListener("MsgMove", OnMsgMove);
	}

	//收到MsgMove协议
	public static void OnMsgMove(MsgBase msgBase) {
		MsgMove msg = (MsgMove)msgBase;
		//消息处理
		Console.WriteLine("OnMsgMove msg.x = " + msg.x);
		Console.WriteLine("OnMsgMove msg.y = " + msg.y);
		Console.WriteLine("OnMsgMove msg.z = " + msg.z);
	}

	//玩家点击连接按钮
	public static void OnConnectClick() {
		NetManager.Connect("127.0.0.1", 8888);
		//TODO:开始转圈圈
	}

	//主动关闭
	public static void OnCloseClick() {
		NetManager.Close();
		//TODO:开始转圈圈
	}

	//玩家点击发送按钮
	public static void OnMoveClick() {
		MsgMove msg = new MsgMove();
		msg.x = 120;
		msg.y = 123;
		msg.z = -6;
		NetManager.Send(msg);
	}

	//连接成功回调
	static void OnConnectSucc(string err){
		Console.WriteLine("OnConnectSucc");
		//TODO:进入游戏
	}
		
	//连接失败回调
	static void OnConnectFail(string err){
		Console.WriteLine("OnConnectFail " + err);
		//TODO:弹出提示框(连接失败,请重试)
	}

	//关闭连接
	static void OnConnectClose(string err){
		Console.WriteLine("OnConnectClose");
		//TODO:弹出提示框(网络断开)
		//TODO:弹出按钮(重新连接)
	}

	// Update is called once per frame
	static void Update() {
		NetManager.Update();
	}
	
	public static void Main(string[] args) {
		Start();
		while(true) {
			string sendStr = Console.ReadLine();
			if(sendStr.Length==0)
				continue;
			else if(sendStr=="quit")
				break;
			else if(sendStr=="connect")
				OnConnectClick();
			else if(sendStr=="close")
				OnCloseClick();
			else if(sendStr=="move")
				OnMoveClick();
			Update();
		}
		OnCloseClick();
	}
}
csc Client.cs -reference:proto.dll,framework.dll
Client

Protobuf协议

什么是Protobuf

Protobuf是谷歌发布的一套协议规范,规定了一系列的编码和解码方法,编码后数据量较小,可以节省网络宽带。

编写proto文件

BattleMsg.proto

message MsgMove{
	optional int32 x = 1;
	optional int32 y = 2;
	optional int32 z = 3;
}

message MsgAttack{
	optional string desc = 1;
}

SysMsg.proto

message MsgPing{

}

message MsgPong{

}

生成协议类

Protobuf-net库,提供将Protobuf描述文件转换为协议类的工具,实现协议对象编码解码的方法。

protobuf-net下载地址:
https://github.com/mgravell/protobuf-net
https://code.google.com/p/protobuf-net

protogen.exe -i:proto\BattleMsg.proto -o:cs\BattleMsg.cs
protogen.exe -i:proto\SysMsg.proto -o:cs\SysMsg.cs
pause

导入protobuf-net.dll

复制protobuf-net.dll库文件

编码解码

  1. 编码方法
using System;
using System.Collections;
using System.Collections.Generic;
using proto.BattleMsg;

public class TestProtobuf {
	
	public static byte[] Encode(ProtoBuf.IExtensible msgBase) {
		using(var memory = new System.IO.MemoryStream()) {
			ProtoBuf.Serializer.Serialize(memory, msgBase);
			return memory.ToArray();
		}
		
	}
	
	static void Start() {
		MsgMove msgMove = new MsgMove();
		msgMove.x = 214;
		byte[] bs = Encode(msgMove);
		Console.WriteLine(System.BitConverter.ToString(bs));
	}

	public static void Main(string[] args) {
		Start();
	}
}

BattleMsg.cs由protogen.exe根据BattleMsg.proto描述文件生成。

csc TestProtobuf.cs BattleMsg.cs -reference:protobuf-net.dll
  1. 获取协议名字
MsgMove msgMove = new MsgMove();
//获取协议名,proto.BattleMsg.MsgMove
Console.WriteLine(msgMove.ToString());
  1. 解码
//解码
public static ProtoBuf.IExtensible Decode(string protoName, byte[] bytes, int offset, int count) {
	using(var memory = new System.IO.MemoryStream(bytes, offset, count)) {
		System.Type t = System.Type.GetType(protoName);
		return (ProtoBuf.IExtensible)ProtoBuf.Serializer.NonGeneric.Deserialize(t, memory);
	}
}
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-02-04 11:20:42  更:2022-02-04 11:21:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 18:32:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码