์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 3 ~ 5)

2024. 2. 18. 10:28ยท๐Ÿ“‚ Unity Engine Study/๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜

 

 

 

 

[ ์„น์…˜ 3. ํŒจํ‚ท ์ง๋ ฌํ™” ]

# Serialization #1

- ์ง๋ ฌํ™”๋ž€ ๊ฐ์ฒด๋ฅผ ์ €์žฅ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ ๋˜๋Š” ์ „์†ก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋œปํ•œ๋‹ค.

 ~> ์ฆ‰, ํŒจํ‚ท ์ง๋ ฌํ™”๋ž€ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํŒจํ‚ท์— ์ฐจ๊ณก์ฐจ๊ณก ์Œ“์€ ๋’ค ์ด๋ฅผ ํ•˜๋‚˜์˜ ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค.

- ์—ญ์ง๋ ฌํ™”๋ž€ ํŠน์ • ํฌ๋งท ์ƒํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋œปํ•œ๋‹ค.

- Session์€ ์ถ”ํ›„์— ๋‹ค์–‘ํ•˜๊ฒŒ ์กด์žฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Session์˜ ์ด๋ฆ„์„ ์ •ํ™•ํ•˜๊ฒŒ ์ง€์–ด์ฃผ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

  (์˜ˆ๋ฅผ ๋“ค์–ด ๋ถ„์‚ฐ์„œ๋ฒ„์ธ ๊ฒฝ์šฐ ๊ฐ ๋‹ค๋ฅธ ๋ถ€๋ถ„์„ ๊ด€๋ฆฌํ•˜๋Š” ์„œ๋ฒ„์˜ ๋Œ€๋ฆฌ์ž ์—ญํ• ์„ ํ•˜๋Š” Session์ด ์—ฌ๋Ÿฌ๊ฐœ ์กด์žฌํ•œ๋‹ค.)

- ์šฐ์„  Serialization ์˜ ํ๋ฆ„๋งŒ ์ดํ•ดํ•œ ๋’ค ์ถ”ํ›„์— ์ž๋™ํ™” ํ•  ์˜ˆ์ •์ด๋‹ค.

DummyClient์— ServerSession Class ์ƒ์„ฑ ํ›„ DummyClient์˜ Program Class ์™€ ๋‚ด์šฉ ๋ถ„๋ฆฌ
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DummyClient
{
    class Packet
    {
        public ushort size; // ushort๋Š” 2Byte
        public ushort packetId; // ushort๋Š” 2Byte
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;
    }

    class PlayerInfoOk : Packet // Server์—์„œ Client๋กœ ์š”์ฒญ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ
    {
        public int hp;
        public int attack;
    }

    public enum PacketID
    {
        PlayerInfoReq = 1,
        PlayerInfoOk = 2,
    }

    class ServerSession : Session
    {
        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");

            PlayerInfoReq packet = new PlayerInfoReq() { packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };

            for (int i = 0; i < 5; i++)
            {
                ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

                // ์•„๋ž˜ ๋ถ€๋ถ„ ์ถ”๊ฐ€ (2๋ฒˆ์˜ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” ๊ฒƒ์„ TryWriteBytes๋ฅผ ํ†ตํ•ด 1๋ฒˆ์˜ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๋„๋ก ์ˆ˜์ •)
                bool success = true;
                ushort count = 0; // ์ง€๊ธˆ๊นŒ์ง€ ๋ช‡ Byte๋ฅผ Buffer์— ๋ฐ€์–ด ๋„ฃ์—ˆ๋Š”๊ฐ€?
                count += 2;
                success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), packet.packetId);
                count += 2;
                success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), packet.playerId);
                count += 8;

                success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count), count); // size๋Š” ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ๋’ค ์ดˆ๊ธฐํ™”

                // ์•„๋ž˜ ๋ถ€๋ถ„ ์‚ญ์ œ
                //byte[] size = BitConverter.GetBytes(packet.size); 
                //byte[] packetId = BitConverter.GetBytes(packet.packetId); 
                //byte[] playerId = BitConverter.GetBytes(packet.playerId);
                //Array.Copy(size, 0, openSegment.Array, openSegment.Offset + count, 2);
                //count += 2;
                //Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + count, 2);
                //count += 2;
                //Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset + count, 8);
                //count += 8;

                ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);

                if (success) // success์‹œ Send
                    Send(sendBuff);
            }
        }

        public override void OnDisconnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnDisconnected : {endPoint}");
        }

        public override int OnRecv(ArraySegment<byte> buffer)
        {
            string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
            Console.WriteLine($"[From Server] {recvData}");
            return buffer.Count;
        }

        public override void OnSend(int numOfBytes)
        {
            Console.WriteLine($"Transferred bytes : {numOfBytes}");
        }
    }
}โ€‹

 

์ˆ˜์ •๋œ DummyClient์˜ Program Class
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using ServerCore; // Servercore ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฐธ์กฐ

namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {
            // DNS (Domain Name System) : Domain์„ IP ๋„คํŠธ์›Œํฌ์—์„œ ์ฐพ์•„๊ฐˆ ์ˆ˜ ์žˆ๋Š” IP๋กœ ๋ณ€ํ™˜ํ•ด ์ค€๋‹ค. 
            string host = Dns.GetHostName(); // Local Computer์˜ host ์ด๋ฆ„์„ ๋ฐ˜ํ™˜
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0]; // ip ์ฃผ์†Œ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ (์˜ˆ๋ฅผ ๋“ค์–ด Google๊ณผ ๊ฐ™์ด Traffic์ด ์–ด๋งˆ๋ฌด์‹œํ•œ ์‚ฌ์ดํŠธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ip ์ฃผ์†Œ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ)
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip ์ฃผ์†Œ์™€ port ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ž…๋ ฅ

            Connector connector = new Connector();
            connector.Connect(endPoint, () => { return new ServerSession(); });

            while (true)
            {
                try
                {

                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }

                Thread.Sleep(100);
            }
        }
    }
}โ€‹

 

Server์— ClientSession Class ์ƒ์„ฑ ํ›„ Server์˜ Program Class ์™€ ๋‚ด์šฉ ๋ถ„๋ฆฌ
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    class Packet // ํŒจํ‚ท ์„ค๊ณ„์‹œ ์ตœ๋Œ€ํ•œ Size๋ฅผ ์••์ถ•ํ•˜์—ฌ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.
    {
        // Packet์ด ์™„์ „์ฒด๋กœ ์™”๋Š”์ง€? ์ž˜๋ ค์„œ ์™”๋Š”์ง€? ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
        public ushort size; // ushort๋Š” 2Byte
        public ushort packetId; // ushort๋Š” 2Byte
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;
    }

    class PlayerInfoOk : Packet // Server์—์„œ Client๋กœ ์š”์ฒญ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ
    {
        public int hp;
        public int attack;
    }

    public enum PacketID
    {
        PlayerInfoReq = 1,
        PlayerInfoOk = 2,
    }

    class ClientSession : PacketSession
    {
        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");

            Packet packet = new Packet() { size = 100, packetId = 10 };

            ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
            byte[] buffer = BitConverter.GetBytes(packet.size);
            byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
            // ์–ด๋А ๋ฐฐ์—ด์˜? ์–ด๋””์„œ๋ถ€ํ„ฐ? ์–ด๋А ๋ฐฐ์—ด์˜? ์–ด๋””๋กœ? ์–ผ๋งˆ๋งŒํผ?
            Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
            Array.Copy(buffer2, 0, openSegment.Array, buffer.Length, buffer2.Length);
            ArraySegment<byte> sendBuff = SendBufferHelper.Close(buffer.Length + buffer2.Length);

            Send(sendBuff);

            Thread.Sleep(5000);

            Disconnect();
        }

        // OnRecvPacket ์ฝ”๋“œ ์ˆ˜์ •
        public override void OnRecvPacket(ArraySegment<byte> buffer)
        {
            ushort count = 0; // ์ง€๊ธˆ๊นŒ์ง€ ๋ช‡ Byte๋ฅผ Buffer์— ๋ฐ€์–ด ๋„ฃ์—ˆ๋Š”๊ฐ€?
            ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count); // ToUInt16์€ Byte ๋ฐฐ์—ด์„ ushort๋กœ ๋ฝ‘์•„๋‹ฌ๋ผ๋Š” ๊ฒƒ
            count += 2;
            ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count); // ToUInt16์€ Byte ๋ฐฐ์—ด์„ ushort๋กœ ๋ฝ‘์•„๋‹ฌ๋ผ๋Š” ๊ฒƒ
            count += 2;

            switch ((PacketID)id)
            {
                case PacketID.PlayerInfoReq:
                    {
                        long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count); // ToUInt16์€ Byte ๋ฐฐ์—ด์„ long์œผ๋กœ ๋ฝ‘์•„๋‹ฌ๋ผ๋Š” ๊ฒƒ
                        count += 8;
                        Console.WriteLine($"PlayerInfoReq: {playerId}");
                    }
                    break;
            }

            Console.WriteLine($"RecvPacketID: {id}, Size: {size}");
        }

        public override void OnDisconnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnDisconnected : {endPoint}");
        }

        public override void OnSend(int numOfBytes)
        {
            Console.WriteLine($"Transferred bytes : {numOfBytes}");
        }
    }
}โ€‹

 

์ˆ˜์ •๋œ Server์˜ Program Class
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        static Listener _listener = new Listener();

        static void Main(string[] args)
        {
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            // ์†๋‹˜์„ ์ž…์žฅ์‹œํ‚จ๋‹ค.
            _listener.Init(endPoint, () => { return new ClientSession(); });
            Console.WriteLine("Listening...");

            while (true)
            {

            }
        }
    }
}โ€‹

 

# Serialization #2

- ์ž๋™ํ™” ํ•˜๊ธฐ์— ์•ž์„œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•œ ์ฝ”๋“œ ์ˆ˜์ •์„ ํ•  ์˜ˆ์ •์ด๋‹ค.

ServerSession Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DummyClient
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        // ์ตœ์ƒ์œ„ Class์ธ Packet์— ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
        public abstract ArraySegment<byte> Write();
        public abstract void Read(ArraySegment<byte> s);
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;

        // ์ƒ์„ฑ์ž
        public PlayerInfoReq()
        {
            this.packetId = (ushort)PacketID.PlayerInfoReq;
        }

        // ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„
        public override ArraySegment<byte> Write()
        {
            ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

            bool success = true;
            ushort count = 0; // ์ง€๊ธˆ๊นŒ์ง€ ๋ช‡ Byte๋ฅผ Buffer์— ๋ฐ€์–ด ๋„ฃ์—ˆ๋Š”๊ฐ€?
            count += 2;
            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), this.packetId);
            count += 2;
            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), this.playerId);
            count += 8;

            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count), count); // size๋Š” ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ๋’ค ์ดˆ๊ธฐํ™”

            if (success == false)
                return null;

            return SendBufferHelper.Close(count);
        }

        // ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„
        public override void Read(ArraySegment<byte> s)
        {
            ushort count = 0;
            //ushort size = BitConverter.ToUInt16(s.Array, s.Offset + count); ~> ์‚ฌ์šฉํ•  ์ผ์ด ์—†์–ด ํ•„์š” X
            count += 2;
            //ushort id = BitConverter.ToUInt16(s.Array, s.Offset + count); ~> Read๋ฅผ ์‹คํ–‰ํ–ˆ๋‹ค๋Š” ๊ฒƒ์€ ์ด๋ฏธ ํŒจํ‚ท ๋ถ„ํ•ด ํ›„ id์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์€ ๋’ค์ด๋ฏ€๋กœ ํ•„์š” X
            count += 2;

            // ์ˆ˜์ • ๋ถ€๋ถ„ (Client๊ฐ€ ์•…์˜์ ์œผ๋กœ ์ž˜๋ชป๋œ Packet Size๋ฅผ ๋ณด๋‚ธ ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ)
            this.playerId = BitConverter.ToInt64(new ReadOnlySpan<byte>(s.Array, s.Offset + count, s.Count - count));
            count += 8;
        }
    }

    // PlayerInfoOk๋Š” ์ถ”ํ›„์— ๊ตฌํ˜„ ์˜ˆ์ •
    //class PlayerInfoOk : Packet // Server์—์„œ Client๋กœ ์š”์ฒญ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ
    //{
    //    public int hp;
    //    public int attack;
    //}

    // ...

    class ServerSession : Session
    {
        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");

            PlayerInfoReq packet = new PlayerInfoReq() { playerId = 1001 };

            for (int i = 0; i < 5; i++)
            {
                ArraySegment<byte> s = packet.Write(); // ์ง๋ ฌํ™”

                if (s != null) // success์‹œ Send
                    Send(s);
            }
        }

        // ...
    }
}โ€‹

 

ClientSession Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        // ์ตœ์ƒ์œ„ Class์ธ Packet์— ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
        public abstract ArraySegment<byte> Write();
        public abstract void Read(ArraySegment<byte> s);
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;

        // ์ƒ์„ฑ์ž
        public PlayerInfoReq()
        {
            this.packetId = (ushort)PacketID.PlayerInfoReq;
        }

        // ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„
        public override ArraySegment<byte> Write()
        {
            ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

            bool success = true;
            ushort count = 0; // ์ง€๊ธˆ๊นŒ์ง€ ๋ช‡ Byte๋ฅผ Buffer์— ๋ฐ€์–ด ๋„ฃ์—ˆ๋Š”๊ฐ€?
            count += 2;
            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), this.packetId);
            count += 2;
            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), this.playerId);
            count += 8;

            success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count), count); // size๋Š” ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ๋’ค ์ดˆ๊ธฐํ™”

            if (success == false)
                return null;

            return SendBufferHelper.Close(count);
        }

        // ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„
        public override void Read(ArraySegment<byte> s)
        {
            ushort count = 0;
            //ushort size = BitConverter.ToUInt16(s.Array, s.Offset + count); ~> ์‚ฌ์šฉํ•  ์ผ์ด ์—†์–ด ํ•„์š” X
            count += 2;
            //ushort id = BitConverter.ToUInt16(s.Array, s.Offset + count); ~> Read๋ฅผ ์‹คํ–‰ํ–ˆ๋‹ค๋Š” ๊ฒƒ์€ ์ด๋ฏธ ํŒจํ‚ท ๋ถ„ํ•ด ํ›„ id์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์€ ๋’ค์ด๋ฏ€๋กœ ํ•„์š” X
            count += 2;

            // ์ˆ˜์ • ๋ถ€๋ถ„ (Client๊ฐ€ ์•…์˜์ ์œผ๋กœ ์ž˜๋ชป๋œ Packet Size๋ฅผ ๋ณด๋‚ธ ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ)
            this.playerId = BitConverter.ToInt64(new ReadOnlySpan<byte>(s.Array, s.Offset + count, s.Count - count));
            count += 8;
        }
    }

    // PlayerInfoOk๋Š” ์ถ”ํ›„์— ๊ตฌํ˜„ ์˜ˆ์ •
    //class PlayerInfoOk : Packet // Server์—์„œ Client๋กœ ์š”์ฒญ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ
    //{
    //    public int hp;
    //    public int attack;
    //}

    // ...

    class ClientSession : PacketSession 
    {
        // ...

        public override void OnRecvPacket(ArraySegment<byte> buffer)
        {
            // ํŒจํ‚ท์„ ๋ถ„ํ•ดํ•˜์—ฌ id ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์€ ๋’ค
            ushort count = 0;
            ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
            count += 2;
            ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
            count += 2;

            // ํ•ด๋‹น id ์— ๋งž๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰
            switch ((PacketID)id)
            {
                case PacketID.PlayerInfoReq:
                    {
                        PlayerInfoReq p = new PlayerInfoReq();
                        p.Read(buffer); // ์—ญ์ง๋ ฌํ™”
                        Console.WriteLine($"PlayerInfoReq: {p.playerId}");
                    }
                    break;
            }

            Console.WriteLine($"RecvPacketID: {id}, Size: {size}");
        }

        // ...
    }
}โ€‹

 

# UTF-8 vs UTF-16

 

- ์ปดํ“จํ„ฐ๊ฐ€ ์„ธ์ƒ์— ์ฒ˜์Œ ๋“ฑ์žฅํ•  ๋‹น์‹œ์—๋Š” ์˜์–ด์™€ ๋ช‡๊ฐ€์ง€ ํŠน์ˆ˜๋ฌธ์ž๋งŒ์„ ์‚ฌ์šฉํ•˜์˜€๊ณ , ์ด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด 1Byte๋กœ ์ถฉ๋ถ„ํ–ˆ๋‹ค.

 ~> ASCII ์ฝ”๋“œ (1Byte) ๋“ฑ์žฅ

 

- ๊ทธ๋Ÿฌ๋‚˜ ์ธํ„ฐ๋„ท ์‹œ๋Œ€ ๋„์ž… ํ›„ ์–ธ์–ด์˜ ๋‹ค์–‘์„ฑ์œผ๋กœ ์ธํ•˜์—ฌ 1Byte ๋งŒ์œผ๋กœ๋Š” ๋ชจ๋“  ๋‚˜๋ผ์˜ ์–ธ์–ด๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๋‹ค.

 ~> UNICODE (2Byte) ๋“ฑ์žฅ

 

- Encoding์€ ์ปดํ“จํ„ฐ์—์„œ ๋ฌธ์ž์™€ ๊ธฐํ˜ธ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌธ์ž๋ฅผ ์ด์ง„ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์ด๋ฉฐ ๋ฌธ์ž์™€ ์ด์ง„ ๋ฐ์ดํ„ฐ ๊ฐ„์˜

  Mapping ๊ทœ์น™์„ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 ~> Variable-Width Encoding (๊ฐ€๋ณ€ ๋„ˆ๋น„ ์ธ์ฝ”๋”ฉ) : UTF-8, UTF-16

 ~> Fixed-Length Encoding (๊ณ ์ • ๊ธธ์ด ์ธ์ฝ”๋”ฉ) : UTF-32

 

- UTF-8

 ~> ์˜๋ฌธ : 1Byte

 ~> ํ•œ๊ธ€ : 3Byte

 

- UTF-16

 ~> BMP X : 2Byte

 ~> BMP O : 4Byte

 ~> ์˜๋ฌธ : 2Byte

 ~> ํ•œ๊ธ€ : 2Byte

 

# Serialization #3

- ๋ฐ์ดํ„ฐ์˜ ๊ธธ์ด๊ฐ€ ๊ฐ€๋ณ€์ ์ธ String์€ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ• ๊นŒ?

String ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ServerSession Class ์™€ ClientSession Class ์ˆ˜์ •
(์•„๋ž˜ ์ฝ”๋“œ๋Š” ServerSession Class์ง€๋งŒ ClientSession Class๋„ ๋™์ผํ•œ ์ฝ”๋“œ๋กœ ์ˆ˜์ •)
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DummyClient
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        public abstract ArraySegment<byte> Write();
        public abstract void Read(ArraySegment<byte> openSegment);
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;
        public string name; // ๊ฐ€๋ณ€ ๊ธธ์ด์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ?

        // ์ƒ์„ฑ์ž
        public PlayerInfoReq()
        {
            this.packetId = (ushort)PacketID.PlayerInfoReq;
        }

        public override ArraySegment<byte> Write()
        {
            ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

            bool success = true;
            ushort count = 0;

            Span<byte> span = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

            count += sizeof(ushort);
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), this.packetId); // Slice๋Š” ์‹ค์งˆ์ ์œผ๋กœ Span์— ๋ณ€ํ™”๋ฅผ ์ฃผ์ง€ X
            count += sizeof(ushort);
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), this.playerId); // Slice๋Š” ์‹ค์งˆ์ ์œผ๋กœ Span์— ๋ณ€ํ™”๋ฅผ ์ฃผ์ง€ X
            count += sizeof(long);

            // string ์ฒ˜๋ฆฌ #1 (Buffer์— 2Byte์ธ string len์„ ๋จผ์ € ์‚ฝ์ž… ํ›„ string data ์‚ฝ์ž…)
            //ushort nameLen = (ushort)Encoding.Unicode.GetByteCount(this.name); // GetByteCount()๋Š” UTF-16 ๊ธฐ์ค€์˜ byte ๋ฐฐ์—ด ํฌ๊ธฐ๋ฅผ ๋ฐ˜ํ™˜
            //success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), nameLen); // Slice๋Š” ์‹ค์งˆ์ ์œผ๋กœ Span์— ๋ณ€ํ™”๋ฅผ ์ฃผ์ง€ X
            //count += sizeof(ushort);
            //Array.Copy(Encoding.Unicode.GetBytes(this.name), 0, openSegment.Array, count, nameLen); // GetBytes()๋Š” string์„ ๋ฐ›์•„ Byte ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
            //count += nameLen;

            // string ์ฒ˜๋ฆฌ #2 (Buffer์— 2Byte์ธ string len์„ ์œ„ํ•œ ๊ณต๊ฐ„์„ ๋‚จ๊ฒจ๋‘” ์ฑ„๋กœ string data๋ฅผ ๋จผ์ € ์‚ฝ์ž… ํ›„ string len ์‚ฝ์ž…) 
            ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this.name, 0, name.Length, openSegment.Array, openSegment.Offset + count + sizeof(ushort)); // Buffer์— string data๋ฅผ ์‚ฝ์ž…ํ•จ๊ณผ ๋™์‹œ์— string len์„ ๋ฐ˜ํ™˜
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), nameLen);
            count += sizeof(ushort);
            count += nameLen;

            success &= BitConverter.TryWriteBytes(span, count); // size๋Š” ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ๋’ค ์ดˆ๊ธฐํ™”

            if (success == false)
                return null;

            return SendBufferHelper.Close(count);
        }

        public override void Read(ArraySegment<byte> openSegment)
        {
            ushort count = 0;

            ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

            count += sizeof(ushort);
            count += sizeof(ushort);
            this.playerId = BitConverter.ToInt64(span.Slice(count, span.Length - count)); // Slice๋Š” ์‹ค์งˆ์ ์œผ๋กœ Span์— ๋ณ€ํ™”๋ฅผ ์ฃผ์ง€ X
            count += sizeof(long);

            // string ์ฒ˜๋ฆฌ
            ushort nameLen = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
            count += sizeof(ushort);
            this.name = Encoding.Unicode.GetString(span.Slice(count, nameLen)); // GetString()๋Š” Byte ๋ฐฐ์—ด์„ ๋ฐ›์•„ string์œผ๋กœ ๋ณ€ํ™˜
            count += nameLen;
        }
    }

    // ...
}โ€‹

 

# Serialization #4

- ๋ฐ์ดํ„ฐ์˜ ๊ธธ์ด๊ฐ€ ๊ฐ€๋ณ€์ ์ธ List๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ• ๊นŒ?

List ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ServerSession Class ์™€ ClientSession Class ์ˆ˜์ •
(์•„๋ž˜ ์ฝ”๋“œ๋Š” ServerSession Class์ง€๋งŒ ClientSession Class๋„ ๋™์ผํ•œ ์ฝ”๋“œ๋กœ ์ˆ˜์ •)
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DummyClient
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        public abstract ArraySegment<byte> Write();
        public abstract void Read(ArraySegment<byte> openSegment);
    }

    class PlayerInfoReq : Packet // Client์—์„œ Server๋กœ Player์˜ ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ๋‹ค๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ
    {
        public long playerId;
        public string name; 

        public struct SkillInfo
        {
            public int id;
            public short level;
            public float duration;

            public bool Write(Span<byte> span, ref ushort count) // span์€ ์ „์ฒด Byte ๋ฐฐ์—ด์„, count๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ˜„์žฌ ์–ด๋А ๊ณณ์„ ์ž‘์—…ํ•˜๋Š”์ง€
            {
                bool success = true;
                success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), id);
                count += sizeof(int);
                success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), level);
                count += sizeof(short);
                success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), duration);
                count += sizeof(float);

                return success;
            }

            public void Read(ReadOnlySpan<byte> span, ref ushort count)
            {
                id = BitConverter.ToInt32(span.Slice(count, span.Length - count));
                count += sizeof(int);
                level = BitConverter.ToInt16(span.Slice(count, span.Length - count));
                count += sizeof(short);
                duration = BitConverter.ToSingle(span.Slice(count, span.Length - count));
                count += sizeof(float);
            }
        }

        public List<SkillInfo> skills = new List<SkillInfo>(); // ๊ฐ€๋ณ€ ๊ธธ์ด์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ?

        // ์ƒ์„ฑ์ž
        public PlayerInfoReq()
        {
            this.packetId = (ushort)PacketID.PlayerInfoReq;
        }

        public override ArraySegment<byte> Write()
        {
            ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

            bool success = true;
            ushort count = 0;

            Span<byte> span = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

            count += sizeof(ushort);
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), this.packetId); 
            count += sizeof(ushort);
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), this.playerId); 
            count += sizeof(long);

            // string ์ฒ˜๋ฆฌ
            ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this.name, 0, name.Length, openSegment.Array, openSegment.Offset + count + sizeof(ushort)); // Buffer์— string data๋ฅผ ์‚ฝ์ž…ํ•จ๊ณผ ๋™์‹œ์— string len์„ ๋ฐ˜ํ™˜
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), nameLen);
            count += sizeof(ushort);
            count += nameLen;

            // list ์ฒ˜๋ฆฌ
            success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), (ushort)skills.Count);
            count += sizeof(ushort);
            foreach (SkillInfo skill in skills)
                success &= skill.Write(span, ref count);

            success &= BitConverter.TryWriteBytes(span, count); // size๋Š” ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ๋’ค ์ดˆ๊ธฐํ™”

            if (success == false)
                return null;

            return SendBufferHelper.Close(count);
        }

        public override void Read(ArraySegment<byte> openSegment)
        {
            ushort count = 0;

            ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

            count += sizeof(ushort);
            count += sizeof(ushort);
            this.playerId = BitConverter.ToInt64(span.Slice(count, span.Length - count)); 
            count += sizeof(long);

            // string ์ฒ˜๋ฆฌ
            ushort nameLen = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
            count += sizeof(ushort);
            this.name = Encoding.Unicode.GetString(span.Slice(count, nameLen));
            count += nameLen;

            // list ์ฒ˜๋ฆฌ
            skills.Clear();
            ushort skillLen = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
            count += sizeof(ushort);  
            for (int i = 0; i < skillLen; i++)
            {
                SkillInfo skill = new SkillInfo();
                skill.Read(span, ref count);
                skills.Add(skill);
            }
        }
    }

    // ...
}โ€‹

 

# Packet Generator #1

- [ ์†”๋ฃจ์…˜ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ์ถ”๊ฐ€ ] - [ ์ƒˆ ์†”๋ฃจ์…˜ ํด๋” ] ๋ฅผ ํ†ตํ•ด ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

- [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ์ถ”๊ฐ€ ] - [ ์ƒˆ ํ•ญ๋ชฉ ] ์˜ [ C# ํ•ญ๋ชฉ ] - [ ๋ฐ์ดํ„ฐ ] ์—์„œ XML ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 ~> ์ƒ์„ฑ๋œ XML ํŒŒ์ผ์€ [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ์—์„œ ํด๋” ์—ด๊ธฐ ] - [ bin ] - [ Debug ] - [ net ] ์•ˆ์—

      ์œ„์น˜ํ•˜๋„๋ก ํ•œ๋‹ค. (์ฆ‰, ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์˜ ์‹คํ–‰ํŒŒ์ผ์ด ์žˆ๋Š” ๊ณณ์— ์œ„์น˜ํ•˜๋„๋ก)

- ํŒจํ‚ท์˜ ์ •์˜๋ฅผ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ํ• ์ง€ ๊ฒฐ์ •ํ•ด์•ผ ํ•œ๋‹ค. (JSON, XML, ์ž์ฒด ์ •์˜ IDL) 

 ~> XML์ด JSON์— ๋น„ํ•ด Hierarchy๊ฐ€ ์ž˜ ๋ณด์ธ๋‹ค๋Š” ์žฅ์ ์„ ๊ฐ€์ง€๋ฏ€๋กœ XML์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

 ~> XML์—์„œ ์ •๋ณด๋Š” ์‹œ์ž‘ Tag์™€ ๋ Tag ์‚ฌ์ด์— ๋‹ด๊ฒจ์ง€๋ฉฐ, Tag๋Š” ์Œ์„ ์ด๋ฃฌ๋‹ค.

     (Tag ์‚ฌ์ด์— ์‚ฝ์ž…ํ•  ์ •๋ณด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์‹œ์ž‘ Tag ๋์— /๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ Tag ์ƒ๋žต ๊ฐ€๋Šฅ)

- List๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„๋“ค์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ Template์„ ๋งŒ๋“ค๊ณ ์ž ํ•œ๋‹ค.

PacketGenerator ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํ›„ PDL XML ํŒŒ์ผ ์ƒ์„ฑ
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
	<packet name="PlayerInfoReq">
		<long name="playerId"/>
		<string name="name"/>
		<list name="skill">
			<int name="id"/>
			<short name="level"/>
			<float name="duration"/>
		</list>
	</packet>
</PDL>โ€‹

 

PacketGenerator ์ฝ”๋“œ ์ˆ˜์ •
using System.Xml;

namespace PacketGenerator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            XmlReaderSettings settings = new XmlReaderSettings()
            {
                IgnoreComments= true, // ์ฃผ์„์„ ๋ฌด์‹œ
                IgnoreWhitespace = true // ๊ณต๋ฐฑ์„ ๋ฌด์‹œ
            };

            // XML ํŒŒ์‹ฑ
            using (XmlReader reader = XmlReader.Create("PDL.xml", settings))
            {
                reader.MoveToContent(); // header๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋‚ด์šฉ ๋ถ€๋ถ„์œผ๋กœ ์ด๋™
                while (reader.Read()) // ํ•œ์ค„์”ฉ ์ฝ์–ด๋‚˜๊ฐ„๋‹ค.
                {
                    if (reader.Depth == 1 && reader.NodeType == XmlNodeType.Element) // Element๋Š” ์‹œ์ž‘ Tag, EndElement๋Š” ๋ Tag
                        ParsePacket(reader);

                    // Console.WriteLine(reader.Name + " " + reader["name"]); // Name์€ Type์„ ๋ฐ˜ํ™˜, []๋Š” Attribute๋ฅผ ๋ฐ˜ํ™˜
                }
            }

            // reader.Dispose(); ~> using ์‚ฌ์šฉ์‹œ using ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚  ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ Dispose()๋ฅผ ํ˜ธ์ถœ
        }

        public static void ParsePacket(XmlReader reader)
        {
            if (reader.NodeType == XmlNodeType.EndElement)
                return;

            if (reader.Name.ToLower() != "packet")
            {
                Console.WriteLine("Invalid packet node");
                return;
            }

            string packetName = reader["name"];
            if (string.IsNullOrEmpty(packetName) )
            {
                Console.WriteLine("Packet without name");
                return;
            }

            ParseMembers(reader);
        }

        public static void ParseMembers(XmlReader reader)
        {
            string packetName = reader["name"];

            int depth = reader.Depth + 1;
            while(reader.Read())
            {
                if (reader.Depth != depth)
                    break;

                string memberName = reader["name"];
                if (string.IsNullOrEmpty(memberName) )
                {
                    Console.WriteLine("Member without name");
                    return;
                }

                string memberType = reader.Name.ToLower();
                switch (memberType)
                {
                    case "bool":
                    case "byte":
                    case "short":
                    case "ushort":
                    case "int":
                    case "long":
                    case "float":
                    case "double":
                    case "string":
                    case "list":
                        break;
                    default:
                        break;
                }
            }
        }
    }
}โ€‹

 

PacketFormat Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // ์—ฌ๋Ÿฌ์ค„์— ๊ฑฐ์ณ ๋ฌธ์ž์—ด์„ ์ •์˜ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ @"" 
        // ๊ณ ์ •์ ์ธ ๋ถ€๋ถ„์„ ์ œ์™ธํ•œ ๋ณ€๊ฒฝ๋˜๋Š” ๋ถ€๋ถ„์„ {}๋กœ ํ‘œ์‹œ(์ผ๋ฐ˜์ ์ธ ์†Œ๊ด„ํ˜ธ๋Š” { { } }๋กœ ํ‘œ์‹œ)

        // {0} ํŒจํ‚ท ์ด๋ฆ„
        // {1} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค
        // {2} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read
        // {3} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write
        public static string packetFormat =
@"
class {0}
{{
    {1}

    public void Read(ArraySegment<byte> openSegment)
    {{
        ushort count = 0;

        ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);
        count += sizeof(ushort);
        count += sizeof(ushort);

        {2}
    }}

    public ArraySegment<byte> Write()
    {{
        ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

        bool success = true;
        ushort count = 0;

        Span<byte> span = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

        count += sizeof(ushort);
        success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), (ushort)PacketID.{0}); 
        count += sizeof(ushort);

        {3}

        success &= BitConverter.TryWriteBytes(span, count);

        if (success == false)
            return null;

        return SendBufferHelper.Close(count);
    }}
}}
";

        // {0} ๋ณ€์ˆ˜ ํ˜•์‹
        // {1} ๋ณ€์ˆ˜ ์ด๋ฆ„
        public static string memberFormat =
@"public {0} {1}";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} To๋ณ€์ˆ˜ํ˜•์‹ (ex : ToInt16, ToInt32, ToSingle ...)
        // {2} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string readFormat =
@"
this.{0} = BitConverter.{1}(span.Slice(count, span.Length - count));
count += sizeof({2});
";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        public static string readStringFormat =
@"
ushort {0}Len = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
count += sizeof(ushort);
this.{0} = Encoding.Unicode.GetString(span.Slice(count, {0}Len));
count += {0}Len;
";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string writeFormat =
@"
success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), this.{0}); 
count += sizeof({1});
";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        public static string writeStringFormat =
@"
ushort {0}Len = (ushort)Encoding.Unicode.GetBytes(this.{0}, 0, this.{0}.Length, openSegment.Array, openSegment.Offset + count + sizeof(ushort)); // Buffer์— string data๋ฅผ ์‚ฝ์ž…ํ•จ๊ณผ ๋™์‹œ์— string len์„ ๋ฐ˜ํ™˜
success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), {0}Len);
count += sizeof(ushort);
count += {0}Len;
";
    }
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://velog.io/@mercurios0603/%ED%8C%8C%EC%8B%B1Parsing%EC%9D%B4%EB%9E%80)

 - Parsing์€ ์ปดํ“จํ„ฐ ๊ณผํ•™ ๋ฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ํŠน์ • ํ˜•์‹์œผ๋กœ ๊ตฌ์„ฑ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๊ทธ ์˜๋ฏธ๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธ

 - Parsing์€ ์ฃผ๋กœ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด์„ํ•˜๊ฑฐ๋‚˜, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ฑฐ๋‚˜, ๋ฌธ์„œ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ 

   ๋‚ด์šฉ์„ ์ถ”์ถœํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ

 

# Packet Generator #2

- ์ง€๋‚œ์‹œ๊ฐ„ ์ œ์™ธํ•œ List ๋ถ€๋ถ„์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ Template์„ ๋งŒ๋“ค๊ณ , Packet Generator๊ฐ€ ์ž˜ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ ์ž ํ•œ๋‹ค.

 ~> Packet Generator์˜ ๊ฒฐ๊ณผ๋ฅผ "GenPackets.cs" ํŒŒ์ผ์— ์ €์žฅํ•˜์˜€๋‹ค.

 ~> "GenPackets.cs" ํŒŒ์ผ์€ [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ์—์„œ ํด๋” ์—ด๊ธฐ ] - [ bin ] - [ Debug ] - [ net ]

      ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // ...

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        // {2} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค
        // {3} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read
        // {4} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write
        public static string memberListFormat =
@"
public struct {0}
{{
    {2}

    public void Read(ReadOnlySpan<byte> span, ref ushort count)
    {{
        {3}
    }}

    public bool Write(Span<byte> span, ref ushort count)
    {{
        bool success = true;
        {4}
        return success;
    }}
}}

public List<{0}> {1}s = new List<{0}>();
";

        // ...

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        public static string readListFormat =
@"
this.{1}s.Clear();
ushort {1}Len = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
count += sizeof(ushort);  
for (int i = 0; i < {1}Len; i++)
{{
    {0} {1} = new {0}();
    {1}.Read(span, ref count);
    {1}s.Add({1});
}}
";

        // ...

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        public static string writeListFormat =
@"
success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), (ushort)this.{1}s.Count);
count += sizeof(ushort);
foreach ({0} {1} in this.{1}s)
    success &= {1}.Write(span, ref count);
";
    }
}

 

PacketGenerator ์ฝ”๋“œ ์ˆ˜์ •
using System.Xml;

namespace PacketGenerator
{
    internal class Program
    {
        static string genPackets; // ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” ํŒจํ‚ท

        static void Main(string[] args)
        {
            XmlReaderSettings settings = new XmlReaderSettings()
            {
                IgnoreComments= true, // ์ฃผ์„์„ ๋ฌด์‹œ
                IgnoreWhitespace = true // ๊ณต๋ฐฑ์„ ๋ฌด์‹œ
            };

            // XML ํŒŒ์‹ฑ
            using (XmlReader reader = XmlReader.Create("PDL.xml", settings))
            {
                reader.MoveToContent(); // header๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋‚ด์šฉ ๋ถ€๋ถ„์œผ๋กœ ์ด๋™
                while (reader.Read()) // ํ•œ์ค„์”ฉ ์ฝ์–ด๋‚˜๊ฐ„๋‹ค.
                {
                    if (reader.Depth == 1 && reader.NodeType == XmlNodeType.Element) // Element๋Š” ์‹œ์ž‘ ๋ถ€๋ถ„, EndElement๋Š” ๋ ๋ถ€๋ถ„
                        ParsePacket(reader);

                    // Console.WriteLine(reader.Name + " " + reader["name"]); // Name์€ Type์„ ๋ฐ˜ํ™˜, []๋Š” Attribute๋ฅผ ๋ฐ˜ํ™˜
                }
            }

            File.WriteAllText("GenPackets.cs", genPackets); // genPackets์˜ ๋‚ด์šฉ์„ ํ†ตํ•ด GenPackets.cs ํŒŒ์ผ ์ƒ์„ฑ
        }

        public static void ParsePacket(XmlReader reader)
        {
            if (reader.NodeType == XmlNodeType.EndElement)
                return;

            if (reader.Name.ToLower() != "packet")
            {
                Console.WriteLine("Invalid packet node");
                return;
            }

            string packetName = reader["name"];
            if (string.IsNullOrEmpty(packetName) )
            {
                Console.WriteLine("Packet without name");
                return;
            }

            Tuple<string, string, string> tuple = ParseMembers(reader);
            genPackets += string.Format(PacketFormat.packetFormat,
                packetName, tuple.Item1, tuple.Item2, tuple.Item3);
        }

        // ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write ์— ๊ด€ํ•œ ์ฝ”๋“œ๋ฅผ ์•Œ๋งž๊ฒŒ ์ œ์ž‘ํ•œ ๋’ค ์ด๋ฅผ string์œผ๋กœ ๋ฐ˜ํ™˜
        public static Tuple<string, string, string> ParseMembers(XmlReader reader)
        {
            string packetName = reader["name"];
            string memberCode = "";
            string readCode = "";
            string writeCode = "";

            int depth = reader.Depth + 1;
            while(reader.Read())
            {
                if (reader.Depth != depth)
                    break;

                string memberName = reader["name"];
                if (string.IsNullOrEmpty(memberName) )
                {
                    Console.WriteLine("Member without name");
                    return null;
                }

                if (string.IsNullOrEmpty(memberCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    memberCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘
                if(string.IsNullOrEmpty(readCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    readCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘
                if(string.IsNullOrEmpty(writeCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    writeCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘

                string memberType = reader.Name.ToLower();
                switch (memberType)
                {
                    case "bool":
                    case "short":
                    case "ushort":
                    case "int":
                    case "long":
                    case "float":
                    case "double":
                        memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
                        readCode += string.Format(PacketFormat.readFormat, memberName, ToMemberType(memberType), memberType);
                        writeCode += string.Format(PacketFormat.writeFormat, memberName, memberType);
                        break;
                    case "string":
                        memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
                        readCode += string.Format(PacketFormat.readStringFormat, memberName);
                        writeCode += string.Format(PacketFormat.writeStringFormat, memberName);
                        break;
                    case "list":
                        Tuple<string, string, string> tuple = ParseList(reader);
                        memberCode += tuple.Item1;
                        readCode += tuple.Item2;
                        writeCode += tuple.Item3;
                        break;
                    default:
                        break;
                }
            }

            // ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด Text๋ฅผ ์ •๋ ฌ
            memberCode = memberCode.Replace("\n", "\n\t"); // Enter๊ฐ€ ์ž…๋ ฅ๋œ ๊ณณ์€ Enter ์ž…๋ ฅ ํ›„ Tab ๊นŒ์ง€ ์ž…๋ ฅ๋˜๋„๋ก ์ˆ˜์ •
            readCode = readCode.Replace("\n", "\n\t\t");  // Enter๊ฐ€ ์ž…๋ ฅ๋œ ๊ณณ์€ Enter ์ž…๋ ฅ ํ›„ Tab Tab ๊นŒ์ง€ ์ž…๋ ฅ๋˜๋„๋ก ์ˆ˜์ •
            writeCode = writeCode.Replace("\n", "\n\t\t");  // Enter๊ฐ€ ์ž…๋ ฅ๋œ ๊ณณ์€ Enter ์ž…๋ ฅ ํ›„ Tab Tab ๊นŒ์ง€ ์ž…๋ ฅ๋˜๋„๋ก ์ˆ˜์ •
            return new Tuple<string, string, string>(memberCode, readCode, writeCode);
        }

        public static Tuple<string, string, string> ParseList(XmlReader reader)
        {
            string listName = reader["name"];
            if (string.IsNullOrEmpty(listName))
            {
                Console.WriteLine("List without name");
                return null;
            }

            // memberListFormat์˜ {2}, {3}, {4}๋Š” ์ˆœ์„œ๋Œ€๋กœ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write ์ด๋ฏ€๋กœ ParseMembers() ํ•จ์ˆ˜ ์‚ฌ์šฉ 
            Tuple<string, string, string> tuple = ParseMembers(reader);

            string memberCode = string.Format(PacketFormat.memberListFormat,
                FirstCharToUpper(listName), FirstCharToLower(listName),
                tuple.Item1, tuple.Item2, tuple.Item3);

            string readCode = string.Format(PacketFormat.readListFormat,
                FirstCharToUpper(listName), FirstCharToLower(listName));

            string writeCode = string.Format(PacketFormat.writeListFormat,
                FirstCharToUpper(listName), FirstCharToLower(listName));

            return new Tuple <string, string, string> (memberCode, readCode, writeCode);
        }

        public static string FirstCharToUpper(string input)
        {
            if (string.IsNullOrEmpty(input))
                return "";
            return input[0].ToString().ToUpper() + input.Substring(1);
        }

        public static string FirstCharToLower(string input)
        {
            if (string.IsNullOrEmpty(input))
                return "";
            return input[0].ToString().ToLower() + input.Substring(1);
        }

        public static string ToMemberType(string memberType)
        {
            switch (memberType)
            {
                case "bool":
                    return "ToBoolean";
                case "short":
                    return "ToInt16";
                case "ushort":
                    return "ToUInt16";
                case "int":
                    return "ToInt32";
                case "long":
                    return "ToInt64";
                case "float":
                    return "ToSingle";
                case "double":
                    return "ToDouble";
                default:
                    return "";
            } 
        }
    }
}โ€‹

 

# Packet Generator #3

- using ๊ณผ enum ๋ฐ byte ๋ถ€๋ถ„์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ Template์„ ๋งŒ๋“ค๊ณ , ํŒจํ‚ท์ด ์—ฌ๋Ÿฌ๊ฐœ ์กด์žฌํ•ด๋„ ์ž๋™ํ™”๊ฐ€ ์ž˜ ๋˜๋Š”์ง€

  ํ™•์ธํ•˜๊ณ ์ž ํ•œ๋‹ค.

PDL XML ํŒŒ์ผ ์ˆ˜์ • (ํŒจํ‚ท ๋ฐ sbyte ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ ์ถ”๊ฐ€)
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
	<packet name="PlayerInfoReq">
		<sbyte name="testByte"/>
		<long name="playerId"/>
		<string name="name"/>
		<list name="skill">
			<int name="id"/>
			<short name="level"/>
			<float name="duration"/>
		</list>
	</packet>
	<packet name="Test">
		<int name="testInt"/>
	</packet>
</PDL>โ€‹

 

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // {0} ํŒจํ‚ท ์ด๋ฆ„/๋ฒˆํ˜ธ ๋ชฉ๋ก
        // {1} ํŒจํ‚ท ๋ชฉ๋ก
        public static string fileFormat =
@"
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using ServerCore;

public enum PacketID
{{
    {0}
}}

{1}
";
        // {0} ํŒจํ‚ท ์ด๋ฆ„
        // {1} ํŒจํ‚ท ๋ฒˆํ˜ธ
        public static string packetEnumFormat =
@"{0} = {1},";

        // ...

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string readByteFormat =
@"
this.{0} = ({1})openSegment.Array[openSegment.Offset + count];
count += sizeof({1});
";

        // ...

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string writeByteFormat =
@"
openSegment.Array[openSegment.Offset + count] = (byte)this.{0};
count += sizeof({1});
";

        // ...
    }
}โ€‹

 

PacketGenerator ์ฝ”๋“œ ์ˆ˜์ •
using System.Xml;

namespace PacketGenerator
{
    internal class Program
    {
        // ...

        static void Main(string[] args)
        {
            // ...
            
            static ushort packetId; // ๋ช‡๊ฐœ์˜ ํŒจํ‚ท์„ ์ฒ˜๋ฆฌํ•˜์˜€๋Š”์ง€
            static string packetEnums; // Parsing ์ฒ˜๋ฆฌ๋œ ํŒจํ‚ท์˜ ์ด๋ฆ„๊ณผ ๋ฒˆํ˜ธ

            // fileFormat์„ ํ†ตํ•ด using๊ณผ enum ์ถ”๊ฐ€
            string fileText = string.Format(PacketFormat.fileFormat, packetEnums, genPackets);
            File.WriteAllText("GenPackets.cs", fileText);
        }

        public static void ParsePacket(XmlReader reader)
        {
            // ...

            Tuple<string, string, string> tuple = ParseMembers(reader);

            genPackets += string.Format(PacketFormat.packetFormat,
                packetName, tuple.Item1, tuple.Item2, tuple.Item3);

            // ํŒจํ‚ท 1๊ฐœ๋ฅผ Parsing ํ• ๋•Œ๋งˆ๋‹ค packetEnums์— Parsing ์ฒ˜๋ฆฌ๋œ ํŒจํ‚ท์˜ ์ด๋ฆ„๊ณผ ๋ฒˆํ˜ธ๋ฅผ ์ €์žฅ
            packetEnums += string.Format(PacketFormat.packetEnumFormat, packetName, ++packetId) + Environment.NewLine + "\t";
        }

        // ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read, ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write ์— ๊ด€ํ•œ ์ฝ”๋“œ๋ฅผ ์•Œ๋งž๊ฒŒ ์ œ์ž‘ํ•œ ๋’ค ์ด๋ฅผ string์œผ๋กœ ๋ฐ˜ํ™˜
        public static Tuple<string, string, string> ParseMembers(XmlReader reader)
        {
            string packetName = reader["name"];
            string memberCode = "";
            string readCode = "";
            string writeCode = "";

            int depth = reader.Depth + 1;
            while(reader.Read())
            {
                // ...

                if (string.IsNullOrEmpty(memberCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    memberCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘
                if(string.IsNullOrEmpty(readCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    readCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘
                if(string.IsNullOrEmpty(writeCode) == false) // ์ด๋ฏธ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
                    writeCode += Environment.NewLine; // Enter๋ฅผ ์น˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋™์ž‘

                string memberType = reader.Name.ToLower();
                switch (memberType)
                {
                    case "byte":
                    case "sbyte":
                        memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
                        readCode += string.Format(PacketFormat.readByteFormat, memberName, memberType);
                        writeCode += string.Format(PacketFormat.writeByteFormat, memberName, memberType);
                        break;
                    case "bool":
                    case "short":
                    case "ushort":
                    case "int":
                    case "long":
                    case "float":
                    case "double":
                        memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
                        readCode += string.Format(PacketFormat.readFormat, memberName, ToMemberType(memberType), memberType);
                        writeCode += string.Format(PacketFormat.writeFormat, memberName, memberType);
                        break;
                    case "string":
                        memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
                        readCode += string.Format(PacketFormat.readStringFormat, memberName);
                        writeCode += string.Format(PacketFormat.writeStringFormat, memberName);
                        break;
                    case "list":
                        Tuple<string, string, string> tuple = ParseList(reader);
                        memberCode += tuple.Item1;
                        readCode += tuple.Item2;
                        writeCode += tuple.Item3;
                        break;
                    default:
                        break;
                }
            }

            // ...
        }

        // ...
    }
}โ€‹

 

# Packet Generator #4

- Packet Generator์˜ ๊ฒฐ๊ณผ๊ฐ€ ์ €์žฅ๋œ "GenPackets.cs" ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ์ˆ˜๋™์œผ๋กœ ServerSession ๊ณผ ClientSession class์—

 ์ถ”๊ฐ€ํ•˜์˜€๋Š”๋ฐ ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ Template์„ ๋งŒ๋“ค๊ณ ์ž ํ•œ๋‹ค.

- ์ถœ๋ ฅ ๊ฒฝ๋กœ๋Š” [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ์†์„ฑ ] - [ ๋นŒ๋“œ ] - [ ์ผ๋ฐ˜ ] ์˜ [ ์ถœ๋ ฅ ] ์—์„œ ์„ค์ • ๊ฐ€๋Šฅํ•˜๋‹ค.

 ~> ๊ทธ ํ›„ [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ์—์„œ ํด๋” ์—ด๊ธฐ ] ์—์„œ

      "ํ”„๋กœ์ ํŠธ์ด๋ฆ„.csproj" ํŒŒ์ผ์„ ๋ฉ”๋ชจ์žฅ์œผ๋กœ ์—ฐ ๋’ค <PropertyGroup></PropertyGroup> ์‚ฌ์ด์—                         

      <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ ,

      <BaseOutputPath>์ถœ๋ ฅ ๊ฒฝ๋กœ</BaseOutputPath> ๋ฅผ <OutputPath>์ถœ๋ ฅ ๊ฒฝ๋กœ</OutputPath> ๋กœ ์ˆ˜์ •ํ•œ๋‹ค.

 ~> ๋ชจ๋“  ์„ค์ •์ด ๋๋‚œ ๋’ค [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ๋นŒ๋“œ ] ์‹œ ์„ค์ •ํ•œ ์ถœ๋ ฅ ๊ฒฝ๋กœ์— ์‹คํ–‰ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.

- ๋ฐฐ์น˜ ํŒŒ์ผ์€ ๋ช…๋ น ์ธํ„ฐํ”„๋ฆฌํ„ฐ์— ์˜ํ•ด ์‹คํ–‰๋˜๊ฒŒ๋” ๊ณต์•ˆ๋œ ๋ช…๋ น์–ด๋“ค์ด ๋‚˜์—ด๋œ ํ…์ŠคํŠธ ํŒŒ์ผ์ด๋‹ค.

 ~> .bat ๋˜๋Š” .cmd ํ˜•์‹์˜ ํ™•์žฅ์ž ํŒŒ์ผ์„ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๋ช…๋ น ํ”„๋กฌํ”„ํŠธ์—์„œ ๋ฐฐ์น˜ ํŒŒ์ผ์˜ ์ด๋ฆ„์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

 ~> ๋ฐฐ์น˜ํŒŒ์ผ์„ ํ†ตํ•ด [ ํ”„๋กœ์ ํŠธ ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ์—์„œ ํด๋” ์—ด๊ธฐ ] - [ bin ] ๋‚ด์˜ ํ”„๋กœ์ ํŠธ ์‹คํ–‰ํŒŒ์ผ์„

      ๋Œ€์‹  ์‹คํ–‰ํ•œ ๋’ค ์‹คํ–‰ ํŒŒ์ผ์˜ ๊ฒฐ๊ณผ์— ์ €์žฅ๋œ ๋‚ด์šฉ์„ ์ž๋™์œผ๋กœ ๋ณต์‚ฌํ•  ์˜ˆ์ •์ด๋‹ค. (์ถœ๋ ฅ ๊ฒฝ๋กœ๋ฅผ /bin ์œผ๋กœ ์„ค์ •)

 ~> START๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ๋ช…๋ น์–ด๋กœ ์‹คํ–‰ํ•˜๊ณ ์ž ํ•˜๋Š” ํŒŒ์ผ์˜ ๊ฒฝ๋กœ์™€ ์ธ์ž๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค.

 ~> XCOPY๋Š” ํŒŒ์ผ ๋ณต์‚ฌ ๋ช…๋ น์–ด๋กœ ๋ณต์‚ฌ ๋Œ€์ƒ๊ณผ ๋ณต์‚ฌ ์œ„์น˜๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค.

     (/Y ์˜ต์…˜์€ ๊ฐ™์€ ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋ฌด์กฐ๊ฑด ๋ฎ์–ด์“ด๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.)

GenPackets.bat ๋ฐฐ์น˜ ํŒŒ์ผ ์ƒ์„ฑ (๋ฐฐ์น˜ ํŒŒ์ผ์€ [ ์†”๋ฃจ์…˜ ] - [ Common ] - [ Packet ] ๋‚ด์— ์กด์žฌ)

(๋ฐฐ์น˜ ํŒŒ์ผ ์‹คํ–‰์‹œ PDL.xml์„ ์ธ์ž๋กœ ๋„˜๊ธด Packet Generator๊ฐ€ ์‹คํ–‰๋˜์–ด DummyClient/Packet ๊ณผ Server/Packet ์‚ฐํ•˜์˜ GenPackets์— Packet Generator์˜ ๊ฒฐ๊ณผ๊ฐ€ ์ €์žฅ๋œ "GenPackets.cs" ํŒŒ์ผ์˜ ๋‚ด์šฉ์ด ์ž๋™์œผ๋กœ ๋ณต์‚ฌ๋œ๋‹ค.)

START ../../PacketGenerator/bin/PacketGenerator.exe ../../PacketGenerator/PDL.xml
XCOPY /Y GenPackets.cs "../../DummyClient/Packet"
XCOPY /Y GenPackets.cs "../../Server/Packet"โ€‹

 

PacketGenerator ์ฝ”๋“œ ์ˆ˜์ •
using System.Xml;

namespace PacketGenerator
{
    internal class Program
    {
        // ...

        static void Main(string[] args)
        {
            string pdlPath = "../PDL.xml"; // bin ํด๋”๊ฐ€ ์•„๋‹Œ PacketGenerator ํด๋”์— ์žˆ๋Š” PDL.xml ์ฐพ๊ธฐ ์œ„ํ•œ ๊ฒƒ (์‹คํ–‰ ํŒŒ์ผ์ด ์œ„์น˜ํ•œ ๊ณณ ๊ธฐ์ค€์œผ๋กœ ์ด๋™)

            // ...

            if (args.Length >= 1) // ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰์‹œ ์ธ์ž๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋„˜๊ฒจ์ค€ ๊ฒฝ์šฐ
                pdlPath = args[0]; // pdlPath๋ฅผ ์ „๋‹ฌ๋ฐ›์€ ์ธ์ž๋กœ ์ดˆ๊ธฐํ™”

            using (XmlReader reader = XmlReader.Create(pdlPath, settings))
            {
                // ...
            }

            // ...
        }

        // ...
    }
}โ€‹

 

# Packet Generator #5

- ๊ฒŒ์ž„ ๊ทœ๋ชจ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ํŒจํ‚ท์˜ ์ข…๋ฅ˜๋Š” ์ƒ๋‹นํžˆ ๋งŽ์•„์ง„๋‹ค.

 ~> ์ฆ‰, ClientSession Class์˜ OnRecvPacket() ํ•จ์ˆ˜ ์•ˆ์˜ switch-case๋ฌธ ๊ธธ์ด๊ฐ€ ์ƒ๋‹นํžˆ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

- switch-case๋ฌธ ๋ฐ OnRecvPacket() ํ•จ์ˆ˜ ์ž๋™ํ™”๋ฅผ ์œ„ํ•ด ๋ชจ๋“  ํŒจํ‚ท๋“ค์ด base interface๋ฅผ ์ƒ์†๋ฐ›๋„๋ก ํ•œ๋‹ค.

 ~> ๋ชจ๋“  ํŒจํ‚ท์— ๋Œ€ํ•ด ๊ณตํ†ต ์ธ์ˆ˜๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŽธ๋ฆฌํ•˜๋‹ค๋Š” ์žฅ์ ์„ ๊ฐ€์ง„๋‹ค.

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // {0} ํŒจํ‚ท ์ด๋ฆ„/๋ฒˆํ˜ธ ๋ชฉ๋ก
        // {1} ํŒจํ‚ท ๋ชฉ๋ก
        public static string fileFormat =
@"
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using ServerCore;

public enum PacketID
{{
    {0}
}}

interface IPacket
{{
	ushort Protocol {{ get; }}
	void Read(ArraySegment<byte> openSegment);
	ArraySegment<byte> Write();
}}

{1}
";
        // ...

        // {0} ํŒจํ‚ท ์ด๋ฆ„
        // {1} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค
        // {2} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read
        // {3} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write
        public static string packetFormat =
@"
public class {0} : IPacket
{{
    {1}

    public ushort Protocol {{ get {{ return (ushort)PacketID.{0}; }} }}

    public void Read(ArraySegment<byte> openSegment)
    {{
        ushort count = 0;

        ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);
        count += sizeof(ushort);
        count += sizeof(ushort);

        {2}
    }}

    public ArraySegment<byte> Write()
    {{
        ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);

        bool success = true;
        ushort count = 0;

        Span<byte> span = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);

        count += sizeof(ushort);
        success &= BitConverter.TryWriteBytes(span.Slice(count, span.Length - count), (ushort)PacketID.{0}); 
        count += sizeof(ushort);

        {3}

        success &= BitConverter.TryWriteBytes(span, count);

        if (success == false)
            return null;

        return SendBufferHelper.Close(count);
    }}
}}
";

        // ...
    }
}โ€‹

 

DummyClient/Packet ๊ณผ Server/Packet ์‚ฐํ•˜์— PacketHandler Class ์ƒ์„ฑ
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class PacketHandler 
    {
        // ํ•ด๋‹น ํŒจํ‚ท์ด ์ „๋ถ€ ์กฐ๋ฆฝ๋œ ๊ฒฝ์šฐ ๋ฌด์—‡์„ ํ• ๊นŒ?
        // PacketHandler๋Š” ์ž๋™ํ™” ์—†์ด ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€
        public static void PlayerInfoReqHandler(PacketSession session, IPacket packet)
        {
            PlayerInfoReq p = packet as PlayerInfoReq;

            Console.WriteLine($"PlayerInfoReq: {p.playerId} {p.name}");

            foreach (PlayerInfoReq.Skill skill in p.skills)
            {
                Console.WriteLine($"Skill({skill.id})({skill.level})({skill.duration})");
            }
        }
    }
}โ€‹

 

DummyClient/Packet ๊ณผ Server/Packet ์‚ฐํ•˜์— PacketManager Class ์ƒ์„ฑ
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class PacketManager
    {
        // PacketManager๋Š” Singleton ํŒจํ„ด ์‚ฌ์šฉ
        #region Singleton
        static PacketManager _instance;
        public static PacketManager Instance
        {
            get { 
                if (_instance == null)
                    _instance = new PacketManager();
                return _instance; 
            }
        }
        #endregion

        // ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ Protocol ID, ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ• ์ง€
        Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
        // ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ Protocol ID, ์–ด๋–ค Handler๋ฅผ ํ˜ธ์ถœํ• ์ง€
        Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();

        public void Register() // ์ถ”ํ›„ ์ž๋™ํ™”ํ•  ์˜ˆ์ •
        {
            _onRecv.Add((ushort)PacketID.PlayerInfoReq, MakePacket<PlayerInfoReq>);
            _handler.Add((ushort)PacketID.PlayerInfoReq, PacketHandler.PlayerInfoReqHandler);
        }

        public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
        {
            // ํŒจํ‚ท์„ ๋ถ„ํ•ดํ•˜์—ฌ id ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์€ ๋’ค
            ushort count = 0;
            ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
            count += 2;
            ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
            count += 2;

            // ์ด์   switch-case๋ฌธ์ด ์•„๋‹Œ Dictionary์—์„œ ์ฐพ์•„ Invoke()
            Action<PacketSession, ArraySegment<byte>> action = null;
            if (_onRecv.TryGetValue(id, out action))
                action.Invoke(session, buffer);
        }

        void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
        {
            T packet = new T();
            packet.Read(buffer); // ์—ญ์ง๋ ฌํ™”

            // Dictionary์—์„œ ์ฐพ์•„ Invoke()
            Action<PacketSession, IPacket> action = null;
            if (_handler.TryGetValue(packet.Protocol, out action))
                action.Invoke(session, packet);
        }
    }
}โ€‹

 

ClientSession Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    class ClientSession : PacketSession 
    {
        // ...

        public override void OnRecvPacket(ArraySegment<byte> buffer)
        {
            PacketManager.Instance.OnRecvPacket(this, buffer);
        }

        // ...
    }
}โ€‹

 

