网络部分知识开发和应用

网络部分知识开发和应用

十月 15, 2022

网络模型

socket结构
在这里插入图片描述

iocp(Input/output completion port)

SocketAPI
输入/输出完成端口,常常使用在windows系统中,采用异步的方式对每个socket进行监听和接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
  public void Start(string host, int port) {

timer.Elapsed += new System.Timers.ElapsedEventHandler(HandleMainTimer);
timer.AutoReset = false;
timer.Enabled = true;


conns = new Conn[maxConn];
for (int i = 0; i < maxConn; i++) {
conns[i] = new Conn();
}
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAdr = IPAddress.Parse(host);
IPEndPoint ipEp = new IPEndPoint(ipAdr, port);
listenfd.Bind(ipEp);
listenfd.Listen(maxConn);
listenfd.BeginAccept(AcceptCb, null);
Console.WriteLine ("[服务器]启动成功");
}
private void AcceptCb(IAsyncResult ar) {
Socket socket = listenfd.EndAccept(ar);
try {
//接收客户端
int index = NewIndex();
if (index < 0) {
socket.Close();
Console.WriteLine("[警告]连接已满");
}
else {
Conn conn = conns[index];
conn.Init(socket);
string host = conn.GetAdress();
Console.WriteLine("客户端连接:[" + host + "] conn池ID:" + index);
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReciveCb, conn);//接收的同时调用ReciveCb回调函数
}
listenfd.BeginAccept(AcceptCb, null);//再次调用AcceprCb回调函数
}
catch (Exception e) {
Console.WriteLine("AccpetCb 失败:" + e.Message);
}
}
private void ReciveCb(IAsyncResult ar) {
Conn conn = (Conn)ar.AsyncState;//这个AsyncState就是上面那个BeginRecive函数里面最后一个参数
lock (conn) {
try {
int count = conn.socket.EndReceive(ar);//返回接收的字节数
//没有信息就关闭
if (count <= 0) {
Console.WriteLine("收到[" + conn.GetAdress() + "] 断开连接");
conn.Close();
return;
}
conn.buffCount += count;
ProcessData(conn);

//继续接收
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReciveCb, conn);
}
catch (Exception e) {
Console.WriteLine("Recive失败" + e.Message);
}
}
}

缺点

异步操作可能会产生死锁等线程问题

状态检测Poll

Poll函数
采用同步循环的方式进行监听

1
2
3
4
5
6
7
8
9
if(socket有可读数据)
socket.Receive()

if(socket缓冲区可写)
socket. Send ()

if(socket发生程序)
错误处理

服务器实现

1
2
3
4
5
6
7
8
9
初始化listenfd
初始化clients列表
while(true){
if(listenfd可读) Accept;
for(遍历clien七s列表) {
if(这个客户端可读) 消息处理;
}
}

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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("l27.0.0.l");
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);
}
}
}
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];
//接收
int count = 0;
try {
count = clientfd.Receive(state.readBuff);
}
catch (SocketException ex) {
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Receive SocketExcep七ion " + ex.ToString());
return false;
}


//客户端关闭
if (count == 0) {
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return false;
}
//广播

string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
Console.WriteLine("Receive" + recvStr);
string sendStr = clientfd.RemoteEndPoint.ToString() + ":" + recvStr;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
foreach (ClientState cs in clients.Values) {
cs.socket.Send(sendBytes);
}
return true;
}

注意

  1. 在主循环最后涸用了System.Threading. Thread.Sleep(I), 让程序挂起1毫秒, 这样做的目的是避免死循环, 让 CPU 有个短暂的喘息时间。
  2. ReadClientfd会返回true或false, 返回false表示该客户端断开(收到长度为
    0的数据)。 由于客户端断开后,ReadClientfd会删除clients列表中对应的客户端信息, 导致clients列表改变, 而ReadClientfd又是在foreach (ClientState s in clients.Values)的循环中被调用的, clients列表变化会导致遍历失败, 因此程序在检测到客户端关闭后将退出foreach循环。
  3. 是将 Poll 的超时时间设置为 0, 程序不会有任何等待。 如果设置较长的超时时间,服务端将无法及时处理多个客户端同时连接的情况。 当然 , 这样设置也会导致程序的CPU占用率很高

    缺点

    cpu占用率过高

    多路复用Select

    Select函数
    同时检测多个Socket的状态 在设性要监听的Socket列表后 , 如果有一个(或多个)Socket可读 (或可写 , 或发生错误信息), 那就返同这些可读的 Socket, 如果没有可读的 , 那就阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static Socket listenfd;
