using System; using System.Collections.Generic; using System.Net.Sockets; using UnityEngine; using UnityEngine.Assertions; public enum HeaderCode { Invalid = 0, Heartbeat = 1, JoinGame = 100, } public struct Header { public HeaderCode code; public int size; public void SetData(byte[] bytes) { Debug.Assert(bytes.Length == GetSize(), $"Header size is invalid! {bytes.Length}"); code = (HeaderCode)BitConverter.ToInt32(bytes, 0); size = BitConverter.ToInt32(bytes, sizeof(HeaderCode)); } public byte[] GetData() { byte[] bytes = new byte[GetSize()]; BitConverter.GetBytes((int)code).CopyTo(bytes, 0); BitConverter.GetBytes(size).CopyTo(bytes, sizeof(HeaderCode)); return bytes; } public static int GetSize() => sizeof(HeaderCode) + sizeof(int); } public struct Body { public byte[] data; } public class Packet { public Header header; public Body body; public byte[] GetData() { byte[] headerData = header.GetData(); byte[] data = new byte[headerData.Length + header.size]; headerData.CopyTo(data, 0); body.data.CopyTo(data, headerData.Length); return data; } } public class Session { Queue penddingBuffer = new Queue(); Header currentHeader; Body currentBody; public Header CurrentHeader => currentHeader; public Body CurrentBody => currentBody; public Session() { ClearHeader(); } public void PenddingData(byte[] data) { for (int i = 0; i < data.Length; i++) { penddingBuffer.Enqueue(data[i]); } } public bool TryParseHeader() { int headerSize = Header.GetSize(); if (currentHeader.code == HeaderCode.Invalid && penddingBuffer.Count >= headerSize) { byte[] headerData = new byte[headerSize]; for (int i = 0; i < headerSize; i++) headerData[i] = penddingBuffer.Dequeue(); currentHeader.SetData(headerData); } return currentHeader.code != HeaderCode.Invalid; } public bool TryParseBody() { if (currentHeader.code != HeaderCode.Invalid && penddingBuffer.Count >= currentHeader.size) { byte[] bodyData = new byte[currentHeader.size]; for (int i = 0; i < currentHeader.size; i++) bodyData[i] = penddingBuffer.Dequeue(); currentBody.data = bodyData; return true; } return false; } public void ClearHeader() { currentHeader.code = HeaderCode.Invalid; currentHeader.size = 0; } } public abstract class TcpServerClient : MonoBehaviour { [SerializeField] string host = "127.0.0.1"; [SerializeField] int port = 123; [SerializeField] int bufferSize = 1024 * 8; // 8KB TcpClient client; NetworkStream stream; byte[] reciveBuffer; Session session; Queue recievedPacket = new Queue(); public bool IsConnected => client != null && client.Connected; private void OnApplicationQuit() { if(client != null && client.Connected) client.Close(); } private void Start() { Invoke(nameof(test), 5); } private void test() { //temp code Header header = new Header { code = HeaderCode.JoinGame, size = UserState.GetSize(), }; Body body = new Body(); Vector3 position = new Vector3(0, 0, 0); byte[] data = new byte[sizeof(float) * 3]; BitConverter.GetBytes(position.x).CopyTo(data, 0); BitConverter.GetBytes(position.y).CopyTo(data, sizeof(float)); BitConverter.GetBytes(position.z).CopyTo(data, sizeof(float) * 2); body.data = data; Send(new Packet { header = header, body = body }); } void Update() { while (recievedPacket.Count > 0) { OnDataReceived(recievedPacket.Dequeue()); } } public void Connect() { if(IsConnected) { Logger.LogWarning("Already connected to server!"); return; } client = new TcpClient { ReceiveBufferSize = bufferSize, SendBufferSize = bufferSize, }; session = new Session(); client.BeginConnect(host, port, ConnectCallback, null); } public async void Send(Packet packet) { if (!IsConnected) { Logger.LogError("Failed to send packet! Not connected to server!"); return; } var dataBytes = packet.GetData(); try { await stream.WriteAsync(dataBytes, 0, dataBytes.Length); } catch (Exception e) { Logger.LogError(e); } } private void ConnectCallback(IAsyncResult ar) { client.EndConnect(ar); if(!client.Connected) { Debug.LogError("Failed to connect server!"); return; } Logger.Log("Connected to server!"); stream = client.GetStream(); reciveBuffer = new byte[bufferSize]; stream.BeginRead(reciveBuffer, 0, bufferSize, ReadCallback, null); } private void ReadCallback(IAsyncResult ar) { try { int read = stream.EndRead(ar); if (read <= 0) return; byte[] buffer = new byte[read]; Array.Copy(reciveBuffer, buffer, read); session.PenddingData(buffer); while (session.TryParseHeader() && session.TryParseBody()) { recievedPacket.Enqueue(new Packet { header = session.CurrentHeader, body = session.CurrentBody, }); session.ClearHeader(); } stream.BeginRead(reciveBuffer, 0, bufferSize, ReadCallback, null); } catch (Exception e) { Logger.LogError(e); } } protected virtual void OnConnected() { } protected abstract void OnDataReceived(in Packet packet); }