Server ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        // ...

        static void Main(string[] args)
        {
            // MultiThread๊ฐ€ ๊ฐœ์ž…ํ•˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„์—์„œ ์‹คํ–‰
            PacketManager.Instance.Register();

            // ...
        }
    }
}โ€‹

 

# Packet Generator #6

- PacketManager ๋ฅผ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ Template์„ ๋งŒ๋“ค๊ณ ์ž ํ•œ๋‹ค.

 ~> ์–‘๋ฐฉํ–ฅ ํŒจํ‚ท์€ ๊ฑฐ์˜ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. (๋Œ€๋ถ€๋ถ„ Client์—์„œ Server ๋˜๋Š” Server์—์„œ Client)

 ~> ๊ทธ๋Ÿฌ๋‚˜ ๋ถ„์‚ฐ Server์ธ ๊ฒฝ์šฐ Client์™€ Server๋งŒ ์†Œํ†ตํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ Server ๋ผ๋ฆฌ๋„ ์†Œํ†ตํ•œ๋‹ค.

 ~> ํŒจํ‚ท ์ด๋ฆ„์— ๊ทœ์น™์„ ์„ค์ •ํ•˜๊ณ  ์ด๋ฅผ ํ†ตํ•ด ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

     (ํŒจํ‚ท ์ด๋ฆ„ ์•ž์— C_ ๊ฐ€ ๋ถ™์€ ๊ฒƒ์€ Client์—์„œ Server๋กœ, S_ ๊ฐ€ ๋ถ™์€ ๊ฒƒ์€ Server์—์„œ Client๋กœ)

     (PacketManager ๋ฅผ ์ž๋™ํ™”ํ• ๋•Œ Server ์ชฝ์— ์ถ”๊ฐ€๋˜๋Š” PacketManager์˜ Register์—๋Š” C_๊ฐ€ ๋ถ™์€ ๊ฒƒ๋“ค๋งŒ,
      DummyClient ์ชฝ์— ์ถ”๊ฐ€๋˜๋Š” PacketManager์˜ Register์—๋Š” S_๊ฐ€ ๋ถ™์€ ๊ฒƒ๋“ค๋งŒ ๋“ฑ๋กํ•œ๋‹ค.)

     (์ฆ‰, Register์— ์˜จ๊ฐ– ํŒจํ‚ท์„ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ํ•„์š”ํ•œ ํŒจํ‚ท๋“ค๋งŒ ๋“ฑ๋กํ•œ๋‹ค.)

PDL XML ํŒŒ์ผ ์ˆ˜์ •
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
	<packet name="C_PlayerInfoReq">
		<sbyte name="testByte"/>
		<long name="playerId"/>
		<string name="name"/>
		<list name="skill">
			<int name="id"/>
			<short name="level"/>
			<float name="duration"/>
		</list>
	</packet>
	<packet name="Test">
		<int name="testInt"/>
	</packet>
</PDL>โ€‹

 

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // {0} ํŒจํ‚ท ๋“ฑ๋ก
        public static string managerFormat =
@"
using ServerCore;
using System;
using System.Collections.Generic;

class PacketManager
{{
    #region Singleton
    static PacketManager _instance;
    public static PacketManager Instance
    {{
        get 
        {{
            if (_instance == null)
                _instance = new PacketManager();
            return _instance; 
        }}
    }}
    #endregion

    Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
    Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();

    public void Register() 
    {{
{0}
    }}

    public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
    {{
        ushort count = 0;
        ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;
        ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;

        Action<PacketSession, ArraySegment<byte>> action = null;
        if (_onRecv.TryGetValue(id, out action))
            action.Invoke(session, buffer);
    }}

    void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
    {{
        T packet = new T();
        packet.Read(buffer);

        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(packet.Protocol, out action))
            action.Invoke(session, packet);
    }}
}}
";

        // {0} ํŒจํ‚ท ์ด๋ฆ„
        public static string managerRegisterFormat =
@"
        _onRecv.Add((ushort)PacketID.{0}, MakePacket<{0}>);
        _handler.Add((ushort)PacketID.{0}, PacketHandler.{0}Handler);
";

        // ...
    }
}โ€‹

 

PacketGenerator ์ฝ”๋“œ ์ˆ˜์ •
using System.Xml;

namespace PacketGenerator
{
    internal class Program
    {
        // ...

        static string clientRegister;
        static string serverRegister;

        static void Main(string[] args)
        {
            // ...
            
            string clientManagerText = string.Format(PacketFormat.managerFormat, clientRegister);
            File.WriteAllText("ClientPacketManager.cs", clientManagerText);
            string serverManagerText = string.Format(PacketFormat.managerFormat, serverRegister);
            File.WriteAllText("ServerPacketManager.cs", serverManagerText);
        }

        public static void ParsePacket(XmlReader reader)
        {
            // ...
            
            if (packetName.StartsWith("S_") || packetName.StartsWith("s_"));
                clientRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
            else
                serverRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
        }

        // ...
    }
}โ€‹

 

GenPackets.bat ๋ฐฐ์น˜ ํŒŒ์ผ ์ˆ˜์ •
START ../../PacketGenerator/bin/PacketGenerator.exe ../../PacketGenerator/PDL.xml
XCOPY /Y GenPackets.cs "../../DummyClient/Packet"
XCOPY /Y GenPackets.cs "../../Server/Packet"
XCOPY /Y ClientPacketManager.cs "../../DummyClient/Packet"
XCOPY /Y ServerPacketManager.cs "../../Server/Packet"

 

[ ์„น์…˜ 4. Job Queue ]

# ์ฑ„ํŒ… ํ…Œ์ŠคํŠธ #1

- Server ๊ตฌํ˜„์‹œ ๋Œ€๋ถ€๋ถ„ ์ฑ„ํŒ… ํ”„๋กœ๊ทธ๋žจ์„ ํ†ตํ•ด ํ…Œ์ŠคํŠธ๊ฐ€ ์ด๋ฃจ์–ด์ง„๋‹ค.

- ์šฐ์„  ServerCore์— ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ˆ˜์ •ํ•  ์˜ˆ์ •์ด๋‹ค.

 ~> Disconnect() ์ค‘๋ณต ํ˜ธ์ถœ์€ ์˜ˆ๋ฐฉํ•˜์˜€์œผ๋‚˜, Disconnect() ํ˜ธ์ถœ์‹œ Send์™€ Receive๋Š” ๋Š๊น€์— ๋Œ€ํ•œ ์˜ˆ๋ฐฉ์ด ์—†๋‹ค.

     (๋™์‹œ๋‹ค๋ฐœ์ ์œผ๋กœ ๋ˆ„๊ตฐ๊ฐ€๋Š” Disconnect๋ฅผ ํ†ตํ•ด socket์„ shutdownํ•˜๊ณ , ๋ˆ„๊ตฐ๊ฐ€๋Š” Send๋‚˜ Receive๋ฅผ ํ˜ธ์ถœ์‹œ ๋ฌธ์ œ ๋ฐœ์ƒ)

- ๋˜ํ•œ ์ฑ„ํŒ… ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด Server ์ž…์žฅ์—์„œ ์ฝ”๋“œ ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ •์„ ํ•  ์˜ˆ์ •์ด๋‹ค.

PDL XML ํŒŒ์ผ ์ˆ˜์ •
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
	<packet name="C_Chat">
		<string name="chat"/>
	</packet>
	<packet name="S_Chat">
		<int name="playerId"/>
		<string name ="chat"/>
	</packet>
</PDL>โ€‹

 

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด Session Class ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace ServerCore
{
    // ...

    public abstract class Session
    {
        // ...

        void Clear() // _sendQueue ์™€ _pendingList ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ ์ถ”๊ฐ€
        {
            lock (_lock)
            {
                _sendQueue.Clear();
                _pendingList.Clear();
            }
        }

        // ...

        public void Disconnect()
        {
            // ...

            Clear(); // _sendQueue ์™€ _pendingList ๋ฅผ ์ดˆ๊ธฐํ™”
        }

        void RegisterSend()
        {
            if (_disconnected == 1) // ์ตœ์†Œํ•œ์˜ ์˜ˆ๋ฐฉ์ฑ…
                return;

            while (_sendQueue.Count > 0)
            {
                ArraySegment<byte> buff = _sendQueue.Dequeue();
                _pendingList.Add(buff);
            }
            _sendArgs.BufferList = _pendingList;

            // socket์„ ๋‹ค๋ฃจ๋Š” ๋ถ€๋ถ„์„ try-catch๋ฌธ์œผ๋กœ ๊ฐ์‹ธ์ค€๋‹ค. (MultiThread ํ™˜๊ฒฝ์„ ์œ„ํ•œ ์˜ˆ๋ฐฉ์ฑ…)
            // ~> ๋ˆ„๊ตฐ๊ฐ€๋Š” ์œ„์˜ if๋ฌธ์„ ํ†ต๊ณผํ•˜์—ฌ ์•„๋ž˜ ๋ถ€๋ถ„์„ ๋งˆ์ € ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜๋Š” ๋„์ค‘์— ๋‹ค๋ฅธ Thread์—์„œ socket์„ disconnect์‹œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ
            try
            {
                bool pending = _socket.SendAsync(_sendArgs);
                if (pending == false)
                    OnSendCompleted(null, _sendArgs);
            }
            catch (Exception e)
            {
                Console.WriteLine($"RegisterSend Failed {e}");
            }
        }

       // ...

        void RegisterRecv()
        {
            if (_disconnected == 1) // ์ตœ์†Œํ•œ์˜ ์˜ˆ๋ฐฉ์ฑ…
                return;

            _recvBuffer.Clean();
            ArraySegment<byte> segment = _recvBuffer.WriteSegment;
            _recvArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);

            // socket์„ ๋‹ค๋ฃจ๋Š” ๋ถ€๋ถ„์„ try-catch๋ฌธ์œผ๋กœ ๊ฐ์‹ธ์ค€๋‹ค. (MultiThread ํ™˜๊ฒฝ์„ ์œ„ํ•œ ์˜ˆ๋ฐฉ์ฑ…)
            // ~> ๋ˆ„๊ตฐ๊ฐ€๋Š” ์œ„์˜ if๋ฌธ์„ ํ†ต๊ณผํ•˜์—ฌ ์•„๋ž˜ ๋ถ€๋ถ„์„ ๋งˆ์ € ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜๋Š” ๋„์ค‘์— ๋‹ค๋ฅธ Thread์—์„œ socket์„ disconnect์‹œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ
            try
            {
                bool pending = _socket.ReceiveAsync(_recvArgs);
                if (pending == false)
                    OnRecvCompleted(null, _recvArgs);
            }
            catch(Exception e)
            {
                Console.WriteLine($"RegisterRecv Failed {e}");
            }
        }

        // ...
    }
}โ€‹

 

Server์— GameRoom Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class GameRoom
    {
        List<ClientSession> _sessions = new List<ClientSession>(); // GameRoom์— ์กด์žฌํ•˜๋Š” session๋“ค
        object _lock = new object(); // List๋‚˜ Dictionary ๋“ฑ ๋Œ€๋ถ€๋ถ„์˜ ์ž๋ฃŒ ๊ตฌ์กฐ๋“ค์€ MultiThread ํ™˜๊ฒฝ์—์„œ ์ž˜ ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋ณด์žฅ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— lock ์ƒ์„ฑ

        public void Broadcast(ClientSession session, string chat) // ํ˜„์žฌ session์ด ์ ‘์†์ค‘์ธ ๋ฐฉ์— ์กด์žฌํ•˜๋Š” ๋ชจ๋‘์—๊ฒŒ chat์„ ๋ฟŒ๋ฆฐ๋‹ค.
        {
            S_Chat packet = new S_Chat();
            packet.playerId = session.SessionId;
            packet.chat = chat;
            ArraySegment<byte> segment = packet.Write();

            lock(_lock)
            {
                foreach (ClientSession s in _sessions)
                    s.Send(segment);
            }
        }

        public void Enter(ClientSession session) // ๋ฐฉ ์ž…์žฅ
        {
            lock (_lock)
            {
                _sessions.Add(session);
                session.Room = this;
            }   
        }

        public void Leave(ClientSession session) // ๋ฐฉ ํ‡ด์žฅ
        {
            lock (_lock)
            {
                _sessions.Remove(session);
            }
        }
    }
}โ€‹

 

Server์— SessionManager Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    class SessionManager // SessionManager๋Š” Engine ์ชฝ์—์„œ ๊ด€๋ฆฌํ•ด๋„ ๋˜๊ณ , Content ์ชฝ์—์„œ ๊ด€๋ฆฌํ•ด๋„ ๋œ๋‹ค. (์„ ํƒ์˜ ์ฐจ์ด)
    {
        // SessionManager๋Š” Singleton ํŒจํ„ด ์‚ฌ์šฉ
        static SessionManager _session = new SessionManager();
        public static SessionManager Instance { get { return _session; } }

        int _sessionId = 0; // session์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ Id
        Dictionary<int, ClientSession> _sessions = new Dictionary<int, ClientSession>(); // ํ˜„์žฌ ์กด์žฌํ•˜๋Š” session๋“ค
        object _lock = new object(); // List๋‚˜ Dictionary ๋“ฑ ๋Œ€๋ถ€๋ถ„์˜ ์ž๋ฃŒ ๊ตฌ์กฐ๋“ค์€ MultiThread ํ™˜๊ฒฝ์—์„œ ์ž˜ ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋ณด์žฅ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— lock ์ƒ์„ฑ

        public ClientSession Generate() // session ์ƒ์„ฑ
        {
            lock (_lock)
            {
                int sessionId = ++_sessionId;

                ClientSession session = new ClientSession();
                session.SessionId = sessionId;
                _sessions.Add(sessionId, session);

                Console.WriteLine($"Connected : {sessionId}");

                return session;
            }
        }

        public ClientSession Find(int id) // sessionId๋ฅผ ํ†ตํ•ด session์„ ์ฐพ๋Š” ํ•จ์ˆ˜
        {
            lock (_lock)
            {
                ClientSession session = null;
                _sessions.TryGetValue(id, out session);
                return session;
            }
        }

        public void Remove(ClientSession session) // session ์‚ญ์ œ
        {
            lock (_lock)
            {
                _sessions.Remove(session.SessionId);
            }
        }
    }
}โ€‹

 

Server ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        static Listener _listener = new Listener();
        public static GameRoom Room = new GameRoom(); // GameRoom ์ƒ์„ฑ (๋”ด ๊ณณ์—์„œ๋„ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก public์œผ๋กœ ์ƒ์„ฑ)

        static void Main(string[] args)
        {
            PacketManager.Instance.Register();

            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            // ์†๋‹˜์„ ์ž…์žฅ์‹œํ‚จ๋‹ค.
            _listener.Init(endPoint, () => { return SessionManager.Instance.Generate(); }); // new๋ฅผ ํ†ตํ•ด Session์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ SessionManager๋ฅผ ํ†ตํ•ด ์ƒ์„ฑํ•˜๋„๋ก ์ˆ˜์ •
            Console.WriteLine("Listening...");

            while (true)
            {

            }
        }
    }
}โ€‹

 

ClientSession ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    class ClientSession : PacketSession 
    {
        public int SessionId { get; set; } // Session ๊ตฌ๋ถ„์„ ์œ„ํ•ด
        public GameRoom Room { get; set; } // ํ˜„์žฌ ์–ด๋–ค ๋ฐฉ์— ์œ„์น˜ํ•˜๋Š”์ง€ ์•Œ๊ธฐ ์œ„ํ•ด

        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");

            Program.Room.Enter(this); // Client๊ฐ€ ์ ‘์†์‹œ ๋ฐฉ์— ์ž…์žฅ์‹œํ‚จ๋‹ค. (Program ์‚ฐํ•˜์— static์œผ๋กœ Room์„ ์ƒ์„ฑํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜ธ์ถœ)

            Thread.Sleep(5000);
            Disconnect();
        }

        // ...

        public override void OnDisconnected(EndPoint endPoint)
        {
            SessionManager.Instance.Remove(this); // ๋‚ด ์ž์‹ ์„ (์ฆ‰, session์„) sessionManager๋ฅผ ํ†ตํ•ด ์‚ญ์ œ ์š”์ฒญ
            if (Room != null) // 
            {
                Room.Leave(this);
                Room = null;
            }

            Console.WriteLine($"OnDisconnected : {endPoint}");
        }

        // ...
    }
}โ€‹

 

Server์˜ PacketHandler ์ˆ˜์ •
using Server;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

class PacketHandler 
{
    // ํ•ด๋‹น ํŒจํ‚ท์ด ์ „๋ถ€ ์กฐ๋ฆฝ๋œ ๊ฒฝ์šฐ ๋ฌด์—‡์„ ํ• ๊นŒ?
    // PacketHandler๋Š” ์ž๋™ํ™” ์—†์ด ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€
    public static void C_ChatHandler(PacketSession session, IPacket packet)
    {
        C_Chat chatPacket = packet as C_Chat;
        ClientSession clientSession = session as ClientSession;

        if (clientSession.Room == null)
            return;

        clientSession.Room.Broadcast(clientSession, chatPacket.chat); // ํ˜„์žฌ clientSession์ด ์ ‘์†์ค‘์ธ ๋ฐฉ์— ์กด์žฌํ•˜๋Š” ๋ชจ๋‘์—๊ฒŒ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋ฅผ ๋ฟŒ๋ฆฐ๋‹ค.
    }
}โ€‹

 

# ์ฑ„ํŒ… ํ…Œ์ŠคํŠธ #2