//客户端 Socket及状态信息
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, Client
public static void Main(string[] args) {
//Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, Proto
//Bind
IPAddress ipAdr = IPAddress.Parse("l27.0.0.l");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
//Listen
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
//checkRead
List<Socket> checkRead = new List<Socket>();
//主循环
while (true) {
//填充 checkRead列表
checkRead.Clear();
checkRead.Add(listenfd);
foreach (ClientState s in clients.Values) {
checkRead.Add(s.socket);
//select
Socket.Select(checkRead, null, null, 1000);
//检查可读对象
foreach (Socket s in checkRead) {
if (s == listenfd) {
ReadListenfd(s);
}
else {
ReadClientfd(s);
}
}
}
}
}

过程

服务端使用主循环结构 while(true){…} , 不断地调用 Select 检测 Socket 状态, 其步骤如下:

  1. 将监听 Socket (listenfd) 和客户端 Socket (遍历 clients 列表)添加到待检测 Socket可读状态的列表 checkList 中。
  2. 调用 Select, 程序中设置超时时间为 1 秒, 若 l 秒内没有任何可读信息, Select 方法将 checkList 列表变成空列表, 然后返回。
  3. 对 Select 处理后的每个 Socket 做处理, 如果监听 Socket (listenfd) 可读, 说明有客户端连接, 需凋用 Accept。 如果客户端 Socket 可读, 说明客户端发送了消息(或关闭), 将消息广播给所有客户端

    适用空间

    由于程序在 Update 中不停地检测数据, 性能较差。 商业上为了做到性能上的极致, 大多使用异步(或使用多线程模拟异步程序)。

    解决粘包问题的方法

    长度信息法

    长度信息法是指在每个数据包前面加上长度信息。 每次接收到数据后 , 先读取表示长度的字节 , 如果缓冲区的数据长度大千要取的字节数, 则取出相应的字节 , 否则等待下一次数据接收。
    在这里插入图片描述

固定长度法

固定一个长度发送消息时多余部分用其他字符填充

结束符号法

在消息最后加一个特殊字符

大端小端问题

来自于平台的不同导致高地址和低地址的顺序不同
在这里插入图片描述

相反则是大端模式,常用的X86结构是小端模式 , 很多的ARM、 DSP都为小端模式, 但KEIL CS I则为大端模式 , 有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。 也就是说市面上的手机有些采用大端模式, 有些采用小端模式

解决方案

采用Reverse统一顺序。

1
2
3
4
if(!BitConverter.IsLittleEndian){ 
Debug.Log("[Send] Reverse lenBytes");
lenBytes.Reverse();
}
1
2
3
4
5
6
7
8
9
10
11
12
//int16时
Int16 bodyLength = (short)((readBuff[1] <<8) | readBuff[O]);



//int32时
Int32 bodyLength =readBuff[O];
for(int i=1;i<4;i++)
{
bodyLength = (readBuff[i]<<(8*i))|bodyLength ;
}

BytesArray

用来储存发送和接收的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System;

public class ByteArray {
//默认大小
const int DEFAULT_SIZE = 1024;
//初始大小
int initSize = 0;
//缓冲区
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;
initSize = size;
readIdx = 0;
writeIdx = 0;
}

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

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

//写入数据
public int Write(byte[] bs, int offset, int count){
if(remain < 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(){
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)
);
}
}

异步处理

参数如下
这是客户端部分,当具体到服务器应该去除静态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定义套接字
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 float lastPingTime = 0;
//上一次收到PONG的时间
static float lastPongTime = 0;

异步连接

