io-game文档地址https://www.yuque.com/iohao/game/ywe7uc#ooyta
io-game游戏服务端
序列化使用的是百度的jprotobuf,io-game文档里有详细教程。
简单看说一下配置,使用tcp协议。
不贴代码了。io-game文档地址https://www.yuque.com/iohao/game/ywe7uc#ooyta
unity客户端
我用的是protobuf-net框架序列化c#类。
protobuf-net导入unity网上有教程的。
直接贴代码。
SocketClient负责发送、接收数据。
CmdMgr是cmd工具类
ProtobufHelper是protobuf-net序列化工具类和创建消息体
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using ProtoBuf;
using System.IO;
using System.Text;
using code.socket.pojo;
using LitJson;
using Object = System.Object;
namespace code.socket {
public class SocketClient {
//客户端socket
private Socket clientSocket;
//服务器IP和端口
public string ServerIP;
public int ServerPort;
//收消息的线程
private Thread acceptMsgThread;
//构造函数
public SocketClient(string _serverIP, int _serverPort) {
ServerIP = _serverIP;
ServerPort = _serverPort;
}
/// <summary>
/// 连接服务器
/// </summary>
/// <returns></returns>
public bool ConnectServer() {
if (ServerIP == null || ServerPort == 0) {
return false;
}
try {
//初始化socket对象
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//初始化地址和端口号
IPAddress serverIp = IPAddress.Parse(ServerIP);
IPEndPoint endPoint = new IPEndPoint(serverIp, ServerPort);
//连接服务器
clientSocket.Connect(endPoint);
//新建线程,接受服务端下发的消息
acceptMsgThread = new Thread(AcceptMsgs);
acceptMsgThread.IsBackground = true;
acceptMsgThread.Start();
return true;
} catch (Exception e) {
Debug.Log("出现异常连接失败:" + e.Message);
return false;
}
}
/// <summary>
/// 收数据。
/// </summary>
private void AcceptMsgs() {
try {
while (true) {
Thread.Sleep(5);
int available = clientSocket.Available;
byte[] len = new byte[4];
int l = clientSocket.Receive(len);
if (l == 0) {
continue;
}
if (l != 4) {
// break;
byte[] bytes = new byte[clientSocket.ReceiveBufferSize];
int receive = clientSocket.Receive(buffer: bytes);
Debug.Log("出现错误了,已清空缓冲区,消息可能丢失");
}
Array.Reverse(len);
int length = System.BitConverter.ToInt32(len, 0);
byte[] dataBytes = new byte[length];
int dataLength = 0;
while (true) {
if (dataLength == length) {
break;
}
dataLength += clientSocket.Receive(dataBytes, dataLength, dataBytes.Length, SocketFlags.None);
}
int ll = dataLength + l;
Debug.Log("收到:" + available + ",,," + ll + ",,");
ProtobufHelper.HandleMsg(dataBytes);
// ProtobufHelper.Split(buffer, dataLength);
}
} catch (Exception e) {
Debug.Log(e.Message);
}
}
/// <summary>
/// 发送文本消息
/// </summary>
/// <param name="msg"></param>
public bool SubmitMsgToServer(byte[] bytes) {
if (clientSocket == null || !clientSocket.Connected) {
return false;
}
try {
//发送给服务端
clientSocket.Send(bytes);
return true;
} catch (Exception e) {
Console.WriteLine("客户端发送数据发生异常:" + e.ToString());
return false;
}
}
public void SendToServerAsync(byte[] data) {
try {
if (clientSocket == null || !clientSocket.Connected) {
Debug.Log("socket未连接,发送消息失败");
return;
}
//创建发送参数
SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs();
// sendEventArgs.RemoteEndPoint = endPoint;
//设置要发送的数据
sendEventArgs.SetBuffer(data, 0, data.Length);
//异步发送数据
clientSocket.SendAsync(sendEventArgs);
} catch (Exception e) {
Debug.Log("异步发送数据异常:" + e);
}
}
}
public class CmdMgr {
//获取cmd
public static int getCmd(int merge) {
return merge >> 16;
}
//获取subCmd
public static int getSubCmd(int merge) {
return merge & 0xFFFF;
}
//获取mergeCmd
public static int getMergeCmd(int cmd, int subCmd) {
return (cmd << 16) + subCmd;
}
}
public class ProtobufHelper {
public static byte[] CreateMsg(int cmd, int subCmd, Object obj) {
byte[] toByte = ProtobufHelper.SerializeToByte(obj);
Message message = new Message() {
cmdCode = cmd,
cmdMerge = CmdMgr.getMergeCmd(cmd, subCmd),
protocolSwitch = 1,
data = toByte
};
byte[] serializeToByte = ProtobufHelper.SerializeToByte(message);
byte[] b = BitConverter.GetBytes(serializeToByte.Length);
Array.Reverse(b);
byte[] bytes = ProtobufHelper.Combine2(b, serializeToByte);
return bytes;
}
public static void HandleMsg(byte[] data) {
Message message = ProtobufHelper.DeserializeFromByteAry_PB<Message>(data);
// Debug.Log(JsonMapper.ToJson(message));
Test01 test01 = ProtobufHelper.DeserializeFromByteAry_PB<Test01>(message.data);
Debug.Log(test01.name);
// Debug.Log(JsonMapper.ToJson(test01));
}
public static void Split(byte[] a, int length) {
byte[] c = new byte[4];
byte[] d = new byte[length - 4];
Array.Copy(a, 0, c, 0, 4);
Array.Copy(a, 4, d, 0, length - 4);
Array.Reverse(c);
int g = System.BitConverter.ToInt32(c, 0);
Debug.Log(g);
Message message = ProtobufHelper.DeserializeFromByteAry_PB<Message>(d);
Debug.Log(JsonMapper.ToJson(message));
Test01 test01 = ProtobufHelper.DeserializeFromByteAry_PB<Test01>(message.data);
Debug.Log(test01.name);
Debug.Log(JsonMapper.ToJson(test01));
}
/// <summary>
/// 将数组a,b进行合并
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
public static byte[] Combine2(byte[] a, byte[] b) {
byte[] c = new byte[a.Length + b.Length];
Array.Copy(a, 0, c, 0, a.Length);
Array.Copy(b, 0, c, a.Length, b.Length);
// Array.Reverse(c);
return c;
}
//对象序列化成序列化成文件
public static string Serialize<T>(T t) {
//MemoryStream继承了IDisposable接口,所以才能被using自动释放
//using 的作用为使用完之后就释放流对象
using (MemoryStream ms = new MemoryStream()) {
//使用protobuf程序集内置方法将对象t序列化到ms中
Serializer.Serialize<T>(ms, t);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
public static byte[] SerializeToByte<T>(T t) {
//MemoryStream继承了IDisposable接口,所以才能被using自动释放
//using 的作用为使用完之后就释放流对象
using (MemoryStream ms = new MemoryStream()) {
//使用protobuf程序集内置方法将对象t序列化到ms中
Serializer.Serialize<T>(ms, t);
return ms.ToArray();
}
}
public static string byteToString(byte[] bytes) {
string strResult = "";
for (var i = 0; i < bytes.Length; i++) {
string strTemp = System.Convert.ToString(bytes[i], 2);
strTemp = strTemp.Insert(0, new string('0', 8 - strTemp.Length));
strResult += strTemp + " ";
}
return strResult;
}
/// <summary>
/// 将字节数组反序列化为对象实例——ProtoBuf
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="arr">字节数组</param>
/// <returns></returns>
public static T DeserializeFromByteAry_PB<T>(byte[] arr) {
using (MemoryStream ms = new MemoryStream(arr))
return ProtoBuf.Serializer.Deserialize<T>(ms);
}
//根据字符串反序列化成对象
public static T DeSerialize<T>(string content) {
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(content))) {
T t = Serializer.Deserialize<T>(ms);
return t;
}
}
}
}
Message是发往服务器的协议,data属性就是我们需要的内容。
Test01就是测试类,序列化为byte[]赋值到Message data属性中。
Person没有大用
using System.Collections.Generic;
using ProtoBuf;
namespace code.socket.pojo {
[ProtoContract]
public class Message {
[ProtoMember(1)]
public int cmdCode { get; set; }
[ProtoMember(2)]
public int protocolSwitch { get; set; }
[ProtoMember(3)]
public int cmdMerge { get; set; }
[ProtoMember(4)]
public int responseStatus { get; set; }
[ProtoMember(5)]
public string validMsg { get; set; }
[ProtoMember(6)]
public byte[] data { get; set; }
[ProtoMember(7)]
public int msgId { get; set; }
}
[ProtoContract]
public class Test01 {
[ProtoMember(1)]
public string name;
[ProtoMember(2)]
public int age;
[ProtoMember(3)]
public List<string> list;
}
[ProtoContract]
class Person {
[ProtoMember(1)]
public string Name { get; set; }
[ProtoMember(2)]
public int Age { get; set; }
}
}
Socket01是我的初始测试类
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using code.socket;
using code.socket.pojo;
using UnityEngine;
public class Socket01 : MonoBehaviour {
void Start() {
SocketClient client = new SocketClient("127.0.0.1", 10100);
client.ConnectServer();
List<string> list = new List<string>();
list.Add("嘿嘿嘿");
list.Add("hhhhh");
Test01 hellorep = new Test01() {
name = "张江南嘿嘿嘿",
age = 20,
list = list,
};
byte[] toByte = ProtobufHelper.SerializeToByte(hellorep);
string byteToString = ProtobufHelper.byteToString(toByte);
// Debug.Log(byteToString);
StringBuilder builder1 = new StringBuilder();
for (var i = 0; i < toByte.Length; i++) {
builder1.Append(toByte[i] + " ");
}
// Debug.Log(builder1.ToString());
Message message = new Message() {
cmdCode = 1,
cmdMerge = CmdMgr.getMergeCmd(1, 0),
protocolSwitch = 1,
data = toByte
};
byte[] serializeToByte = ProtobufHelper.SerializeToByte(message);
// Debug.Log(ProtobufHelper.byteToString(serializeToByte));
StringBuilder builder = new StringBuilder();
for (var i = 0; i < serializeToByte.Length; i++) {
builder.Append(serializeToByte[i] + " ");
}
// Debug.Log(builder.ToString());
byte[] b = BitConverter.GetBytes(serializeToByte.Length);
Array.Reverse(b);
byte[] bytes = ProtobufHelper.Combine2(b, serializeToByte);
// Debug.Log(ProtobufHelper.byteToString(bytes));
Debug.Log("字节个数:" + bytes.Length);
client.SubmitMsgToServer(bytes);
for (int i = 1; i <= 10; i++) {
}
// Person person1 = new Person(){ Name = "小明同学", Age = 18 };
// //序列化
// string testInfo = ProtobufHelper.Serialize<Person>(person1);
// print("testInfo"+ testInfo);
// //反序列化
// Person person2 = ProtobufHelper.DeSerialize<Person>(testInfo);
// print("person2" + person2.Name +" " +person2.Age);
}
void Update() { }
}
Socket02是我测试数据量大的时候会不会丢失数据(接收数据的时候)。初步是没有的。
using System;
using System.Collections;
using System.Collections.Generic;
using code.socket;
using code.socket.pojo;
using UnityEngine;
public class Socket02 : MonoBehaviour {
void Start() {
SocketClient client = new SocketClient("127.0.0.1", 10100);
client.ConnectServer();
List<string> list = new List<string>();
list.Add("嘿嘿嘿");
list.Add("hhhhh");
String str = "张江南嘿嘿嘿酷酷酷哈哈哈呃呃呃;";
for (int i = 0; i < 5; i++) {
str += str;
}
Test01 helloRep = new Test01() {
name = str,
age = 20,
list = list,
};
for (int i = 1; i <= 10; i++) {
helloRep.name = helloRep.name + str + "===========" + i;
byte[] bytes = ProtobufHelper.CreateMsg(1, 0, helloRep);
Debug.Log(i + "---字节个数:" + bytes.Length);
// client.SubmitMsgToServer(bytes);
client.SendToServerAsync(bytes);
}
}
void Update() { }
}
缺点:
接收数据的时候,如何一直没有数据。就会一直空循环,所以我线程睡眠了一会。有没有一种像Java响应式socket一样的方法,socket缓冲区有数据了,在执行接收数据的方法。
列入:
while(clientSocket.hasDate()) {// 如果socket缓冲区没有数据会阻塞线程,缓冲区有数据了,hasData()才会返回true,继续执行方法
…
}