- ์ฑ„ํŒ… ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด Client ์ž…์žฅ์—์„œ ์ฝ”๋“œ ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ •์„ ํ•  ์˜ˆ์ •์ด๋‹ค.

 ~>  ํ˜„์žฌ 1๋ช…์˜ ์œ ์ €๋งŒ ์ ‘์†ํ•˜๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์ด๋ฏ€๋กœ, ์ด๋ฅผ ๋‹ค์ˆ˜์˜ ์œ ์ €๋“ค์ด ์ ‘์†ํ•˜๋Š” ์ƒํ™ฉ์œผ๋กœ ๊ฐ€์ •ํ•˜๊ณ  ๋ณ€๊ฒฝํ•  ์˜ˆ์ •์ด๋‹ค.

- ๊ธฐ์กด Server์˜ Register ๋ถ€๋ถ„์„ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋„๋ก ๋ณ€๊ฒฝํ•  ์˜ˆ์ •์ด๋‹ค.

ServerSession Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DummyClient
{
    class ServerSession : PacketSession // Session์ด ์•„๋‹Œ PacketSession ์„ ์ƒ์† ๋ฐ›๋„๋ก ์ˆ˜์ •
    {
        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");
        }

        public override void OnDisconnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnDisconnected : {endPoint}");
        }

        public override void OnRecvPacket(ArraySegment<byte> buffer) // PacketSession ์„ ์ƒ์† ๋ฐ›์œผ๋ฏ€๋กœ OnRecv๊ฐ€ ์•„๋‹Œ OnRecvPacket์œผ๋กœ ์ˆ˜์ • (๋ฐ˜ํ™˜๊ฐ’๋„ int๊ฐ€ ์•„๋‹Œ void๋กœ)
        {
            PacketManager.Instance.OnRecvPacket(this, buffer);
        }

        public override void OnSend(int numOfBytes)
        {
            // Console.WriteLine($"Transferred bytes : {numOfBytes}"); ~> session์ด ๋งŽ์•„์ง€๋ฉด OnSend() ๊ฐ€ ์ž์ฃผ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ์ผ๋‹จ์€ ์ถœ๋ ฅ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์„ ์ฒ˜๋ฆฌ
        }
    }
}

 

ServerCore์˜ Connector ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerCore
{
    public class Connector
    {
        Func<Session> _sessionFactory;

        public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory, int count = 1) // ๋‹ค์ˆ˜์˜ Client ํ™˜๊ฒฝ์—์„œ Test ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
        {
            for (int i = 0; i < count; i++) // ์ž…๋ ฅ๋ฐ›์€ ๋งค๊ฐœ๋ณ€์ˆ˜ count ๋งŒํผ ์•„๋ž˜์˜ ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•˜๋„๋ก ์ˆ˜์ •
            {
                // ํœด๋Œ€ํฐ ์„ค์ •
                Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _sessionFactory = sessionFactory;

                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.Completed += OnConnectCompleted;
                args.RemoteEndPoint = endPoint;
                args.UserToken = socket;

                RegisterConnect(args);
            } 
        }

        // ...
    }
}โ€‹

 

DummyClient์— SessionManager Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Text;

namespace DummyClient
{
    class SessionManager
    {
        // SessionManager๋Š” Singleton ํŒจํ„ด ์‚ฌ์šฉ
        static SessionManager _session = new SessionManager();
        public static SessionManager Instance { get { return _session; } }

        List<ServerSession> _sessions = new List<ServerSession>(); // ํ˜„์žฌ ์กด์žฌํ•˜๋Š” session๋“ค
        object _lock = new object(); // List๋‚˜ Dictionary ๋“ฑ ๋Œ€๋ถ€๋ถ„์˜ ์ž๋ฃŒ ๊ตฌ์กฐ๋“ค์€ MultiThread ํ™˜๊ฒฝ์—์„œ ์ž˜ ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋ณด์žฅ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— lock ์ƒ์„ฑ

        public void SendForEach() // Server์ชฝ์œผ๋กœ ์ฑ„ํŒ… ํŒจํ‚ท์„ ์ „์†ก
        {
            foreach (ServerSession session in _sessions)
            {
                C_Chat chatPacket = new C_Chat();
                chatPacket.chat = $"Hello Server!";
                ArraySegment<byte> segment = chatPacket.Write();

                session.Send(segment);
            }
        }

        public ServerSession Generate()  // session ์ƒ์„ฑ
        {
            lock (_lock)
            {
                ServerSession session = new ServerSession();
                _sessions.Add(session);
                return session;
            }
        }
    }
}โ€‹

 

DummyClient ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using ServerCore;

namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = Dns.GetHostName(); 
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0]; 
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            Connector connector = new Connector();
            connector.Connect(endPoint, () => { return SessionManager.Instance.Generate(); }, 10); // new๋ฅผ ํ†ตํ•ด Session์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ SessionManager๋ฅผ ํ†ตํ•ด ์ƒ์„ฑํ•˜๋„๋ก ์ˆ˜์ •, ์›ํ•˜๋Š” Client ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋„˜๊น€

            while (true)
            {
                try
                {
                    SessionManager.Instance.SendForEach(); // ๋ชจ๋“  Session๋“ค์ด Server์ชฝ์œผ๋กœ ๊ณ„์†ํ•ด์„œ ์ฑ„ํŒ… ํŒจํ‚ท์„ ์˜๋„๋ก
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }

                Thread.Sleep(250); // 0.25์ดˆ ํœด์‹
            }
        }
    }
}โ€‹

 

DummyClient์˜ PacketHandler ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

class PacketHandler
{
    // C_Chat์„ Server์— ๋ณด๋‚ธ๋’ค, Server๋Š” ๋ฐฉ์— ์žˆ๋Š” ๋ชจ๋“  ์• ๋“ค์—๊ฒŒ S_Chat์œผ๋กœ ๋‹ต์žฅ์„ ์ฃผ๋Š” ๋ถ€๋ถ„์„ ๋‹ค๋ฃฌ๋‹ค.
    public static void S_ChatHandler(PacketSession session, IPacket packet)
    {
        S_Chat chatPacket = packet as S_Chat;
        ServerSession serverSession = session as ServerSession;

        Console.WriteLine(chatPacket.chat);
    }
}โ€‹

 

PacketFormat Class ์ˆ˜์ •
(Main์—์„œ PacketManager.Instance.Register(); ๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋„๋ก ์ˆ˜์ •)
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // {0} ํŒจํ‚ท ๋“ฑ๋ก
        public static string managerFormat =
@"
using ServerCore;
using System;
using System.Collections.Generic;

class PacketManager
{{
    #region Singleton
    static PacketManager _instance = new PacketManager();
    public static PacketManager Instance {{ get {{ return _instance; }} }}
    #endregion

    PacketManager()
    {{
        Register();
    }}

    Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
    Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();

    public void Register() 
    {{
{0}
    }}

    public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
    {{
        ushort count = 0;
        ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;
        ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;

        Action<PacketSession, ArraySegment<byte>> action = null;
        if (_onRecv.TryGetValue(id, out action))
            action.Invoke(session, buffer);
    }}

    void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
    {{
        T packet = new T();
        packet.Read(buffer);

        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(packet.Protocol, out action))
            action.Invoke(session, packet);
    }}
}}
";

        // ...
    }
}โ€‹

 

- ์œ„์˜ ๊ฒฐ๊ณผ๋กœ GameRoom ์•ˆ์— ์กด์žฌํ•˜๋Š” 10๋ช…์˜ ์œ ์ €๋“ค์—๊ฒŒ ์ฑ„ํŒ… ํŒจํ‚ท์„ ๋ฟŒ๋ ค์ฃผ๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 ~>  ๊ทธ๋Ÿฌ๋‚˜ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ MMORPG์— ๋„์ž…ํ•  ๊ฒฝ์šฐ ์†๋„๊ฐ€ ์ƒ๋‹นํžˆ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค.

       (์œ ์ €์˜ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ• ์ˆ˜๋ก, ํŒจํ‚ท์„ ๋ฟŒ๋ฆฌ๋Š” ์–‘์ด ๋งŽ์•„์ง€๋ฏ€๋กœ)

- ์œ„์˜ Thread๋“ค์€ ๋ชจ๋‘ Broadcast์˜ lock ๋ถ€๋ถ„์—์„œ ๋Œ€๊ธฐ์ค‘์ด๋‹ค.

 ~> ์ด๋Š” ๋‹น์—ฐํ•œ ๊ฒฐ๊ณผ์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด Thread.Sleep(250) ์„ ํ†ตํ•ด 0.25์ดˆ์— 1๋ฒˆ ๋™์ž‘ํ•˜๋„๋ก ์„ค์ •ํ•˜์˜€์œผ๋ฏ€๋กœ ๋งŒ์•ฝ 100๋ช…์˜ 

      ์œ ์ €๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด 100 * 100 = 10,000๋ฒˆ์ด๋ฏ€๋กœ 1์ดˆ์— 40,000๋ฒˆ ๋™์ž‘ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (250 * 4 = 1000 = 1์ดˆ)

      ์ด์— lock ๋ถ€๋ถ„์— ๋™์‹œ๋‹ค๋ฐœ์ ์œผ๋กœ ์ˆ˜๋งŽ์€ Thread๋“ค์ด ๋“ค์–ด์˜ค์ง€๋งŒ lock ๋•Œ๋ฌธ์— 1๋ฒˆ์— 1๊ฐœ์˜ Thread๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 ~> ๋”ฐ๋ผ์„œ ์œ„์˜ ์ˆ˜๋งŽ์€ ์ž‘์—…๋“ค์ด ๋ฐ€๋ฆฌ๊ฒŒ ๋˜๋ฉด์„œ Thread๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ž…์žฅ์—์„œ๋Š” Thread๋ฅผ ๋ณด๋ƒˆ์œผ๋‚˜ ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ 

      ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์‹œ Thread๋ฅผ ๋ณด๋‚ด๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค. ์ด์— Thread๊ฐ€ ๊ณ„์†ํ•ด์„œ ์Œ“์ด๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

- ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š” Recv๋ฅผ ํ•˜์ž๋งˆ์ž, lock์„ ํ†ตํ•ด ํŒจํ‚ท์„ ์ „์†กํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 ~> ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ํ•˜๋‚˜์˜ Thread๋งŒ Queue์— ์Œ“์—ฌ์žˆ๋Š” ์ผ๊ฐ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๋‹ค๋ฅธ Thread๋“ค์˜ ์ผ๊ฐ์€ Queue์— ๋‹ด์•„๋‘๋Š”

      ๊ฒƒ์ด๋‹ค. (์ด๋ฅผ Job ๋˜๋Š” Task๋ผ๊ณ  ํ•˜๋ฉฐ, ์ค‘์š”ํ•œ ๊ฒƒ์€ ํŒจํ‚ท์„ ์ €์žฅํ•˜๊ณ  ํ•˜๋‚˜์˜ Thread์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ)

 

# Command ํŒจํ„ด

- ๋‹ค์Œ ์‹œ๊ฐ„์— ๋งŒ๋“ค์–ด๋ณผ Job ๋˜๋Š” Task๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” Queue๊ฐ€ ์ „ํ˜•์ ์ธ Command ํŒจํ„ด์˜ ์˜ˆ์ œ์ด๋‹ค.

 

- ์†๋‹˜์„ ๋Œ€๋ฆฌํ•˜๋Š” ClientSession์ด ์ง์›์ธ Thread์—๊ฒŒ ์ฃผ๋ฌธ์„ ํ•œ๋‹ค.

 

 

- ๋งŒ์•ฝ ์ง์›์ด ์„œ๋น™, ์š”๋ฆฌ, ๊ณ„์‚ฐ์„ ์ „๋ถ€ ๋„๋งก์•„ ํ•  ๊ฒฝ์šฐ ์ฃผ๋ฌธ์„ ๋ฐ›์ž๋งˆ์ž ์ฃผ๋ฐฉ์— ๋‹ฌ๋ ค๊ฐ€ ์š”๋ฆฌ๋ฅผ ๋ฐ”๋กœ ์‹œ์ž‘ํ•  ๊ฒƒ์ด๋‹ค.

 ~> ์ง€๊ธˆ๊นŒ์ง€ ๊ตฌํ˜„๋œ ์ฝ”๋“œ๊ฐ€ ์œ„์™€ ๋น„์Šทํ•˜๋‹ค.

 

 

- ๋งŒ์•ฝ ์ฃผ๋ฐฉ์˜ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์•„ ๋™์‹œ์— 1๋ช…๋งŒ ์š”๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ์ฃผ๋ฌธ์„ ๋ฐ›์€ ์ง์›๋“ค์€ ์ฃผ๋ฐฉ ์•ž์—์„œ ์ž์‹ ์˜ ์š”๋ฆฌ ์ฐจ๋ก€๊ฐ€

  ์˜ฌ๋•Œ๊นŒ์ง€ ๊ณ„์†ํ•ด์„œ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋œ๋‹ค.

 

 

- ๋ชจ๋“  ์ง์›๋“ค์ด ์ž์‹ ์˜ ์š”๋ฆฌ ์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ๋ฌธ์„ ๋ฐ›์„ ์ง์›์ด ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ ์‹๋‹น์€ ์ง์›์„ ๋” ๊ณ ์šฉํ•œ๋‹ค.

 ~> ์ง€๊ธˆ๊นŒ์ง€ ๊ตฌํ˜„๋œ ์ฝ”๋“œ์˜ ๊ฒฐ๊ณผ๊ฐ€ ์œ„์™€ ๋น„์Šทํ•˜๋‹ค.

 

 

- ์œ„์™€ ๊ฐ™์ด 1๋ช…์˜ ์ง์›์ด ์„œ๋น™, ์š”๋ฆฌ, ๊ณ„์‚ฐ์„ ๋ชจ๋‘ ๋‹ด๋‹นํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ง์›๋“ค ๊ฐ๊ฐ์ด ์—…๋ฌด๋ฅผ ๋ถ„๋‹ด๋ฐ›๋„๋ก ํ•œ๋‹ค.

 ~> ์„œ๋น™์„ ๋‹ด๋‹นํ•˜๋Š” ์ง์›์ด ์ฃผ๋ฌธ์„ ๋ฐ›์•„ ์ฃผ๋ฌธ์„œ๋ฅผ ์ฃผ๋ฐฉ์žฅ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.

 ~> ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์ด command ํŒจํ„ด๊ณผ ์œ ์‚ฌํ•˜๋‹ค.

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://velog.io/@kyeun95/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%BB%A4%EB%A7%A8%EB%93%9C-%ED%8C%A8%ED%84%B4Command-Pattern)

- command ํŒจํ„ด์ด๋ž€?

 ~> command ํŒจํ„ด์€ ๊ฐ์ฒด ์ง€ํ–ฅ ๋””์ž์ธ ํŒจํ„ด ์ค‘ ํ•˜๋‚˜๋กœ, ๊ฐ์ฒด ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ  ์œ ์—ฐ์„ฑ์„ ๋†’์ด๋Š” ํŒจํ„ด์ด๋‹ค.

 ~> command ํŒจํ„ด์˜ ์ฃผ์š” ๋ชฉ์ ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋‚ธ ์š”์ฒญ์„ ๊ฐ์ฒด์˜ ํ˜•ํƒœ๋กœ ์บก์Аํ™”ํ•˜์—ฌ ์ด๋ฅผ ๋‚˜์ค‘์— ์ด์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก

     ์ด๋ฆ„, ๋งค๊ฐœ๋ณ€์ˆ˜ ๋“ฑ ์š”์ฒญ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅ ๋˜๋Š” ๋กœ๊น…, ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํŒจํ„ด์œผ๋กœ, ์ด๋ฅผ ํ†ตํ•ด ๋ฉ”์†Œ๋“œ๋ฅผ

     ํ˜ธ์ถœํ•˜๋Š” Class์™€ ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” Class ์‚ฌ์ด์˜ ๊ฒฐํ•ฉ์„ ๋А์Šจํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.

 ~> ๋”ฐ๋ผ์„œ Client๊ฐ€ ์š”์ฒญ์˜ ์ˆ˜์‹ ์ž๋ฅผ ์•Œ ํ•„์š” ์—†์ด ๋‹ค์–‘ํ•œ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://bamtory29.tistory.com/entry/%EC%BB%A4%EB%A7%A8%EB%93%9C-%ED%8C%A8%ED%84%B4-Command-Pattern)

Command ์ธํ„ฐํŽ˜์ด์Šค
public interface Command 
{
    public void execute();
}โ€‹

 

ComputerOnCommand Class
public class ComputerOnCommand implements Command
{
    private Computer computer;

    public ComputerOnCommand(Computer computer) 
    {
        this.computer = computer;
    }
    
    @Override
    public void execute() 
    {
        computer.turnOn();
    }
}โ€‹

 

ComputerOffCommand Class
public class ComputerOffCommand implements Command 
{
    private Computer computer;

    public ComputerOffCommand(Computer computer) 
    {
        this.computer = computer;
    }
    
    @Override
    public void execute() 
    {
        computer.turnOff();
    }
}โ€‹

 

Computer Class
public class Computer 
{
    public void Computer() {}

    public void turnOn() 
    {
        System.out.println("์ปดํ“จํ„ฐ ์ „์› ์ผœ์ง");
    }

    public void turnOff() 
    {
        System.out.println("์ปดํ“จํ„ฐ ์ „์› ๊บผ์ง");
    }
}โ€‹

 

Button Class
public class Button 
{
    private Command command;

    public Button(Command command) 
    {
        this.command = command;
    }

    public void setCommand(Command command) 
    {
        this.command = command;
    }

    public void pressButton() 
    {
        this.command.execute();
    }
}โ€‹

 

Main ๋ฉ”์†Œ๋“œ
public static void main(String[] args) 
{
        Computer computer = new Computer(); //์ปดํ“จํ„ฐ๋Š” Receiver

	    //์ปดํ“จํ„ฐ ๊ฐ์ฒด ์ƒ์„ฑ
        ComputerOnCommand computerOnCmd = new ComputerOnCommand(computer);
        ComputerOffCommand computerOffCmd = new ComputerOffCommand(computer);

        Button btn = new Button(computerOnCmd); //๋ฒ„ํŠผ์ด Invoker ์—ญํ• 
        btn.pressButton();
        btn.setCommand(computerOffCmd);
        btn.pressButton();
}

 

# JobQueue #1

- ์ง€๋‚œ ์‹œ๊ฐ„ ํ•™์Šตํ•œ Comman ํŒจํ„ด์„ ํ™œ์šฉํ•˜์—ฌ JobQueue๋ฅผ ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ด๋‹ค.

 ~> Queue์— ์Œ“์ธ ์ผ๊ฐ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Thread๋Š” Push์‹œ _jobQueue์— ์ฒ˜์Œ์œผ๋กœ ์ผ๊ฐ์„ ๋ฐ€์–ด ๋„ฃ๋Š” Thread๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค.

