Add project files.
This commit is contained in:
+235
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
public enum MsgType : byte
|
||||
{
|
||||
Hello = 1, // Client -> Host: Mod-Version
|
||||
Welcome = 2, // Host -> Client: Handshake ok
|
||||
Reject = 3, // Host -> Client: Grund
|
||||
Player = 4, // Spielerposition
|
||||
SaveBegin = 5, // Host -> Client: Spielstand kommt
|
||||
SaveFile = 6, // Host -> Client: Dateiname + Inhalt
|
||||
StartGame = 7, // Host -> Client: jetzt laden
|
||||
}
|
||||
|
||||
public struct Message
|
||||
{
|
||||
public MsgType Type;
|
||||
public byte[] Payload;
|
||||
|
||||
public BinaryReader Reader()
|
||||
{
|
||||
return new BinaryReader(new MemoryStream(Payload));
|
||||
}
|
||||
}
|
||||
|
||||
public struct PlayerState
|
||||
{
|
||||
public Vector3 Position;
|
||||
public float RotationY;
|
||||
|
||||
public byte[] Serialize()
|
||||
{
|
||||
Vector3 pos = Position;
|
||||
float rotY = RotationY;
|
||||
return NetSession.Build(w =>
|
||||
{
|
||||
w.Write(pos.x); w.Write(pos.y); w.Write(pos.z);
|
||||
w.Write(rotY);
|
||||
});
|
||||
}
|
||||
|
||||
public static PlayerState Deserialize(BinaryReader r)
|
||||
{
|
||||
return new PlayerState
|
||||
{
|
||||
Position = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()),
|
||||
RotationY = r.ReadSingle()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP-Verbindung zu genau einem Mitspieler. Nachrichten: [int Länge][byte Typ][Payload].
|
||||
/// Lesen ist nicht-blockierend und wird jedes Frame über Poll() abgefragt.
|
||||
/// </summary>
|
||||
public class NetSession
|
||||
{
|
||||
const int MaxFrame = 32 * 1024 * 1024;
|
||||
|
||||
public bool IsHost { get; private set; }
|
||||
public bool IsConnected { get { return stream != null; } }
|
||||
public bool IsClosed { get; private set; }
|
||||
public string CloseReason { get; private set; }
|
||||
|
||||
TcpListener listener;
|
||||
TcpClient client;
|
||||
NetworkStream stream;
|
||||
IAsyncResult connecting;
|
||||
bool justConnected;
|
||||
|
||||
byte[] buffer = new byte[64 * 1024];
|
||||
int bufferCount;
|
||||
|
||||
public static NetSession StartHost(int port)
|
||||
{
|
||||
var s = new NetSession { IsHost = true };
|
||||
s.listener = new TcpListener(IPAddress.Any, port);
|
||||
s.listener.Start();
|
||||
return s;
|
||||
}
|
||||
|
||||
public static NetSession StartClient(string ip, int port)
|
||||
{
|
||||
var s = new NetSession();
|
||||
s.client = new TcpClient();
|
||||
s.connecting = s.client.BeginConnect(ip, port, null, null);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>True genau einmal, direkt nachdem die Verbindung steht.</summary>
|
||||
public bool ConsumeConnected()
|
||||
{
|
||||
bool c = justConnected;
|
||||
justConnected = false;
|
||||
return c;
|
||||
}
|
||||
|
||||
public List<Message> Poll()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
if (IsClosed) return messages;
|
||||
|
||||
try
|
||||
{
|
||||
AcceptOrConnect();
|
||||
if (stream == null) return messages;
|
||||
|
||||
// Verbindung von der Gegenseite geschlossen?
|
||||
if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
|
||||
{
|
||||
Close("Connection closed by other player");
|
||||
return messages;
|
||||
}
|
||||
|
||||
while (stream.DataAvailable)
|
||||
{
|
||||
EnsureCapacity(bufferCount + client.Available);
|
||||
int read = stream.Read(buffer, bufferCount, buffer.Length - bufferCount);
|
||||
if (read <= 0) break;
|
||||
bufferCount += read;
|
||||
}
|
||||
|
||||
ParseFrames(messages);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
void AcceptOrConnect()
|
||||
{
|
||||
if (IsHost)
|
||||
{
|
||||
while (listener.Pending())
|
||||
{
|
||||
TcpClient incoming = listener.AcceptTcpClient();
|
||||
if (client != null) { incoming.Close(); continue; } // nur ein Mitspieler
|
||||
client = incoming;
|
||||
OnSocketReady();
|
||||
}
|
||||
}
|
||||
else if (connecting != null && connecting.IsCompleted)
|
||||
{
|
||||
IAsyncResult result = connecting;
|
||||
connecting = null;
|
||||
client.EndConnect(result); // wirft, wenn nicht erreichbar
|
||||
OnSocketReady();
|
||||
}
|
||||
}
|
||||
|
||||
void OnSocketReady()
|
||||
{
|
||||
client.NoDelay = true;
|
||||
client.SendBufferSize = 4 * 1024 * 1024;
|
||||
stream = client.GetStream();
|
||||
justConnected = true;
|
||||
}
|
||||
|
||||
void ParseFrames(List<Message> messages)
|
||||
{
|
||||
int offset = 0;
|
||||
while (bufferCount - offset >= 5)
|
||||
{
|
||||
int length = BitConverter.ToInt32(buffer, offset);
|
||||
if (length < 1 || length > MaxFrame) throw new IOException("Invalid packet");
|
||||
if (bufferCount - offset - 4 < length) break;
|
||||
|
||||
var payload = new byte[length - 1];
|
||||
Buffer.BlockCopy(buffer, offset + 5, payload, 0, payload.Length);
|
||||
messages.Add(new Message { Type = (MsgType)buffer[offset + 4], Payload = payload });
|
||||
offset += 4 + length;
|
||||
}
|
||||
if (offset > 0)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, offset, buffer, 0, bufferCount - offset);
|
||||
bufferCount -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureCapacity(int needed)
|
||||
{
|
||||
if (needed <= buffer.Length) return;
|
||||
var bigger = new byte[Math.Max(needed, buffer.Length * 2)];
|
||||
Buffer.BlockCopy(buffer, 0, bigger, 0, bufferCount);
|
||||
buffer = bigger;
|
||||
}
|
||||
|
||||
public void Send(MsgType type, byte[] payload = null)
|
||||
{
|
||||
if (stream == null) return;
|
||||
if (payload == null) payload = new byte[0];
|
||||
try
|
||||
{
|
||||
var frame = new byte[5 + payload.Length];
|
||||
BitConverter.GetBytes(payload.Length + 1).CopyTo(frame, 0);
|
||||
frame[4] = (byte)type;
|
||||
Buffer.BlockCopy(payload, 0, frame, 5, payload.Length);
|
||||
stream.Write(frame, 0, frame.Length); // blockierend, damit große Dateien komplett rausgehen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Build(Action<BinaryWriter> write)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
using (var w = new BinaryWriter(ms))
|
||||
{
|
||||
write(w);
|
||||
w.Flush();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(string reason = "Disconnected")
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
stream = null;
|
||||
try { if (client != null) client.Close(); } catch { }
|
||||
try { if (listener != null) listener.Stop(); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user