236 lines
7.2 KiB
C#
236 lines
7.2 KiB
C#
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 { }
|
|
}
|
|
}
|
|
}
|