ServerCore์— JobQueue Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ServerCore
{
    public interface IJobQueue
    {
        void Push(Action job);
    }

    public class JobQueue : IJobQueue // IJobQueue๋ฅผ ์ƒ์† ๋ฐ›๋Š”๋‹ค.
    {
        Queue<Action> _jobQueue = new Queue<Action>(); // ์ผ๊ฐ ๋ชฉ๋ก์„ ๋‹ด๋Š” ๊ณณ
        object _lock = new object(); // MultiThread ํ™˜๊ฒฝ์„ ์œ„ํ•œ lock ์„ ์–ธ
        bool _flush = false; // Queue์— ์Œ“์ธ ์ผ๊ฐ๋“ค์„ ๋ณธ์ธ์ด ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ์ง€ ์•„๋‹Œ์ง€

        public void Push(Action job)  // _jobQueue์— ์ผ๊ฐ์„ ๋ฐ€์–ด ๋„ฃ๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜
        {
            bool flush = false; // MultiThread ํ™˜๊ฒฝ์—์„œ ๋‹จ 1๊ฐœ์˜ Thread๋งŒ ์ผ๊ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋„๋ก
            lock (_lock)
            {
                _jobQueue.Enqueue(job);
                if (_flush == false) // _flush๊ฐ€ false์ธ ๊ฒฝ์šฐ Queue์— ์Œ“์ธ ์ผ๊ฐ๋“ค์„ ๋ณธ์ธ์ด ์ฒ˜๋ฆฌ
                    flush = _flush = true;
            }

            if (flush)
                Flush();
        }

        void Flush()
        {
            while (true)
            {
                Action action = Pop();
                if (action == null)
                    return;

                action.Invoke();
            }
        }

        Action Pop() // ์ผ๊ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด _jobQueue์—์„œ ์ผ๊ฐ์„ ๊บผ๋‚ด๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜
        {
            lock (_lock)
            {
                if (_jobQueue.Count == 0)
                {
                    _flush = false;
                    return null;
                }

                return _jobQueue.Dequeue();
            }
        }
    }
}โ€‹

 

GameRoom Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class GameRoom : IJobQueue // IJobQueue๋ฅผ ์ƒ์† ๋ฐ›๋Š”๋‹ค.
    {
        // ...
        
        JobQueue _jobQueue = new JobQueue();

        public void Push(Action job)
        {
            _jobQueue.Push(job);
        }

        // ...
    }
}โ€‹

 

ClientSession Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    class ClientSession : PacketSession 
    {
        // ...

        public override void OnConnected(EndPoint endPoint)
        {
            // ...

            // ๋ฐ”๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ผ๊ฐ์„ _jobQueue์— ๋‹ด๋Š” ๊ฒƒ
            Program.Room.Push(() => Program.Room.Enter(this));
            // Program.Room.Enter(this); // Client๊ฐ€ ์ ‘์†์‹œ ๋ฐฉ์— ์ž…์žฅ์‹œํ‚จ๋‹ค. (Program ์‚ฐํ•˜์— static์œผ๋กœ Room์„ ์ƒ์„ฑํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜ธ์ถœ)

            // ...
        }

        // ...

        public override void OnDisconnected(EndPoint endPoint)
        {
            // ...
            
            if (Room != null)
            {
                // ์‹คํ–‰ ๋„์ค‘ Client ์ข…๋ฃŒ์‹œ Null Crash ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๊ฒƒ & ๋ฐ”๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ผ๊ฐ์„ _jobQueue์— ๋‹ด๋Š” ๊ฒƒ
                GameRoom room = Room;
                room.Push(() => room.Leave(this));
                //Room.Leave(this);
                Room = null;
            }

            // ...
        }

        // ...
    }
}โ€‹

 

PacketHandler Class ์ˆ˜์ •
using Server;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

class PacketHandler 
{
    public static void C_ChatHandler(PacketSession session, IPacket packet)
    {
        // ...

        // ์‹คํ–‰ ๋„์ค‘ Client ์ข…๋ฃŒ์‹œ Null Crash ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๊ฒƒ & ๋ฐ”๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ผ๊ฐ์„ _jobQueue์— ๋‹ด๋Š” ๊ฒƒ
        GameRoom room = clientSession.Room;
        room.Push(() => room.Broadcast(clientSession, chatPacket.chat));
        // clientSession.Room.Broadcast(clientSession, chatPacket.chat); // ํ˜„์žฌ clientSession์ด ์ ‘์†์ค‘์ธ ๋ฐฉ์— ์กด์žฌํ•˜๋Š” ๋ชจ๋‘์—๊ฒŒ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋ฅผ ๋ฟŒ๋ฆฐ๋‹ค.
    }
}โ€‹

 

# JobQueue #2

- ์ง€๋‚œ ์‹œ๊ฐ„์— ๊ตฌํ˜„ํ•œ JobQueue์™€ ๋˜‘๊ฐ™์ด ๋™์ž‘ํ•˜์ง€๋งŒ ์ˆ˜๋™์ ์œผ๋กœ Task๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ์˜ˆ์ •์ด๋‹ค.

 ~> ๋žŒ๋‹ค์‹ ๊ฐœ๋…์ด ๋“ฑ์žฅํ•œ์ง€ ์–ผ๋งˆ ๋˜์ง€ ์•Š์•„ ํ˜„์—…๊ณผ ๊ฐ™์€ ์‹ค๋ฌด์—์„œ๋Š” ์ˆ˜๋™์ ์œผ๋กœ ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํ•จ์ˆ˜๋“ค์„ ๊ตฌํ˜„ํ•ด์„œ

      ์ฒ˜๋ฆฌํ•˜๋Š” ํ˜•์‹์ด ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค.

 ~> ๊ทธ๋Ÿฌ๋‚˜ ์œ„์™€ ๊ฐ™์ด ์ˆ˜๋™์ ์ธ ๋ฐฉ๋ฒ•์€ ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํ•จ์ˆ˜๋ฅผ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.

Server์— TaskQueue Class ์ƒ์„ฑ
(_queue์— ์Œ“์ธ ์ผ๊ฐ ์ฒ˜๋ฆฌ๋Š” ์ง€๋‚œ ์‹œ๊ฐ„์˜ Flush() ์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Server
{
    interface ITask
    {
        void Execute();
    }

    // ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํ•จ์ˆ˜์— ๋งž์ถฐ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ
    class BroadcastTask : ITask
    {
        // ํ•จ์ˆ˜ ์‹คํ–‰์‹œ ํ•„์š”ํ•œ ๋ณ€์ˆ˜๋“ค์„ ์„ ์–ธ
        GameRoom _room;
        ClientSession _session;
        string _chat;

        // ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”
        BroadcastTask(GameRoom room, ClientSession session, string chat)
        {
            _room = room;
            _session = session;
            _chat = chat;
        }

        public void Execute()
        {
            _room.Broadcast(_session, _chat);
        }
    }

    class TaskQueue
    {
        Queue<ITask> _queue = new Queue<ITask>();
    }
}โ€‹

 

๋ถ€ํ•˜ Test๋ฅผ ์œ„ํ•œ Listener Class ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace ServerCore
{
    public class Listener
    {
        Socket _listenSocket;

        Func<Session> _sessionFactory;

        // ๋ฌธ์ง€๊ธฐ ์ˆ˜๋ฅผ 10๋ช…์œผ๋กœ ์ฆ๊ฐ€, ์ตœ๋Œ€ ๋Œ€๊ธฐ์ˆ˜๋ฅผ 100๋ช…์œผ๋กœ ์ฆ์›
        public void Init(IPEndPoint endPoint, Func<Session> sessionFactory, int register = 10, int backlog = 100)
        {
            // ๋ฌธ์ง€๊ธฐ ๊ณ ์šฉ
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            _sessionFactory += sessionFactory;

            // ๋ฌธ์ง€๊ธฐ ๊ต์œก
            _listenSocket.Bind(endPoint);

            // ์˜์—… ์‹œ์ž‘
            // backlog : ์ตœ๋Œ€ ๋Œ€๊ธฐ ์ˆ˜
            _listenSocket.Listen(backlog);

            for (int i = 0; i < register; i++)
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
                RegisterAccept(args);
            }
        }

        // ...
    }
}โ€‹

 

- ๋งŒ์•ฝ Client ์ ‘์† ์ธ์›์„ 500๋ช…์œผ๋กœ ๋Š˜๋ฆฐ ๋’ค์— ์‹คํ–‰ํ•  ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ณ„์†ํ•ด์„œ ์ƒ์Šนํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 ~> ์ด๋Š” ์ˆ˜๋งŽ์€ ์ž‘์—…๋“ค์ด ๋ฐ€๋ฆฌ๊ฒŒ ๋˜๋ฉด์„œ Thread๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ž…์žฅ์—์„œ๋Š” Thread๋ฅผ ๋ณด๋ƒˆ์œผ๋‚˜ ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜๊ธฐ

      ๋•Œ๋ฌธ์— ThreadPool์—์„œ ์ƒˆ๋กœ์šด Thread๋ฅผ ๋ฝ‘์•„ ๋ณด๋‚ด๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ณ„์†ํ•ด์„œ ์ƒ์Šนํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

- ๊ฐ€์žฅ ์‹ฌํ•œ ๋ถ€ํ•˜๋ฅผ ์œ ๋ฐœํ•˜๋Š” ๊ณณ์€ Broadcast์˜ foreach๋ฌธ ๋‚ด์˜ Send์ด๋‹ค.

 ~> ์™œ๋ƒํ•˜๋ฉด Thread.Sleep(250) ์„ ํ†ตํ•ด 0.25์ดˆ์— 1๋ฒˆ ๋™์ž‘ํ•˜๋„๋ก ์„ค์ •ํ•˜์˜€์œผ๋ฏ€๋กœ 500๋ช…์˜ ์œ ์ €๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด

      500 * 500 = 250,000๋ฒˆ์ด๋ฏ€๋กœ 1์ดˆ์— 1,000,000๋ฒˆ ๋™์ž‘ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. 

 ~> ์ด๋Š” N^2์˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋ฅผ ๊ฐ€์ง„๋‹ค.

 ~> ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ํŒจํ‚ท ์š”์ฒญ์ด ์˜ฌ๋•Œ๋งˆ๋‹ค ๋ฐ”๋กœ Send ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ํŒจํ‚ท์„ ๋ชจ์€ ๋’ค ์ถ”ํ›„์— ํ•œ๋ฒˆ์— ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://shung2.tistory.com/1445)

- Zone ํ˜•์‹์˜ ๊ฒŒ์ž„์€ ๋งต์„ ๋„˜์–ด๊ฐˆ ๋•Œ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ์žˆ๊ธฐ๋•Œ๋ฌธ์— ํ•˜๋‚˜์˜ Zone ๋‹จ์œ„์— JobQueue๋ฅผ ๋„ฃ์–ด ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋งŒ๋“ค๋ฉด

 ๋˜๋ฏ€๋กœ ๋น„๊ต์  JobQueue ํ™œ์šฉ ์ฒ˜๋ฆฌ๊ฐ€ ์šฉ์ดํ•˜๋‹ค.

- ๊ทธ๋Ÿฌ๋‚˜ Seamless ๊ฒŒ์ž„์€ ๋„“์€ ๋งต์—์„œ ์ž„์˜๋กœ ์˜์—ญ์„ ๊ตฌ๋ถ„ํ•˜์—ฌ JobQueue๋ฅผ ๋‘๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜๋Š” ์žˆ๊ฒ ์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ๋œ๋‹ค๋ฉด

  ๊ตฌ๋ถ„๋œ ์˜์—ญ ์‚ฌ์ด์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ง€์— ๋Œ€ํ•œ ์˜๋ฌธ์ ์ด ์ƒ๊ธธ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

 ~> JobQueue๋ฅผ ๋ชจ๋“  ๊ฐ์ฒด๋“ค์—๊ฒŒ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. (์ฆ‰, User, Monster, Skill ...)

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://drehzr.tistory.com/1683)

 

# ํŒจํ‚ท ๋ชจ์•„ ๋ณด๋‚ด๊ธฐ

- ํŒจํ‚ท์„ ๋ชจ์•„ ๋ณด๋‚ด๋Š” ๊ฒƒ์€ Engine ์ชฝ์—์„œ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ  Content ์ชฝ์—์„œ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 ~> ๋งŒ์•ฝ Engine ์ชฝ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ•  ๊ฒฝ์šฐ Session์˜ Send() ๋ฉ”์†Œ๋“œ์—์„œ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

      (์–ด๋А์ •๋„ ์Œ“์•„๋‘” ๋’ค ์ผ์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ณด๋‚ด๋Š” ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝ)

 ~> ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” Content ์ชฝ์—์„œ ํŒจํ‚ท ๋ชจ์•„ ๋ณด๋‚ด๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ด๋‹ค.

 ~> ํŒจํ‚ท์ด ๋ถˆ๊ทœ์น™ํ•˜๊ฒŒ ์ „์†ก๋  ๊ฒฝ์šฐ RecvBuffer์˜ ํฌ๊ธฐ๋ฅผ ๋Š˜๋ ค์ฃผ๋ฉด ๋œ๋‹ค.

Session Class ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace ServerCore
{
    // ...

    public abstract class Session
    {
        // ...
        
        // List<ArraySegment<byte>> ๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ List์— ๋‹ด๊ธด ํŒจํ‚ท๋“ค์„ ์ „๋ถ€ ์ฒ˜๋ฆฌํ•˜๋Š” Send ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€
        public void Send(List<ArraySegment<byte>> sendBuffList)
        {
            if (sendBuffList.Count == 0)
                return;

            lock (_lock)
            {
                foreach (ArraySegment<byte> sendBuff in sendBuffList)
                    _sendQueue.Enqueue(sendBuff);

                if (_pendingList.Count == 0)
                    RegisterSend();
            }
        }
        
        // ๊ธฐ์กด์˜ Send ๋ฉ”์†Œ๋“œ
        public void Send(ArraySegment<byte> sendBuff)
        {
            lock (_lock)
            {
                _sendQueue.Enqueue(sendBuff);

                if (_pendingList.Count == 0)
                    RegisterSend();
            }
        }

        // ...
    }
}โ€‹

 

GameRoom Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class GameRoom : IJobQueue 
    {
        // ...
        
        List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>(); // ํŒจํ‚ท์„ ๋ชจ์œผ๊ธฐ ์œ„ํ•œ List

        // ...

        public void Flush() // ๋ชจ์•„๋‘” ํŒจํ‚ท์„ ์ฒ˜๋ฆฌ
        {
            foreach (ClientSession s in _sessions)
                s.Send(_pendingList);

            Console.WriteLine($"Flushed {_pendingList.Count} items");
            _pendingList.Clear();
        }

        // ..
    }
}โ€‹

 

Server ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        // ...

        static void Main(string[] args)
        {
            // ...

            while (true)
            {
                Room.Push(() => Room.Flush());
                Thread.Sleep(250);
            }
        }
    }
}โ€‹

 

# JobTimer

- GameRoom ๋ฟ๋งŒ์ด ์•„๋‹Œ ๋‹ค์–‘ํ•œ Room๋“ค์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ ๊ฐ๊ฐ์˜ ๋ฃธ๋“ค์€ ์„œ๋กœ ๋‹ค๋ฅธ ๋Œ€๊ธฐ์‹œ๊ฐ„์„ ๊ฐ€์ง€๋ฉฐ ์‹คํ–‰๋œ๋‹ค.

 ~> ์ฒซ๋ฒˆ์งธ๋กœ๋Š” Tick์„ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

 ~> ๋‘๋ฒˆ์งธ๋กœ๋Š” PriorityQueue๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

Tick์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ Server ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        // ...

        static void Main(string[] args)
        {
            // ...

            int roomTick = 0;
            // room์ด ์ถ”๊ฐ€๋ ๋•Œ๋งˆ๋‹ค Tick ๋ณ€์ˆ˜๋„ ์ถ”๊ฐ€๋œ๋‹ค.
            while (true)
            {
                int now = System.Environment.TickCount;
                if (roomTick < now) // ๊ทธ๋Ÿฌ๋‚˜ ์ด์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์€ ๋ถˆํ•„์š”ํ•œ if๋ฌธ ์ฒดํฌ๊ฐ€ ๋ฐ˜๋ณต๋œ๋‹ค.
                {
                    Room.Push(() => Room.Flush());
                    roomTick = now + 250;
                }
                // room์ด ์ถ”๊ฐ€๋ ๋•Œ๋งˆ๋‹ค if๋ฌธ๋„ ์ถ”๊ฐ€๋œ๋‹ค.
            }
        }
    }
}โ€‹

 

ServerCore์— PriorityQueue Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Text;

namespace ServerCore
{
    public class PriorityQueue<T> where T : IComparable<T>
    {
        List<T> _heap = new List<T>();

        public int Count { get { return _heap.Count; } }

        // 0 (logN)
        public void Push(T data)
        {
            // ํž™์˜ ๋งจ ๋์— ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•œ๋‹ค.
            _heap.Add(data);

            int now = _heap.Count - 1;
            // ๋„์žฅ๊นจ๊ธฐ๋ฅผ ์‹œ์ž‘
            while (now > 0)
            {
                // ๋„์žฅ๊นจ๊ธฐ๋ฅผ ์‹œ๋„
                int next = (now - 1) / 2;
                if (_heap[now].CompareTo(_heap[next]) < 0) // ๋Œ€์ƒ๊ฐ’์ด ๋น„๊ต๊ฐ’๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ 0, ์ž‘์€ ๊ฒฝ์šฐ -1, ํฐ ๊ฒฝ์šฐ 1 ( ๋Œ€์ƒ๊ฐ’.CompareTo(๋น„๊ต๊ฐ’) )
                    break; // ์‹คํŒจ

                // ๋‘ ๊ฐ’์„ ๊ต์ฒดํ•œ๋‹ค.
                T temp = _heap[now];
                _heap[now] = _heap[next];
                _heap[next] = temp;

                // ๊ฒ€์‚ฌ ์œ„์น˜๋ฅผ ์ด๋™ํ•œ๋‹ค.
                now = next;
            }
        }

        // 0 (logN)
        public T Pop()
        {
            // ๋ฐ˜ํ™˜ํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๋”ฐ๋กœ ์ €์žฅ
            T ret = _heap[0];

            // ๋งˆ์ง€๋ง‰ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฃจํŠธ๋กœ ์ด๋™ํ•œ๋‹ค.
            int lastIndex = _heap.Count - 1;
            _heap[0] = _heap[lastIndex];
            _heap.RemoveAt(lastIndex);
            lastIndex--;

            // ์—ญ์œผ๋กœ ๋‚ด๋ ค๊ฐ€๋Š” ๋„์žฅ๊นจ๊ธฐ ์‹œ์ž‘
            int now = 0;
            while (true)
            {
                int left = 2 * now + 1;
                int right = 2 * now + 2;

                int next = now;
                // ์™ผ์ชฝ๊ฐ’์ด ํ˜„์žฌ๊ฐ’๋ณด๋‹ค ํฌ๋ฉด, ์™ผ์ชฝ์œผ๋กœ ์ด๋™
                if (left <= lastIndex && _heap[next].CompareTo(_heap[left]) < 0)
                    next = left;
                // ์˜ค๋ฅธ์ชฝ๊ฐ’์ด ํ˜„์žฌ๊ฐ’(์™ผ์ชฝ ์ด๋™ ํฌํ•จ)๋ณด๋‹ค ํฌ๋ฉด, ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์ด๋™
                if (right <= lastIndex && _heap[next].CompareTo(_heap[right]) < 0)
                    next = right;

                // ์™ผ์ชฝ/์˜ค๋ฅธ์ชฝ ๋ชจ๋‘ ํ˜„์žฌ๊ฐ’๋ณด๋‹ค ์ž‘์œผ๋ฉด ์ข…๋ฃŒ
                if (next == now)
                    break;

                // ๋‘ ๊ฐ’์„ ๊ต์ฒดํ•œ๋‹ค.
                T temp = _heap[now];
                _heap[now] = _heap[next];
                _heap[next] = temp;
                // ๊ฒ€์‚ฌ ์œ„์น˜๋ฅผ ์ด๋™ํ•œ๋‹ค.
                now = next;
            }

            return ret;
        }

        public T Peek() // ์ผ๊ฐ ๋‚ด์šฉ ํ™•์ธ์„ ์œ„ํ•œ ํ•จ์ˆ˜
        {
            if (_heap.Count == 0)
                return default(T);
            return _heap[0];
        }
    }
}โ€‹

 

Server์— JobTimer Class ์ƒ์„ฑ
using System;
using System.Collections.Generic;
using System.Text;
using ServerCore;