引用致Unity网络游戏实战第二版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//连接
public static void Connect(string ip, int port)
{
//状态判断
if(socket!=null && socket.Connected){
Debug.Log("Connect fail, already connected!");
return;
}
if(isConnecting){
Debug.Log("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 = Time.time;
//上一次收到PONG的时间
lastPongTime = Time.time;
//监听PONG协议
if(!msgListeners.ContainsKey("MsgPong")){
AddMsgListener("MsgPong", OnMsgPong);
}
}

//Connect回调
private static void ConnectCallback(IAsyncResult ar){
try{
Socket socket = (Socket) ar.AsyncState;
socket.EndConnect(ar);
Debug.Log("Socket Connect Succ ");
FireEvent(NetEvent.ConnectSucc,"");
isConnecting = false;
//开始接收
socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);

}
catch (SocketException ex){
Debug.Log("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, "");
}
}

异步发送

引用致Unity网络游戏实战第二版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//发送数据
public static void Send(MsgBase msg) {
//状态判断
if(socket==null || !socket.Connected){
return;
}
if(isConnecting){
return;
}
if(isClosing){
return;
}
//数据编码 #带修改用linq改进
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);
//写入队列 /#带修改用linq改进
ByteArray ba = new ByteArray(sendBytes);
int count = 0; //writeQueue的长度
lock(writeQueue){
writeQueue.Enqueue(ba);
count = writeQueue.Count;
}
//send
if(count == 1){
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();
}
//完整发送
ba.readIdx+=count;
if(ba.length == 0){
lock(writeQueue){
writeQueue.Dequeue();
ba = writeQueue.First();
}
}
//继续发送
if(ba != null){
socket.BeginSend(ba.bytes, ba.readIdx, ba.length,
0, SendCallback, socket);
}
//正在关闭
else if(isClosing) {
socket.Close();
}
}

异步接收

引用致Unity网络游戏实战第二版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//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){
Debug.Log("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)
return;
readBuff.readIdx+=2;
//解析协议名
int nameCount = 0;
string protoName = MsgBase.DecodeName(readBuff.bytes, readBuff.readIdx, out nameCount);
if(protoName == ""){
Debug.Log("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();
}
}

ProtoBuf

和json功能一样作为序列化的工具,当产生消耗更小,优化带宽。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//将protobuf对象序列化为Byte数组  
public static byte[] Encode(ProtoBuf.IExtensible msgBase)
{
using (var memory = new System.IO.MemoryStream())
{
ProtoBuf.Serializer.Serialize(memory, msgBase);
return memory.ToArray();
}
}
//解码
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);
}
}

proto编写格式

1
2
3
4
5
6
7
8
9
10
11
message MsgMove{
optional int32 x = 1;
optional int32 y = 2;
optional int32 z = 3;
}

message MsgAttack{
optional string desc = 1;
}


生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// Generated from: proto/BattleMsg.proto
namespace proto.BattleMsg
{
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"MsgMove")]
public partial class MsgMove : global::ProtoBuf.IExtensible
{
public MsgMove() {}

private int _x = default(int);
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"x", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
public int x
{
get { return _x; }
set { _x = value; }
}
private int _y = default(int);
[global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"y", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
public int y
{
get { return _y; }
set { _y = value; }
}
private int _z = default(int);
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"z", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
[global::System.ComponentModel.DefaultValue(default(int))]
public int z
{
get { return _z; }
set { _z = value; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}

[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"MsgAttack")]
public partial class MsgAttack : global::ProtoBuf.IExtensible
{
public MsgAttack() {}

private string _desc = "";
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"desc", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string desc
{
get { return _desc; }
set { _desc = value; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}

}

具体使用

1
2
3
4
5
6
7
8
MsgMove msgMove = new MsgMove();
msgMove.x = 214;
byte[] bs = Encode(msgMove);
Debug.Log(System.BitConverter.ToString(bs));
//解码
ProtoBuf.IExtensible m = Decode("proto.BattleMsg.MsgMove", bs, 0, bs.Length);
MsgMove m2 = (MsgMove)m;
Debug.Log(m2.x);

命令行调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

void main()
{
var args = GetCommandlineArgs();

if (args.TryGetValue("-mode", out string mode))
{
//通过mode字符串启动服务器或者客户端
switch (mode)
{
case "server":

break;
case "host":

break;
case "client":
break;
}
}
}

private Dictionary<string, string> GetCommandlineArgs()
{
Dictionary<string, string> argDictionary = new Dictionary<string, string>();

var args = System.Environment.GetCommandLineArgs();

for (int i = 0; i < args.Length; ++i)
{
var arg = args[i].ToLower();
if (arg.StartsWith("-"))
{
var value = i < args.Length - 1 ? args[i + 1].ToLower() : null;
value = (value?.StartsWith("-") ?? false) ? null : value;

argDictionary.Add(arg, value);
}
}
return argDictionary;
}

启动指令

1
xxx.exe -mode server