# PolyServer **Repository Path**: c-lanq/poly-server ## Basic Information - **Project Name**: PolyServer - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-10 - **Last Updated**: 2020-12-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 已过时 # PolyServer 0.1.2 ## 使用 ``` dotnet PolyServer.dll ``` ## 程序主逻辑 启动服务器 ```csharp namespace Cemit.PolyServer { class Program { static void Main(string[] args) { PolyServer.StartLoop(); } } } ``` 在启动后,服务器将会创建一个Socket,并以Select方式刷新套接字消息,每个循环读取一遍本地监听和客户端的串接字 ```csharp const string IP = "127.0.0.1"; const int PORT = 2333; public static void StartLoop() { listenfd = CreateTcpSocket(); //绑定本地端口 listenfd.Bind(GetListenIP()); //开启监听,参数backlog指定队列中最多可容纳等待接受的连接数,0表示不限制 listenfd.Listen(0); Debug.Log($"服务器启动成功!PolyServer {VERSION}"); Debug.Log($"正在监听:{IP}:{PORT}"); while (true) { ResetCheckRead(); //选择有状态刷新的套接字 Socket.Select(checkRead, null, null, 1000); foreach (var item in checkRead) { if (item == listenfd) { //读取监听套接字,通常是新客户端的连接 ReadListenfd(item); } else { //读取客户端套接字,通常是客户端的消息 ReadClientfd(item); } } Update(); } } ``` 读取本地监听时,将为新的客户端连接分配一个ClientState对象,并与该串接字(Socket)记录到字典clientsStatesPairs中ClientState对象, ```csharp static Dictionary clientsStatesPairs = new Dictionary(); private static void ReadListenfd(Socket item) { try { Debug.Log("收到连接申请。"); Socket client = listenfd.Accept(); Debug.Log("成功连接:" + client.RemoteEndPoint.ToString()); ClientState state = new ClientState(client); clientsStatesPairs.Add(client, state); } catch (Exception e) { Debug.Log("接收连接申请失败:" + e.Message); throw; } } ``` 读取客户端串接字时,使用ClientState对象的字节缓冲区(有自动增长功能)读取消息 ```csharp private static bool ReadClientfd(Socket socket) { ClientState state = clientsStatesPairs[socket]; ByteArray readBuff = state.readBuff; int count; if (readBuff.Remain <= 0) { ReceiveData(state); readBuff.MoveBytes(); } if (readBuff.Remain <= 0) { Close(state); Debug.Log("缓冲区溢出!" + "单条协议最长长度为 " + ByteArray.DEFAULT_SIZE + " bytes。" + "已强制关闭连接!"); } try { count = socket.Receive(readBuff.bytes, readBuff.writeIndex, readBuff.Remain, 0); } catch (Exception e) { Debug.Log($"接收数据错误:{e}。" + $"已强制关闭连接!"); Close(state); return false; } if (count == 0) { Debug.Log("客户端请求关闭[0]" + socket.RemoteEndPoint.ToString()); state.Close(); return false; } readBuff.writeIndex += count; //解析消息 ReceiveData(state); readBuff.CheckAndMoveBytes(); return true; } ``` 消息将被反序列化为IMessage消息(Protobuf),并查找监听该消息的类(实现了IMessageHandle的类),然后进行分发 ```csharp private static void ReceiveData(ClientState state) { //readBuff: 0904namehello ByteArray readBuff = state.readBuff; int msgLength = readBuff.ReadInt16(); if (msgLength == 0 || readBuff.Length < msgLength) { return; //处理分包 } readBuff.readIndex += 2; //readBuff: 04namehello string protoName = MessageEncoding.DecodeName( readBuff.bytes, readBuff.readIndex, out int nameCount); if (string.IsNullOrEmpty(protoName)) { Close(state); Debug.Log("接收到错误的数据:无法识别消息名!" + "已强行关闭连接"); return; } readBuff.readIndex += nameCount; //readBuff: hello int bodyCount = msgLength - nameCount; byte[] bodyBytes = new byte[bodyCount]; Array.Copy(readBuff.bytes, readBuff.readIndex, bodyBytes, 0, bodyCount); IMessage message = MessageEncoding.Decode(protoName, bodyBytes); readBuff.readIndex += bodyCount; readBuff.CheckAndMoveBytes(); //分发消息 Debug.Log($"接收信息:{protoName}"); IMessageHandle method = message.GetHandle(); if (method.IsNull()) { Debug.Log($"找不到消息的接收者:" + message); } else { method.Execute(state, message); } if (readBuff.Length > 2) { ReceiveData(state); } } ``` 此处PingHandle继承了IMessageHandle,当收到MsgPing时,Execute()就会被执行 ```csharp class PingHandle : IMessageHandle { public void Execute(ClientState clientState, IMessage message) { clientState.lastPingTime = PolyServer.GetTimeStamp(); Debug.Log("已刷新客户端时间戳:" + clientState.IP.ToString()); PolyServer.Send(clientState, new MsgPong()); } } ``` ## 消息概述 注:以下客户端特指**ClientState**对象,玩家特指**Player**对象。 注:转发特指按消息需要发送数据给其他客户端。 注:该概述未提及的消息均为已弃用消息。 ### 客户端消息 **MsgPing** 刷新服务器内该客户端的PING间隔记录,服务器返回一个PONG。仅限客户端发送。 **MsgPong** 代表一个PING-PONG成功。仅限服务器发送。 ### 数据类消息(不可直接发送) **MsgPlayerData** 角色数据 ### 临时用消息(正式版取消) **MsgCreatePlayer** 客户端发送时为向服务器申请创建一个角色;服务器发送时为告知服务器内已有的角色。 **MsgGetPlayerData** 向客户端索取玩家信息,客户端需要返回一个MsgGetPlayerData,服务器再进行转发。 **MsgKeyInput** 向服务器提交该角色按键更改,再由服务器进行转发。 ### TCP阶段消息(大厅-匹配-准备房间) #### 大厅消息总览 ### UDP阶段消息(游戏房间)