该项目的目的是同时测试和演示来自 Unity DOTS 技术堆栈的多个新包。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

571 行
20 KiB

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using UnityEngine;
using Unity.Networking.Transport;
using Unity.Networking.Transport.LowLevel.Unsafe;
using Unity.Sample.Core;
/// <summary>
/// An implementation of the ServerInfo part of the Server Query Protocol
/// </summary>
namespace SQP
{
[Flags]
public enum SQPChunkType
{
ServerInfo = 1,
ServerRules = 2,
PlayerInfo = 4,
TeamInfo = 8
}
public enum SQPMessageType
{
ChallangeRequest = 0,
ChallangeResponse = 0,
QueryRequest = 1,
QueryResponse = 1
}
public interface ISQPMessage
{
void ToStream(ref DataStreamWriter writer);
void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx);
}
public struct SQPHeader : ISQPMessage
{
public byte Type { get; internal set; }
public uint ChallangeId;
public void ToStream(ref DataStreamWriter writer)
{
writer.Write((byte)Type);
writer.WriteNetworkByteOrder((uint)ChallangeId);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
Type = reader.ReadByte(ref ctx);
ChallangeId = reader.ReadUIntNetworkByteOrder(ref ctx);
}
}
public struct ChallangeRequest : ISQPMessage
{
public SQPHeader Header;
public void ToStream(ref DataStreamWriter writer)
{
Header.Type = (byte)SQPMessageType.ChallangeRequest;
Header.ToStream(ref writer);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
Header.FromStream(reader, ref ctx);
}
}
public struct ChallangeResponse
{
public SQPHeader Header;
public void ToStream(ref DataStreamWriter writer)
{
Header.Type = (byte)SQPMessageType.ChallangeResponse;
Header.ToStream(ref writer);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
Header.FromStream(reader, ref ctx);
}
}
public struct QueryRequest
{
public SQPHeader Header;
public ushort Version;
public byte RequestedChunks;
public void ToStream(ref DataStreamWriter writer)
{
Header.Type = (byte)SQPMessageType.QueryRequest;
Header.ToStream(ref writer);
writer.WriteNetworkByteOrder((UInt16)Version);
writer.Write((byte)RequestedChunks);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
Header.FromStream(reader, ref ctx);
Version = reader.ReadUShortNetworkByteOrder(ref ctx);
RequestedChunks = reader.ReadByte(ref ctx);
}
}
public struct QueryResponseHeader
{
public SQPHeader Header;
public ushort Version;
public byte CurrentPacket;
public byte LastPacket;
public ushort Length;
public DataStreamWriter.DeferredUShortNetworkByteOrder ToStream(ref DataStreamWriter writer)
{
Header.Type = (byte)SQPMessageType.QueryResponse;
Header.ToStream(ref writer);
writer.WriteNetworkByteOrder((UInt16)Version);
writer.Write((byte)CurrentPacket);
writer.Write((byte)LastPacket);
return writer.WriteNetworkByteOrder((UInt16)Length);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
Header.FromStream(reader, ref ctx);
Version = reader.ReadUShortNetworkByteOrder(ref ctx);
CurrentPacket = reader.ReadByte(ref ctx);
LastPacket = reader.ReadByte(ref ctx);
Length = reader.ReadUShortNetworkByteOrder(ref ctx);
}
}
public static class DataStreamExtensions
{
static byte[] buffer = new byte[byte.MaxValue];
unsafe public static void WriteString(this DataStreamWriter writer, string value, Encoding encoding)
{
var encoder = encoding.GetEncoder();
var chars = value.ToCharArray();
int charsUsed, bytesUsed;
bool completed;
encoder.Convert(chars, 0, chars.Length, buffer, 0, byte.MaxValue, true, out charsUsed, out bytesUsed, out completed);
Debug.Assert(bytesUsed <= byte.MaxValue);
writer.Write((byte)bytesUsed);
fixed(byte* buf = buffer)
{
writer.WriteBytes(buf, bytesUsed);
}
}
unsafe public static string ReadString(this DataStreamReader reader, ref DataStreamReader.Context ctx, Encoding encoding)
{
var length = reader.ReadByte(ref ctx);
fixed(byte* buf = buffer)
{
reader.ReadBytes(ref ctx, buf, length);
}
return encoding.GetString(buffer, 0, length);
}
}
public class ServerInfo
{
public QueryResponseHeader QueryHeader;
public uint ChunkLen;
public Data ServerInfoData;
public ServerInfo()
{
ServerInfoData = new Data();
}
public class Data
{
public ushort CurrentPlayers;
public ushort MaxPlayers;
public string ServerName = "";
public string GameType = "";
public string BuildId = "";
public string Map = "";
public ushort Port;
public void ToStream(ref DataStreamWriter writer)
{
writer.WriteNetworkByteOrder((UInt16)CurrentPlayers);
writer.WriteNetworkByteOrder((UInt16)MaxPlayers);
writer.WriteString(ServerName, encoding);
writer.WriteString(GameType, encoding);
writer.WriteString(BuildId, encoding);
writer.WriteString(Map, encoding);
writer.WriteNetworkByteOrder((UInt16)Port);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
CurrentPlayers = reader.ReadUShortNetworkByteOrder(ref ctx);
MaxPlayers = reader.ReadUShortNetworkByteOrder(ref ctx);
ServerName = reader.ReadString(ref ctx, encoding);
GameType = reader.ReadString(ref ctx, encoding);
BuildId = reader.ReadString(ref ctx, encoding);
Map = reader.ReadString(ref ctx, encoding);
Port = reader.ReadUShortNetworkByteOrder(ref ctx);
}
}
public void ToStream(ref DataStreamWriter writer)
{
var lengthValue = QueryHeader.ToStream(ref writer);
var start = (ushort)writer.Length;
var chunkValue = writer.WriteNetworkByteOrder((uint)0);
var chunkStart = writer.Length;
ServerInfoData.ToStream(ref writer);
ChunkLen = (uint)(writer.Length - chunkStart);
QueryHeader.Length = (ushort)(writer.Length - start);
lengthValue.Update(QueryHeader.Length);
chunkValue.Update(ChunkLen);
var length = (ushort)System.Net.IPAddress.HostToNetworkOrder((short)QueryHeader.Length);
var chunkLen = (uint)System.Net.IPAddress.HostToNetworkOrder((int)ChunkLen);
}
public void FromStream(DataStreamReader reader, ref DataStreamReader.Context ctx)
{
QueryHeader.FromStream(reader, ref ctx);
ChunkLen = reader.ReadUIntNetworkByteOrder(ref ctx);
ServerInfoData.FromStream(reader, ref ctx);
}
static private Encoding encoding = new UTF8Encoding();
}
public static class UdpExtensions
{
public static SocketError SetupAndBind(this Socket socket, int port = 0)
{
SocketError error = SocketError.Success;
socket.Blocking = false;
var ep = new IPEndPoint(IPAddress.Any, port);
try
{
socket.Bind(ep);
}
catch (SocketException e)
{
error = e.SocketErrorCode;
throw e;
}
return error;
}
}
public class SQPClient
{
Socket m_Socket;
byte[] m_Buffer = new byte[1472];
System.Net.EndPoint endpoint = new System.Net.IPEndPoint(0, 0);
public enum SQPClientState
{
Idle,
WaitingForChallange,
WaitingForResponse,
}
public class SQPQuery
{
public SQPQuery()
{
m_ServerInfo = new ServerInfo();
m_State = SQPClientState.Idle;
m_Server = null;
}
public void Init(IPEndPoint server)
{
GameDebug.Assert(m_State == SQPClientState.Idle);
GameDebug.Assert(m_Server == null);
m_Server = server;
validResult = false;
}
public IPEndPoint m_Server;
public bool validResult;
public SQPClientState m_State;
public uint ChallangeId;
public long RTT;
public long StartTime;
public ServerInfo m_ServerInfo;
}
List<SQPQuery> m_Queries = new List<SQPQuery>();
public SQPClient()
{
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
m_Socket.SetupAndBind(0);
}
public SQPQuery GetSQPQuery(IPEndPoint server)
{
SQPQuery q = null;
foreach (var pending in m_Queries)
{
if (pending.m_State == SQPClientState.Idle && pending.m_Server == null)
{
q = pending;
break;
}
}
if (q == null)
{
q = new SQPQuery();
m_Queries.Add(q);
}
q.Init(server);
return q;
}
public void ReleaseSQPQuery(SQPQuery q)
{
q.m_Server = null;
}
unsafe public void StartInfoQuery(SQPQuery q)
{
GameDebug.Assert(q.m_State == SQPClientState.Idle);
q.StartTime = NetworkUtils.stopwatch.ElapsedMilliseconds;
var writer = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
var req = new ChallangeRequest();
req.ToStream(ref writer);
writer.CopyTo(0, writer.Length, ref m_Buffer);
m_Socket.SendTo(m_Buffer, writer.Length, SocketFlags.None, q.m_Server);
q.m_State = SQPClientState.WaitingForChallange;
}
void SendServerInfoQuery(SQPQuery q)
{
q.StartTime = NetworkUtils.stopwatch.ElapsedMilliseconds;
var req = new QueryRequest();
req.Header.ChallangeId = q.ChallangeId;
req.RequestedChunks = (byte)SQPChunkType.ServerInfo;
var writer = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
req.ToStream(ref writer);
q.m_State = SQPClientState.WaitingForResponse;
writer.CopyTo(0, writer.Length, ref m_Buffer);
m_Socket.SendTo(m_Buffer, writer.Length, SocketFlags.None, q.m_Server);
writer.Dispose();
}
public void Update()
{
if (m_Socket.Poll(0, SelectMode.SelectRead))
{
int read = m_Socket.ReceiveFrom(m_Buffer, m_Buffer.Length, SocketFlags.None, ref endpoint);
if (read > 0)
{
// Transfer incoming data in m_Buffer into a DataStreamReader
var writer = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
writer.Write(m_Buffer, read);
var reader = new DataStreamReader(writer, 0, read);
var ctx = default(DataStreamReader.Context);
var header = new SQPHeader();
header.FromStream(reader, ref ctx);
foreach (var q in m_Queries)
{
if (q.m_Server == null || !endpoint.Equals(q.m_Server))
continue;
switch (q.m_State)
{
case SQPClientState.Idle:
// Just ignore if we get extra data
break;
case SQPClientState.WaitingForChallange:
if ((SQPMessageType)header.Type == SQPMessageType.ChallangeResponse)
{
q.ChallangeId = header.ChallangeId;
q.RTT = NetworkUtils.stopwatch.ElapsedMilliseconds - q.StartTime;
// We restart timer so we can get an RTT that is an average between two measurements
q.StartTime = NetworkUtils.stopwatch.ElapsedMilliseconds;
SendServerInfoQuery(q);
}
break;
case SQPClientState.WaitingForResponse:
if ((SQPMessageType)header.Type == SQPMessageType.QueryResponse)
{
ctx = default(DataStreamReader.Context);
q.m_ServerInfo.FromStream(reader, ref ctx);
// We report the average of two measurements
q.RTT = (q.RTT + (NetworkUtils.stopwatch.ElapsedMilliseconds - q.StartTime)) / 2;
/*
GameDebug.Log(string.Format("ServerName: {0}, BuildId: {1}, Current Players: {2}, Max Players: {3}, GameType: {4}, Map: {5}, Port: {6}",
m_ServerInfo.ServerInfoData.ServerName,
m_ServerInfo.ServerInfoData.BuildId,
(ushort)m_ServerInfo.ServerInfoData.CurrentPlayers,
(ushort)m_ServerInfo.ServerInfoData.MaxPlayers,
m_ServerInfo.ServerInfoData.GameType,
m_ServerInfo.ServerInfoData.Map,
(ushort)m_ServerInfo.ServerInfoData.Port));
*/
q.validResult = true;
q.m_State = SQPClientState.Idle;
}
break;
default:
break;
}
}
}
}
foreach (var q in m_Queries)
{
// Timeout if stuck in any state but idle for too long
if (q.m_State != SQPClientState.Idle)
{
var now = NetworkUtils.stopwatch.ElapsedMilliseconds;
if (now - q.StartTime > 3000)
{
q.m_State = SQPClientState.Idle;
}
}
}
}
}
public class SQPServer
{
Socket m_Socket;
System.Random m_Random;
SQP.ServerInfo m_ServerInfo = new ServerInfo();
public SQP.ServerInfo.Data ServerInfoData
{
get { return m_ServerInfo.ServerInfoData; }
set { m_ServerInfo.ServerInfoData = value; }
}
byte[] m_Buffer = new byte[1472];
System.Net.EndPoint endpoint = new System.Net.IPEndPoint(0, 0);
Dictionary<EndPoint, uint> m_OutstandingTokens = new Dictionary<EndPoint, uint>();
public SQPServer(int port)
{
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
m_Socket.SetupAndBind(port);
m_Random = new System.Random();
GameDebug.Log("SQP Initialized. Listening on port " + port);
}
public void Update()
{
if (m_Socket.Poll(0, SelectMode.SelectRead))
{
int read = m_Socket.ReceiveFrom(m_Buffer, m_Buffer.Length, SocketFlags.None, ref endpoint);
if (read > 0)
{
var bufferWriter = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
bufferWriter.Write(m_Buffer, read);
var reader = new DataStreamReader(bufferWriter, 0, read);
var ctx = default(DataStreamReader.Context);
var header = new SQPHeader();
header.FromStream(reader, ref ctx);
SQPMessageType type = (SQPMessageType)header.Type;
switch (type)
{
case SQPMessageType.ChallangeRequest:
{
if (!m_OutstandingTokens.ContainsKey(endpoint))
{
uint token = GetNextToken();
//Debug.Log("token generated: " + token);
var writer = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
var rsp = new ChallangeResponse();
rsp.Header.ChallangeId = token;
rsp.ToStream(ref writer);
writer.CopyTo(0, writer.Length, ref m_Buffer);
m_Socket.SendTo(m_Buffer, writer.Length, SocketFlags.None, endpoint);
m_OutstandingTokens.Add(endpoint, token);
}
}
break;
case SQPMessageType.QueryRequest:
{
uint token;
if (!m_OutstandingTokens.TryGetValue(endpoint, out token))
{
//Debug.Log("Failed to find token!");
return;
}
m_OutstandingTokens.Remove(endpoint);
ctx = default(DataStreamReader.Context);
var req = new QueryRequest();
req.FromStream(reader, ref ctx);
if ((SQPChunkType)req.RequestedChunks == SQPChunkType.ServerInfo)
{
var rsp = m_ServerInfo;
var writer = new DataStreamWriter(m_Buffer.Length, Unity.Collections.Allocator.Temp);
rsp.QueryHeader.Header.ChallangeId = token;
rsp.ToStream(ref writer);
writer.CopyTo(0, writer.Length, ref m_Buffer);
m_Socket.SendTo(m_Buffer, writer.Length, SocketFlags.None, endpoint);
}
}
break;
default:
break;
}
}
}
}
uint GetNextToken()
{
uint thirtyBits = (uint)m_Random.Next(1 << 30);
uint twoBits = (uint)m_Random.Next(1 << 2);
return (thirtyBits << 2) | twoBits;
}
}
}