namespace Server
{
    struct JobTimerElem : IComparable<JobTimerElem> // ํ•˜๋‚˜์˜ ์ผ๊ฐ ๋‹จ์œ„
    {
        public int execTick; // ์‹คํ–‰ ์‹œ๊ฐ„
        public Action action; // ์ผ๊ฐ (์ฆ‰, Job)

        // IComparable ์ธํ„ฐํŽ˜์ด์Šค์˜ ํ•„์ˆ˜ ๊ตฌ์„ฑ ์š”์†Œ์ธ CompareTo()
        public int CompareTo(JobTimerElem other)
        {
            // ์‹คํ–‰ ์‹œ๊ฐ„์ด ์ ์„์ˆ˜๋ก ๋จผ์ € ํŠ€์–ด๋‚˜์˜ค๋„๋ก (์ฆ‰, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ํ•ญ๋ชฉ)
            return other.execTick - execTick;
        }
    }

    class JobTimer // Job์„ ์˜ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ์Šคํ…œ
    {
        // ์šฐ์„ ์ˆœ์œ„ ํ๋Š” ๋Œ€์†Œ๊ด€๊ณ„ ์—ฐ์‚ฐ ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ์ƒ๋‹นํžˆ ๋น ๋ฅด๋‹ค.
        PriorityQueue<JobTimerElem> _pq = new PriorityQueue<JobTimerElem>();
        object _lock = new object(); // MultiThread ํ™˜๊ฒฝ์„ ์œ„ํ•œ lock ์„ ์–ธ

        public static JobTimer Instance { get; } = new JobTimer();

        public void Push(Action action, int tickAfter = 0) // ์˜ˆ์•ฝํ•˜๊ณ ์ž ํ•˜๋Š” ์ผ๊ฐ, ๋ช‡ Tick ํ›„์— ์‹คํ–‰ํ•ด์•ผํ•˜๋Š”์ง€ (์ž…๋ ฅ ๋ฐ›์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ ๋ฐ”๋กœ ์‹คํ–‰ํ•˜๋„๋ก 0์œผ๋กœ ์„ค์ •)
        {
            JobTimerElem job;
            job.execTick = System.Environment.TickCount + tickAfter;
            job.action = action;

            lock (_lock)
            {
                _pq.Push(job);
            }
        }

        public void Flush() // ์ผ๊ฐ ์ฒ˜๋ฆฌ
        {
            while (true)
            {
                int now = System.Environment.TickCount;

                JobTimerElem job;

                lock (_lock)
                {
                    if (_pq.Count == 0) // ์ผ๊ฐ์ด ์—†๋Š” ๊ฒฝ์šฐ
                        break; // while๋ฌธ์„ ๋‚˜๊ฐ„๋‹ค๋Š” ์˜๋ฏธ

                    job = _pq.Peek(); // ๊บผ๋‚ด์ง€ ์•Š๊ณ  ์ผ๊ฐ ๋‚ด์šฉ๋งŒ ํ™•์ธํ•˜๋Š” ๊ฒƒ
                    if (job.execTick > now) // ํ˜„์žฌ ์‹œ๊ฐ„๋ณด๋‹ค ์‹คํ–‰ ์‹œ๊ฐ„์ด ๋งŽ์ด ๋‚จ์€ ๊ฒฝ์šฐ
                        break;

                    _pq.Pop();
                }

                // ์ผ๊ฐ์„ ์‹คํ–‰์‹œํ‚จ๋‹ค.
                job.action.Invoke();
            }
        }
    }
}โ€‹

 

Server ์ฝ”๋“œ ์ˆ˜์ •
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;

namespace Server
{
    class Program
    {
        // ...
        
        static void FlushRoom()
        {
            Room.Push(() => Room.Flush());
            JobTimer.Instance.Push(FlushRoom, 250);
        }

        static void Main(string[] args)
        {
            // ...

            JobTimer.Instance.Push(FlushRoom);

            while (true)
            {
                JobTimer.Instance.Flush();
            }
        }
    }
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://velog.io/@aenyoung/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90%EC%99%80-%ED%9E%99)

- Priority Queue๋ž€? (์ฆ‰, ์šฐ์„ ์ˆœ์œ„ ํ๋ž€?) 

 ~> ์„ ์ž…์„ ์ถœ(FIFO)์˜ ์›์น™์— ์˜ํ•˜์—ฌ ๋จผ์ € ๋“ค์–ด์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ๋จผ์ € ๋‚˜๊ฐ€๋Š” Queue์™€ ๋‹ฌ๋ฆฌ Priority Queue๋Š” ๋ฐ์ดํ„ฐ๋“ค์ด

      ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋จผ์ € ์ถœ๋ ฅ๋˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ์ด๋‹ค.

 

[ ์„น์…˜ 5. ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ ]

# ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ #1

- ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ ์ „ ์ข‹์€ ์†Œ์‹๊ณผ ๋‚˜์œ ์†Œ์‹์ด ์กด์žฌํ•œ๋‹ค.

 ~> ์ข‹์€ ์†Œ์‹์€ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ์œ„ํ•ด ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋“ค์„ ์–ด๋А์ •๋„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 ~> ๋‚˜์œ ์†Œ์‹์€ Unity์˜ ์ •์ฑ…์ƒ C#์—์„œ ํ—ˆ์šฉ๋˜๋Š” ๋ฌธ๋ฒ•๊ณผ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฌธ๋ฒ•๋“ค์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— GenPacket์˜ 

      Span, ReadOnlySpan, TryWriteBytes ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

      (Unity 2021 ๋ฒ„์ „ ์ดํ›„๋ถ€ํ„ฐ๋Š” Engine์—์„œ ์ง€์›ํ•˜์ง€๋งŒ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์— ๊ฐ•์˜๋ฅผ ๋“ค์„ ์˜ˆ์ •)

- ๋˜ํ•œ Unity์—์„œ MultiThread ํ™˜๊ฒฝ์€ ์ œ์•ฝ์‚ฌํ•ญ์ด ์กด์žฌํ•œ๋‹ค.

 ~> Main Thread๊ฐ€ ์•„๋‹Œ Background Thread์—์„œ Unity๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๋“ค์„ ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜๋Š” ๊ฒฝ์šฐ

      Crash๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 ~> ๋”ฐ๋ผ์„œ Game Logic์€ Main Thread ์—์„œ๋งŒ ์ ‘๊ทผ ๋ฐ ์‹คํ–‰ํ•˜๋„๋ก ์กฐ์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

- ์šฐ์„  Unity Hub๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด Client ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค Scripts ํด๋” ์‚ฐํ•˜์— ์žฌ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ๋“ค์„ ๋ณต๋ถ™ํ•œ๋‹ค.

 ~> ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ๋กœ๋Š” ์—ฌํƒœ๊นŒ์ง€ ์‹ค์Šตํ•˜๋˜ ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ๋กœ๋กœ ์„ค์ •

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // ...

        // {0} ํŒจํ‚ท ์ด๋ฆ„
        // {1} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค
        // {2} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read
        // {3} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write
        public static string packetFormat =
@"
public class {0} : IPacket
{{
	{1}

	public ushort Protocol {{ get {{ return (ushort)PacketID.{0}; }} }}

	public void Read(ArraySegment<byte> segment)
	{{
		ushort count = 0;
		count += sizeof(ushort);
		count += sizeof(ushort);
		{2}
	}}

	public ArraySegment<byte> Write()
	{{
		ArraySegment<byte> segment = SendBufferHelper.Open(4096);
		ushort count = 0;

		count += sizeof(ushort);
		Array.Copy(BitConverter.GetBytes((ushort)PacketID.{0}), 0, segment.Array, segment.Offset + count, sizeof(ushort));
		count += sizeof(ushort);
		{3}

		Array.Copy(BitConverter.GetBytes(count), 0, segment.Array, segment.Offset, sizeof(ushort));

		return SendBufferHelper.Close(count);
	}}
}}
";
        // ...

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        // {2} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค
        // {3} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Read
        // {4} ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ Write
        public static string memberListFormat =
@"public class {0}
{{
	{2}

	public void Read(ArraySegment<byte> segment, ref ushort count)
	{{
		{3}
	}}

	public bool Write(ArraySegment<byte> segment, ref ushort count)
	{{
		bool success = true;
		{4}
		return success;
	}}	
}}
public List<{0}> {1}s = new List<{0}>();";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} To~ ๋ณ€์ˆ˜ ํ˜•์‹
        // {2} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string readFormat =
@"this.{0} = BitConverter.{1}(segment.Array, segment.Offset + count);
count += sizeof({2});";

        // ...

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        public static string readStringFormat =
@"ushort {0}Len = BitConverter.ToUInt16(segment.Array, segment.Offset + count);
count += sizeof(ushort);
this.{0} = Encoding.Unicode.GetString(segment.Array, segment.Offset + count, {0}Len);
count += {0}Len;";

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        public static string readListFormat =
@"this.{1}s.Clear();
ushort {1}Len = BitConverter.ToUInt16(segment.Array, segment.Offset + count);
count += sizeof(ushort);
for (int i = 0; i < {1}Len; i++)
{{
	{0} {1} = new {0}();
	{1}.Read(segment, ref count);
	{1}s.Add({1});
}}";

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        // {1} ๋ณ€์ˆ˜ ํ˜•์‹
        public static string writeFormat =
@"Array.Copy(BitConverter.GetBytes(this.{0}), 0, segment.Array, segment.Offset + count, sizeof({1}));
count += sizeof({1});";

        // ...

        // {0} ๋ณ€์ˆ˜ ์ด๋ฆ„
        public static string writeStringFormat =
@"ushort {0}Len = (ushort)Encoding.Unicode.GetBytes(this.{0}, 0, this.{0}.Length, segment.Array, segment.Offset + count + sizeof(ushort));
Array.Copy(BitConverter.GetBytes({0}Len), 0, segment.Array, segment.Offset + count, sizeof(ushort));
count += sizeof(ushort);
count += {0}Len;";

        // {0} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [๋Œ€๋ฌธ์ž]
        // {1} ๋ฆฌ์ŠคํŠธ ์ด๋ฆ„ [์†Œ๋ฌธ์ž]
        public static string writeListFormat =
@"Array.Copy(BitConverter.GetBytes((ushort)this.{1}s.Count), 0, segment.Array, segment.Offset + count, sizeof(ushort));
count += sizeof(ushort);
foreach ({0} {1} in this.{1}s)
	{1}.Write(segment, ref count);";

    }
}โ€‹

 

GenPackets.bat ๋ฐฐ์น˜ ํŒŒ์ผ ์ˆ˜์ • (๋ฐฐ์น˜ ํŒŒ์ผ ์‹คํ–‰์‹œ Unity Script ๋˜ํ•œ ๊ฐ™์ด ๋ณ€๊ฒฝ๋˜๋„๋ก)
START ../../PacketGenerator/bin/PacketGenerator.exe ../../PacketGenerator/PDL.xml
XCOPY /Y GenPackets.cs "../../DummyClient/Packet"
XCOPY /Y GenPackets.cs "../../Client/Assets/Scripts/Packet"
XCOPY /Y GenPackets.cs "../../Server/Packet"
XCOPY /Y ClientPacketManager.cs "../../DummyClient/Packet"
XCOPY /Y ClientPacketManager.cs "../../Client/Assets/Scripts/Packet"
XCOPY /Y ServerPacketManager.cs "../../Server/Packet"โ€‹

 

Unity Project์— NetworkManager Script ์ƒ์„ฑํ›„ ๋นˆ ๊ฐ์ฒด์— Component๋กœ ์ถ”๊ฐ€
using DummyClient;
using ServerCore;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using UnityEngine;

public class NetworkManager : MonoBehaviour
{
    ServerSession _session = new ServerSession();

    void Start()
    {
        // DNS (Domain Name System)
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

        Connector connector = new Connector();

        connector.Connect(endPoint, () => { return _session; }, 1);
    }

    void Update()
    {
        
    }
}โ€‹

 

ํ†ต์‹ ์ด ์›ํ™œํ•˜๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด Unity Project์˜ PacketHandler Script ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

class PacketHandler
{
	public static void S_ChatHandler(PacketSession session, IPacket packet)
	{
		S_Chat chatPacket = packet as S_Chat;
		ServerSession serverSession = session as ServerSession;

        if (chatPacket.playerId == 1)
            Debug.Log(chatPacket.chat);
		//if (chatPacket.playerId == 1)
			//Console.WriteLine(chatPacket.chat);
	}
}โ€‹

 

 

- Test๋ฅผ ์œ„ํ•ด Server ์†”๋ฃจ์…˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰์‹œ์ผœ Server์™€ Client๋ฅผ ๊ตฌ๋™์‹œํ‚จ ๋’ค, Unity์˜ Play ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด

  Console ์ฐฝ์— Log๊ฐ€ ๋œจ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

# ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ #2

- ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ๋‹จ์ˆœํžˆ Log๋งŒ ๋„์šฐ๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์‹ค์งˆ์ ์ธ ์•ก์…˜์„ ์ทจํ•˜๋„๋ก ๋งŒ๋“ค ์˜ˆ์ •์ด๋‹ค.

 ~> ์šฐ์„  Unity์—์„œ [ Hierarchy ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ 3D Object ] - [ Cylinder ] ๋ฅผ ํ†ตํ•ด 3D ๊ฐ์ฒด ์ƒ์„ฑ ํ›„ ์ด๋ฆ„์„

      Player๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

Unity Project์˜ PacketHandler Script ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

class PacketHandler
{
	public static void S_ChatHandler(PacketSession session, IPacket packet)
	{
        S_Chat chatPacket = packet as S_Chat;
        ServerSession serverSession = session as ServerSession;

        if (chatPacket.playerId == 1)
        {
            Debug.Log(chatPacket.chat);

            // Unity ๋‚ด์˜ ๊ฐ์ฒด๋ฅผ ์ฐพ๋Š” Logic ์ถ”๊ฐ€
            GameObject go = GameObject.Find("Player");
            if (go == null)
                Debug.Log("Player not found");
            else
                Debug.Log("Player found");
        }
    }
}โ€‹

 

- ์œ„์˜ ์ถ”๊ฐ€ํ•œ Logic์€ ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

 ~> ๊ธฐ์กด์˜ ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ Server ์†”๋ฃจ์…˜์˜ Logic์€ ๋น„๋™๊ธฐ๋กœ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ•˜๊ณ  ์žˆ๋‹ค.

 ~> ๋”ฐ๋ผ์„œ Unity๋ฅผ ๊ตฌ๋™ํ•˜๋Š” Main Thread์—์„œ ๋„คํŠธ์›Œํฌ ํŒจํ‚ท์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, Thread Pool์—์„œ Thread๋ฅผ ๊บผ๋‚ด์™€

      ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค.

 ~> Unity๋Š” ๋‹ค๋ฅธ Thread์—์„œ ๊ฒŒ์ž„๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์„ ์ ‘๊ทผํ•˜์—ฌ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์›์ฒœ์ ์œผ๋กœ ์ฐจ๋‹จํ•ด ๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— 

      ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.

 ~> ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” PacketHandler Class๊ฐ€ Main Thread์—์„œ ์‹คํ–‰๋˜๋„๋ก ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.

      (S_ChatHandler์—์„œ Logic์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ Queue์— ์ผ๊ฐ์„ ๋“ฑ๋กํ›„, ์ฒ˜๋ฆฌํ•˜๋Š” Logic์„ ๊ตฌ๋ถ„ ์ง€์–ด ์‚ฌ์šฉ)

Unity Project์˜ GenPacket Script ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using ServerCore;

// ...

public interface IPacket // ์ ‘๊ทผํ•œ์ •์ž public ์ถ”๊ฐ€
{
	ushort Protocol { get; }
	void Read(ArraySegment<byte> segment);
	ArraySegment<byte> Write();
}

// ...โ€‹

 

Unity Project์— NetworkManager Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// Main Thread์™€ Background Thread (์ฆ‰, ๋„คํŠธ์›Œํฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์• ๋“ค๋ผ๋ฆฌ) ๋Š” PacketQueue๋ผ๋Š” ํ†ต๋กœ๋ฅผ ํ†ตํ•ด ์†Œํ†ตํ•œ๋‹ค.
//  ~> Background Thread๋Š” Pakcet์„ Pushํ•˜์—ฌ ๋ฐ€์–ด ๋„ฃ๊ณ , Main Thread์—์„œ๋Š” Packet์„ Popํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค.
public class PacketQueue // Component๋กœ ์‚ฌ์šฉํ•  ๊ฑด X ~> MonoBehaviour ์ƒ์† X
{
    public static PacketQueue Instance { get; } = new PacketQueue();

    Queue<IPacket> _packetQueue = new Queue<IPacket>();
    object _lock = new object();

    public void Push(IPacket packet)
    {
        lock (_lock)
        {
            _packetQueue.Enqueue(packet);
        }
    }

    public IPacket Pop()
    {
        lock ( _lock)
        {
            if (_packetQueue.Count == 0)
                return null;

            return _packetQueue.Dequeue();
        }
    }
}โ€‹

 

์ผ๊ฐ ๋“ฑ๋ก์„ ์œ„ํ•œ Unity Project์˜ ClientPacketManager Script ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;

class PacketManager
{
    #region Singleton
    static PacketManager _instance = new PacketManager();
    public static PacketManager Instance { get { return _instance; } }
    #endregion

    PacketManager()
    {
        Register();
    }

    Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>> _makeFunc = new Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>>();
    Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();
		
    public void Register()
    {
        _makeFunc.Add((ushort)PacketID.S_Chat, MakePacket<S_Chat>);
        _handler.Add((ushort)PacketID.S_Chat, PacketHandler.S_ChatHandler);
    }

    // Action<PacketSession, IPacket> Type์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ธ onRecvCallback ์„ ์ถ”๊ฐ€๋กœ ์ž…๋ ฅ ๋ฐ›๋Š”๋‹ค.
    public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer, Action<PacketSession, IPacket> onRecvCallback = null)
    {
        ushort count = 0;

        ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
        count += 2;
        ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;

        Func<PacketSession, ArraySegment<byte>, IPacket> func = null;
        if (_makeFunc.TryGetValue(id, out func))
        {
            IPacket packet = func.Invoke(session, buffer);
            if (onRecvCallback != null)
                onRecvCallback.Invoke(session, packet);
            else
                HandlePacket(session, packet);
        }
    }

    T MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
    {
        T pkt = new T();
        pkt.Read(buffer);
        return pkt;
    }

    public void HandlePacket(PacketSession session, IPacket packet)
    {
        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(packet.Protocol, out action))
            action.Invoke(session, packet);
    }
}โ€‹

 

์ผ๊ฐ ๋“ฑ๋ก์„ ์œ„ํ•œ Unity Project์˜ ServerSession Script ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using ServerCore;

namespace DummyClient
{
    class ServerSession : PacketSession
    {
        // ...

        // Action<PacketSession, IPacket> Type์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ธ onRecvCallback ์„ ์ถ”๊ฐ€๋กœ ์ž…๋ ฅ
        public override void OnRecvPacket(ArraySegment<byte> buffer)
        {
            PacketManager.Instance.OnRecvPacket(this, buffer, (s, p) => PacketQueue.Instance.Push(p));
        }

        // ...
    }
}โ€‹

 

์ผ๊ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Unity Project์˜ NetworkManager Script ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using UnityEngine;

public class NetworkManager : MonoBehaviour
{
    ServerSession _session = new ServerSession();

    void Start()
    {
        // DNS (Domain Name System)
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

        Connector connector = new Connector();

        connector.Connect(endPoint,
            () => { return _session; }, 1);

        StartCoroutine("CoSendPacket");
    }

    void Update()
    {
        // ์ผ๊ฐ ์ฒ˜๋ฆฌ
        IPacket packet = PacketQueue.Instance.Pop();
        if (packet != null)
        {
            PacketManager.Instance.HandlePacket(_session, packet);
        }
    }

    // 3์ดˆ๋งˆ๋‹ค ํŒจํ‚ท์„ ๋ณด๋‚ด๋„๋ก (์ฆ‰, DummyClient์˜ ์—ญํ• )
    IEnumerator CoSendPacket()
    {
        while (true)
        {
            yield return new WaitForSeconds(3.0f);

            C_Chat chatPacket = new C_Chat();
            chatPacket.chat = "Hello Unity !";
            ArraySegment<byte> segment = chatPacket.Write();

            _session.Send(segment);
        }
    }
}โ€‹

 

PacketFormat Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace PacketGenerator
{
    class PacketFormat
    {
        // {0} ํŒจํ‚ท ๋“ฑ๋ก
        public static string managerFormat =
@"using ServerCore;
using System;
using System.Collections.Generic;

public class PacketManager
{{
	#region Singleton
	static PacketManager _instance = new PacketManager();
	public static PacketManager Instance {{ get {{ return _instance; }} }}
	#endregion

	PacketManager()
	{{
		Register();
	}}

	Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>> _makeFunc = new Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>>();
	Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();
		
	public void Register()
	{{
{0}
	}}

	public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer, Action<PacketSession, IPacket> onRecvCallback = null)
	{{
		ushort count = 0;

		ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
		count += 2;
		ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
		count += 2;

		Func<PacketSession, ArraySegment<byte>, IPacket> func = null;
		if (_makeFunc.TryGetValue(id, out func))
		{{
            IPacket packet = func.Invoke(session, buffer);
			if (onRecvCallback != null)
				onRecvCallback.Invoke(session, packet);
			else
				HandlePacket(session, packet);
        }}
	}}

	T MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
	{{
		T pkt = new T();
		pkt.Read(buffer);
		return pkt;
	}}

	public void HandlePacket(PacketSession session, IPacket packet)
	{{
        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(packet.Protocol, out action))
            action.Invoke(session, packet);
    }}
}}";

        // {0} ํŒจํ‚ท ์ด๋ฆ„
        public static string managerRegisterFormat =
@"		_makeFunc.Add((ushort)PacketID.{0}, MakePacket<{0}>);
		_handler.Add((ushort)PacketID.{0}, PacketHandler.{0}Handler);";

        // {0} ํŒจํ‚ท ์ด๋ฆ„/๋ฒˆํ˜ธ ๋ชฉ๋ก
        // {1} ํŒจํ‚ท ๋ชฉ๋ก
        public static string fileFormat =
@"using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using ServerCore;

public enum PacketID
{{
	{0}
}}

public interface IPacket
{{
	ushort Protocol {{ get; }}
	void Read(ArraySegment<byte> segment);
	ArraySegment<byte> Write();
}}

{1}
";

        // ...
    }
}โ€‹

 

# ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ #3

- ์ด๋ฒˆ ์‹œ๊ฐ„๊ณผ ๋‹ค์Œ ์‹œ๊ฐ„์„ ํ†ตํ•ด Server์—์„œ์˜ Player ์ƒ์„ฑ ๋ฐ ์›€์ง์ž„์„ ๊ตฌํ˜„ ์˜ˆ์ •์ด๋‹ค.

 ~> ์šฐ์„  Server Logic ์ˆ˜์ • ํ›„, DummyClient Logic์„ ์ˆ˜์ •ํ•  ๊ฒƒ์ด๋‹ค.

PDL XML ํŒŒ์ผ ์ˆ˜์ •
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
	<packet name="S_BroadcastEnterGame">
		<int name="playerId"/>
		<float name="posX"/>
		<float name="posY"/>
		<float name="posZ"/>
	</packet>
	<packet name="C_LeaveGame">
	</packet>
	<packet name="S_BroadcastLeaveGame">
		<int name="playerId"/>
	</packet>
	<packet name="S_PlayerList">
		<list name="player">
			<bool name="isSelf"/>
			<int name="playerId"/>
			<float name="posX"/>
			<float name="posY"/>
			<float name="posZ"/>
		</list>
	</packet>
	<packet name="C_Move">
		<float name="posX"/>
		<float name="posY"/>
		<float name="posZ"/>
	</packet>
	<packet name="S_BroadcastMove">
		<int name="playerId"/>
		<float name="posX"/>
		<float name="posY"/>
		<float name="posZ"/>
	</packet>
</PDL>โ€‹

 

ClientSession Class ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;
using System.Net;

namespace Server
{
    class ClientSession : PacketSession
    {
        public int SessionId { get; set; }
        public GameRoom Room { get; set; }
        public float PosX { get; set; } // x ์ขŒํ‘œ ๊ฐ’ ์ €์žฅ์„ ์œ„ํ•ด ์ถ”๊ฐ€
        public float PosY { get; set; } // y ์ขŒํ‘œ ๊ฐ’ ์ €์žฅ์„ ์œ„ํ•ด ์ถ”๊ฐ€
        public float PosZ { get; set; } // z ์ขŒํ‘œ ๊ฐ’ ์ €์žฅ์„ ์œ„ํ•ด ์ถ”๊ฐ€

        // ...
    }
}

 

GameRoom Class ์ˆ˜์ •
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace Server
{
    class GameRoom : IJobQueue
    {
        // ...

        public void Broadcast(ArraySegment<byte> segment)
        {
            _pendingList.Add(segment);		
        }

        public void Enter(ClientSession session)
        {
            // ํ”Œ๋ ˆ์ด์–ด ์ถ”๊ฐ€
            _sessions.Add(session);
            session.Room = this;

            // ์‹ ์ž…์ƒํ•œํ…Œ ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ์ „์†ก
            S_PlayerList players = new S_PlayerList();
            foreach (ClientSession s in _sessions)
            {
                players.players.Add(new S_PlayerList.Player()
                {
                    isSelf = (s == session),
                    playerId = s.SessionId,
                    posX = s.PosX,
                    posY = s.PosY,
                    posZ = s.PosZ
                });
            }
            session.Send(players.Write());

            // ์‹ ์ž…์ƒ ์ž…์žฅ์„ ๋ชจ๋‘์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค
            S_BroadcastEnterGame enter = new S_BroadcastEnterGame();
            enter.playerId = session.SessionId;
            enter.posX = 0;
            enter.posY = 0;
            enter.posZ = 0;
            Broadcast(enter.Write());
        }

        public void Leave(ClientSession session)
        {
            // ํ”Œ๋ ˆ์ด์–ด ์ œ๊ฑฐ
            _sessions.Remove(session);

            // ํ”Œ๋ ˆ์ด์–ด ํ‡ด์žฅ์„ ๋ชจ๋‘์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค
            S_BroadcastLeaveGame leave = new S_BroadcastLeaveGame();
            leave.playerId = session.SessionId;
            Broadcast(leave.Write());
        }

        public void Move(ClientSession session, C_Move packet)
        {
            // ์ขŒํ‘œ๋ฅผ ๋ฐ”๊ฟ”์ฃผ๊ณ 
            session.PosX = packet.posX;
            session.PosY = packet.posY;
            session.PosZ = packet.posZ;

            // ๋ชจ๋‘์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค
            S_BroadcastMove move = new S_BroadcastMove();
            move.playerId = session.SessionId;
            move.posX = session.PosX;
            move.posY = session.PosY;
            move.posZ = session.PosZ;
            Broadcast(move.Write());
        }
    }
}โ€‹

 

Server์˜ PacketHandler ์ˆ˜์ •
using Server;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

class PacketHandler
{
    public static void C_LeaveGameHandler(PacketSession session, IPacket packet)
    {
        C_LeaveGame chatPacket = packet as C_LeaveGame;
        ClientSession clientSession = session as ClientSession;

        if (clientSession.Room == null)
            return;

        GameRoom room = clientSession.Room;
        room.Push(() => room.Leave(clientSession));
    }

    public static void C_MoveHandler(PacketSession session, IPacket packet)
    {
        C_Move movePacket = packet as C_Move;
        ClientSession clientSession = session as ClientSession;

        if (clientSession.Room == null)
            return;

        GameRoom room = clientSession.Room;
        room.Push(() => room.Move(clientSession, movePacket));
    }
}โ€‹

 

DummyClient์˜ PacketHandler ์ˆ˜์ •
(๋นŒ๋“œ๊ฐ€ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ์ˆ˜๋งŒ ๋งŒ๋“ค์–ด์ค„ ๋ฟ, ์‹ค์งˆ์ ์ธ ์ž‘์—…์€ ์œ ๋‹ˆํ‹ฐ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌ)
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;

class PacketHandler
{
    public static void S_BroadcastEnterGameHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastEnterGame pkt = packet as S_BroadcastEnterGame;
        ServerSession serverSession = session as ServerSession;
    }

    public static void S_BroadcastLeaveGameHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastLeaveGame pkt = packet as S_BroadcastLeaveGame;
        ServerSession serverSession = session as ServerSession;
    }

    public static void S_PlayerListHandler(PacketSession session, IPacket packet)
    {
        S_PlayerList pkt = packet as S_PlayerList;
        ServerSession serverSession = session as ServerSession;
    }

    public static void S_BroadcastMoveHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastMove pkt = packet as S_BroadcastMove;
        ServerSession serverSession = session as ServerSession;
    }
}โ€‹

 

DummyClient์˜ SessionManager ์ˆ˜์ •
using System;
using System.Collections.Generic;
using System.Text;

namespace DummyClient
{
    class SessionManager
    {
        // ...
        
        Random _rand = new Random();

        public void SendForEach()
        {
            lock (_lock)
            {
                // ์ฑ„ํŒ… ํŒจํ‚ท์ด ์•„๋‹Œ ์ด๋™ ํŒจํ‚ท์„ ๋ณด๋‚ด๋„๋ก ์ˆ˜์ •
                foreach (ServerSession session in _sessions)
                {
                    C_Move movePacket = new C_Move();
                    movePacket.posX = _rand.Next(-50, 50);
                    movePacket.posY = 0;
                    movePacket.posZ = _rand.Next(-50, 50);
                    session.Send(movePacket.Write());
                }
            }
        }

        // ...
    }
}

 

# ์œ ๋‹ˆํ‹ฐ ์—ฐ๋™ #4

- ์ง€๋‚œ ์‹œ๊ฐ„์— ์ด์–ด Server์—์„œ์˜ Player ์ƒ์„ฑ ๋ฐ ์›€์ง์ž„์„ ๋งˆ์ € ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ด๋‹ค.

Unity Project์˜ PacketQueue Script ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PacketQueue
{
    // ...

    public List<IPacket> PopAll()
    {
        List<IPacket> list = new List<IPacket>();

        lock (_lock) 
        {
            while (_packetQueue.Count > 0)
                list.Add(_packetQueue.Dequeue());
        }

        return list;
    }
}โ€‹

 

Unity Project์˜ NetworkManager Script ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using UnityEngine;

public class NetworkManager : MonoBehaviour
{
    // ...
    
    public void Send(ArraySegment<byte> sendBuff)
    {
        _session.Send(sendBuff);
    }
    
    // ...

    void Update()
    {
        // ์ผ๊ฐ ์ฒ˜๋ฆฌ
        // ํ”„๋ ˆ์ž„๋งˆ๋‹ค 1๊ฐœ์˜ ์ผ๊ฐ๋งŒ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ชจ๋“  ์ผ๊ฐํ•˜๋„๋ก ์ˆ˜์ •
        List<IPacket> list = PacketQueue.Instance.PopAll(); // ์ฆ‰, Pop()์ด ์•„๋‹Œ PopAll()์„ ์‹คํ–‰ํ•˜๋„๋ก ์ˆ˜์ •
        foreach (IPacket packet in list)
            PacketManager.Instance.HandlePacket(_session, packet);
    }
}โ€‹

 

Unity Project์— Player Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public int PlayerId { get; set; }
}โ€‹

 

Unity Project์— MyPlayer Script ์ƒ์„ฑ
using ServerCore;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MyPlayer : Player
{
    NetworkManager _network;

    void Start()
    {
        StartCoroutine("CoSendPacket");
        _network = GameObject.Find("NetworkManager").GetComponent<NetworkManager>();
    }

    void Update()
    {
        
    }

    IEnumerator CoSendPacket()
    {
        while (true)
        {
            yield return new WaitForSeconds(0.25f);

            C_Move movePacket = new C_Move();
            movePacket.posX = UnityEngine.Random.Range(-50, 50);
            movePacket.posY = 0;
            movePacket.posZ = UnityEngine.Random.Range(-50, 50);

            _network.Send(movePacket.Write());
        }
    }
}โ€‹

 

Unity Project์— PlayerManager Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerManager // ๊ธฐ์ƒํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋งŒ ๋“ค๊ณ  ์žˆ๋„๋ก MonoBehaviour ์ƒ์† X
{
    MyPlayer _myPlayer;
    Dictionary<int, Player> _players = new Dictionary<int, Player>();

    public static PlayerManager Instance { get; } = new PlayerManager();

    public void Add(S_PlayerList packet)
    {
        Object obj = Resources.Load("Player");

        foreach (S_PlayerList.Player p in packet.players)
        {
            GameObject go = Object.Instantiate(obj) as GameObject;

            if (p.isSelf)
            {
                MyPlayer myPlayer = go.AddComponent<MyPlayer>();
                myPlayer.PlayerId = p.playerId;
                myPlayer.transform.position = new Vector3(p.posX, p.posY, p.posZ);
                _myPlayer = myPlayer;
            }
            else
            {
                Player player = go.AddComponent<Player>();
                player.PlayerId = p.playerId;
                player.transform.position = new Vector3(p.posX, p.posY, p.posZ);
                _players.Add(p.playerId, player);
            }
        }
    }

    public void Move(S_BroadcastMove packet)
    {
        if (_myPlayer.PlayerId == packet.playerId)
        {
            _myPlayer.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
        }
        else
        {
            Player player = null;
            if (_players.TryGetValue(packet.playerId, out player))
            {
                player.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
            }
        }
    }

    public void EnterGame(S_BroadcastEnterGame packet)
    {
        if (packet.playerId == _myPlayer.PlayerId)
            return;

        Object obj = Resources.Load("Player");
        GameObject go = Object.Instantiate(obj) as GameObject;

        Player player = go.AddComponent<Player>();
        player.transform.position = new Vector3(packet.posX, packet.posY, packet.posZ);
        _players.Add(packet.playerId, player);
    }

    public void LeaveGame(S_BroadcastLeaveGame packet)
    {
        if (_myPlayer.PlayerId == packet.playerId)
        {
            GameObject.Destroy(_myPlayer.gameObject);
            _myPlayer = null;
        }
        else
        {
            Player player = null;
            if (_players.TryGetValue(packet.playerId, out player))
            {
                GameObject.Destroy(player.gameObject);
                _players.Remove(packet.playerId);
            }
        }
    }
}โ€‹

 

Unity Project์˜ PacketHandler Script ์ˆ˜์ •
using DummyClient;
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

class PacketHandler
{
    public static void S_BroadcastEnterGameHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastEnterGame pkt = packet as S_BroadcastEnterGame;
        ServerSession serverSession = session as ServerSession;

        PlayerManager.Instance.EnterGame(pkt);
    }

    public static void S_BroadcastLeaveGameHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastLeaveGame pkt = packet as S_BroadcastLeaveGame;
        ServerSession serverSession = session as ServerSession;

        PlayerManager.Instance.LeaveGame(pkt);
    }

    public static void S_PlayerListHandler(PacketSession session, IPacket packet)
    {
        S_PlayerList pkt = packet as S_PlayerList;
        ServerSession serverSession = session as ServerSession;

        PlayerManager.Instance.Add(pkt);
    }

    public static void S_BroadcastMoveHandler(PacketSession session, IPacket packet)
    {
        S_BroadcastMove pkt = packet as S_BroadcastMove;
        ServerSession serverSession = session as ServerSession;

        PlayerManager.Instance.Move(pkt);
    }
}โ€‹

 

'๐Ÿ“‚ Unity Engine Study > ๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์ธํ”„๋Ÿฐ ๊ฐ•์˜ - [์‹ค์ „ ๊ฒŒ์ž„ ์ฝ”๋“œ ๋ฆฌ๋ทฐ] ์œ ๋‹ˆํ‹ฐ ์บ์ฃผ์–ผ ๊ฒŒ์ž„ (์—˜๋ฆฌ์ŠคํŒก) (์„น์…˜ 0~1)  (0) 2024.04.21
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 0 ~ 2)  (0) 2024.02.12
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 13)  (0) 2024.02.03
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 7 ~ 12)  (0) 2024.01.24
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 0 ~ 6)  (0) 2024.01.04
'๐Ÿ“‚ Unity Engine Study/๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - [์‹ค์ „ ๊ฒŒ์ž„ ์ฝ”๋“œ ๋ฆฌ๋ทฐ] ์œ ๋‹ˆํ‹ฐ ์บ์ฃผ์–ผ ๊ฒŒ์ž„ (์—˜๋ฆฌ์ŠคํŒก) (์„น์…˜ 0~1)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 0 ~ 2)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 13)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 7 ~ 12)
YeonSu02
YeonSu02
Email : rkddustn2519@naver.com
  • YeonSu02
    IsLiife2
    YeonSu02
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ
      • ๐Ÿ“‚ Computer Science
      • ๐Ÿ“‚ Unity Engine Study
        • ๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜
        • ๐Ÿ“„ Unity ์œ ํŠœ๋ธŒ ๊ฐ•์˜
        • ๐Ÿ“„ Unity ์ฐธ๊ณ 
        • ๐Ÿ’ป Game Development
      • ๐Ÿ“‚ Quality Assurance Study
        • ๐Ÿ”ฅ ์—˜๋ฆฌ์Šค SW QAํŠธ๋ž™
        • ๐Ÿ“„ QA ๊ณต๋ถ€
        • ๐Ÿ“š QA ์ฑ… ๋ฆฌ๋ทฐ
      • ๐Ÿ“‚ Program Language Study
        • ๐Ÿ“„ C# ๊ณต๋ถ€
        • ๐Ÿ“„ ํŒŒ์ด์ฌ ๊ณต๋ถ€
        • ๐Ÿ“„ JavaScript ๊ณต๋ถ€
        • ๐Ÿ“„ TypeScript ๊ณต๋ถ€
      • ๐Ÿ“‚ Additional Study
        • ๐Ÿ“„ Git
        • ๐Ÿ“„ Docker
        • ๐Ÿ“„ Jenkins
        • ๐Ÿ“„ Firebase
        • ๐Ÿ“„ Economy
        • ๐Ÿ“„ License
      • ๐Ÿ“‚ Experience
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
  • ๋งํฌ

    • GitHub
  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    qa ๊ฐ•์˜
    qa ์ฑ…
    istqb-ctfl
    qa ์ฑ… ๋ฆฌ๋ทฐ
    ํ…Œ์ŠคํŒ…์ž๊ฒฉ์ฆ
    qa ๋ถ€ํŠธ์บ ํ”„
    ์ปดํ™œ
    ๊ตญ๋น„๋ถ€ํŠธ์บ ํ”„ ์ถ”์ฒœ
    ์ •์ฒ˜๊ธฐ ์‹ค๊ธฐ
    qa์ž๊ฒฉ์ฆ
    ์ •์ฒ˜๊ธฐ ํ•„๊ธฐ
    ์ •์ฒ˜๊ธฐ ๋…ํ•™
    ์ž๊ฒฉ์ฆ
    QA
    qa ์ง๋ฌด ๊ต์œก
    ์ปดํ“จํ„ฐํ™œ์šฉ๋Šฅ๋ ฅ
    ์—˜๋ฆฌ์ŠคํŠธ๋ž™ ํ›„๊ธฐ
    ์—‘์…€
    ๋ถ€ํŠธ์บ ํ”„ ์ถ”์ฒœ
    ์—˜๋ฆฌ์ŠคํŠธ๋ž™
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
YeonSu02
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 3 ~ 5)
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”