• Home
  • About
    • Hanna's Blog photo

      Hanna's Blog

      I wanna be a global developer.

    • Learn More
    • Email
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

[C#] Network Programming

04 Apr 2021

Reading time ~17 minutes

Reference by [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

Socket Programming

  • Client and Server communicate by Socket

Test

  • start ServerCore and DummyClient at the same time

  • ServerCore\Program.cs

class Program
{
    static void Main(string[] args)
    {
        // DNS(Domain Name System)
        // ex) www.google.com
        string host = Dns.GetHostName();

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

        // Socket is for connection between server and client
        Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            listenSocket.Bind(endPoint);

            listenSocket.Listen(10);

            while (true)
            {
                Console.WriteLine("Listening...");

                // if there is client, then rest codes excute
                // if there is no client, then wait for client
                Socket clientSocket = listenSocket.Accept();

                // receive client's message in recvBuff
                // store bytes of client's message
                // Server has to wait for client's sending
                byte[] recvBuff = new byte[1024];
                int recvBytes = clientSocket.Receive(recvBuff);
                
                // change client's message to letter, even you can formating by index
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"[From Client] {recvData}");

                // send server's message to client
                // Server has to wait for client's receiving
                byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
                clientSocket.Send(sendBuff);

                // close receive and send function
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
        }
        catch(Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}
  • DummyClient\Program.cs
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);

        Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            socket.Connect(endPoint);
            Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}");

            byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World!");
            int sendBytes = socket.Send(sendBuff);

            byte[] recvBuff = new byte[1024];
            int recvBytes = socket.Receive(recvBuff);
            string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
            Console.WriteLine($"[From Server] {recvData}");

            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }
        catch(Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}
C# Network

Listener

  • make listener to class
  • change blocking methods to non-blocking methods

  • ServerCore\Listener.cs
class Listener
{
    Socket _listenSocket;
    // Action after connecting
    Action<Socket> _onAcceptHandler;

    public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _onAcceptHandler = onAcceptHandler;

        _listenSocket.Bind(endPoint);

        _listenSocket.Listen(10);

        // non-blocking
        // process: register → Accept → register → Accept → ...
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        // Case: Accept is not excuted directly, then wait for next accept
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
        RegisterAccept(args);
    }

    void RegisterAccept(SocketAsyncEventArgs args)
    {
        // Initialization
        args.AcceptSocket = null;

        // Accept can be excuted randomly
        bool pending = _listenSocket.AcceptAsync(args);
        // Case: Accept is excuted directly
        if (pending == false)
            OnAcceptCompleted(null, args);
    }

    void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.SocketError == SocketError.Success)
        {
            _onAcceptHandler.Invoke(args.AcceptSocket);
        }
        else
            Console.WriteLine(args.SocketError.ToString());

        // This is for next turn client
        RegisterAccept(args);
    }
}

Test

  • ServerCore\Program.cs
class Program
{
    static Listener _listener = new Listener();

    static void OnAcceptHandler(Socket clientSocket)
    {
        try
        {
            byte[] recvBuff = new byte[1024];
            int recvBytes = clientSocket.Receive(recvBuff);
            string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
            Console.WriteLine($"[From Client] {recvData}");
        
            byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
            clientSocket.Send(sendBuff);

            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
        catch(Exception e)
        {
            Console.WriteLine(e);
        }
    }

    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, OnAcceptHandler);
        Console.WriteLine("Listening...");

        while (true){ }
    }
}
  • DummyClient\Program.cs
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);

        while (true)
        {
            Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                socket.Connect(endPoint);
                Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}");

                byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World!");
                int sendBytes = socket.Send(sendBuff);

                byte[] recvBuff = new byte[1024];
                int recvBytes = socket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"[From Server] {recvData}");

                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Thread.Sleep(100);
        }
    }
}
C# Network

Session

  • make receive and send to asynchronize

Receive async

  • ServerCore\Session.cs
class Session
{
    Socket _socket;
    int _disconnected = 0;

    public void Start(Socket socket)
    {
        _socket = socket;
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);

        // SetBuffer is for receiving
        recvArgs.SetBuffer(new byte[1024], 0, 1024);

        RegisterRecv(recvArgs);
    }

    public void Send(byte[] sendBuff)
    {
        _socket.Send(sendBuff);
    }

    public void Disconnect()
    {
        // the connection is already closed
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
            return;

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region Network Communication

    void RegisterRecv(SocketAsyncEventArgs args)
    {
        bool pending = _socket.ReceiveAsync(args);
        if (pending == false)
            OnRecvCompleted(null, args);
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");
                RegisterRecv(args);
            }
            catch(Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
                
        }
        else 
        { 
            Disconnect();
        }
    }
    #endregion
}

Test

  • ServerCore\Program.cs
class Program
{
    static Listener _listener = new Listener();

    static void OnAcceptHandler(Socket clientSocket)
    {
        try
        {
            Session session = new Session();
            session.Start(clientSocket);

            byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
            session.Send(sendBuff);

            Thread.Sleep(1000);

            session.Disconnect();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    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, OnAcceptHandler);
        Console.WriteLine("Listening...");

        while (true) { }
    }
}
  • DummyClient\Program.cs
class Program
{
    static void Main(string[] args)
    {
        ...

        while (true)
        {
            ...
            try
            {
                socket.Connect(endPoint);
                Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}");

                for(int i=0; i<5; i++)
                {
                    byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World! {i}");
                    int sendBytes = socket.Send(sendBuff);
                }

                ...
            }
            ...
        }
    }
}
C# Network

Send async

  • ServerCore\Session.cs
class Session
{
    Socket _socket;
    int _disconnected = 0;

    // for multi thread sending
    object _lock = new object();
    // store sendBuff
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    // if other client is using sending, then only store sendBuffs on Queue
    bool _pending = false;
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();

    public void Start(Socket socket)
    {
        _socket = socket;
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        recvArgs.SetBuffer(new byte[1024], 0, 1024);

        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompeleted);

        RegisterRecv(recvArgs);
    }

    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            if (_pending == false)
                RegisterSend();
        }
    }

    public void Disconnect()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
            return;

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region Network Communication

    void RegisterSend()
    {
        _pending = true;
        byte[] buff = _sendQueue.Dequeue();
        _sendArgs.SetBuffer(buff, 0, buff.Length);

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
            OnSendCompeleted(null, _sendArgs);
    }

    void OnSendCompeleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    // if other client store sendBuff on Queue, Register again
                    if (_sendQueue.Count > 0)
                        RegisterSend();
                    else
                        _pending = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompeleted Failed {e}");
                }
            }
            else
            {
                Disconnect();
            }
        }
    }

    void RegisterRecv(SocketAsyncEventArgs args)
    {
        bool pending = _socket.ReceiveAsync(args);
        if (pending == false)
            OnRecvCompleted(null, args);
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");
                RegisterRecv(args);
            }
            catch(Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
                
        }
        else
        {
            Disconnect();
        }
    }

    #endregion
}

Test

C# Network

Send packet

  • match interface of receive and send
  • make send buffer to package

  • ServerCore\Session.cs
class Session
{
    Socket _socket;
    int _disconnected = 0;

    object _lock = new object();
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    // for add buffer to BufferList
    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();

    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

    public void Start(Socket socket)
    {
        _socket = socket;

        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        _recvArgs.SetBuffer(new byte[1024], 0, 1024);

        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompeleted);

        RegisterRecv();
    }

    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            // there is no waiting sending
            if (_pendingList.Count == 0)
                RegisterSend();
        }
    }

    public void Disconnect()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
            return;

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region Network Communication

    void RegisterSend()
    {
        // add sendQueue data to BufferList
        while (_sendQueue.Count > 0)
        {
            byte[] buff = _sendQueue.Dequeue();
            _pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
        }

        _sendArgs.BufferList = _pendingList;

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
            OnSendCompeleted(null, _sendArgs);
    }

    void OnSendCompeleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    // Initialize
                    _sendArgs.BufferList = null;
                    _pendingList.Clear();

                    Console.WriteLine($"Transferred bytes: {_sendArgs.BytesTransferred}");
                        
                    if (_sendQueue.Count > 0)
                        RegisterSend();
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompeleted Failed {e}");
                }
            }
            else
            {
                Disconnect();
            }            }
    }

    void RegisterRecv()
    {
        bool pending = _socket.ReceiveAsync(_recvArgs);
        if (pending == false)
            OnRecvCompleted(null, _recvArgs);
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");
                RegisterRecv();
            }
            catch(Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
                
        }
        else
        {
            Disconnect();
        }
    }
    #endregion
}

Test

C# Network

seperate Session

  • seperate engine and contents
  • add EventHandler

  • ServerCore\Session.cs
    • Core Engine
abstract class Session
{
    Socket _socket;
    int _disconnected = 0;

    object _lock = new object();
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();

    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

    // contents
    public abstract void OnConnected(EndPoint endPoint); // This is on Listener
    public abstract void OnRecv(ArraySegment<byte> buffer);
    public abstract void OnSend(int numOfBytes);
    public abstract void OnDisconnected(EndPoint endPoint);


    public void Start(Socket socket)
    {
        _socket = socket;

        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        _recvArgs.SetBuffer(new byte[1024], 0, 1024);

        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompeleted);

        RegisterRecv();
    }

    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            if (_pendingList.Count == 0)
                RegisterSend();
        }
    }

    public void Disconnect()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
            return;

        OnDisconnected(_socket.RemoteEndPoint);
        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region Network Communication

    void RegisterSend()
    {
        while (_sendQueue.Count > 0)
        {
            byte[] buff = _sendQueue.Dequeue();
            _pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
        }

        _sendArgs.BufferList = _pendingList;

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
            OnSendCompeleted(null, _sendArgs);
    }

    void OnSendCompeleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    _sendArgs.BufferList = null;
                    _pendingList.Clear();

                    OnSend(_sendArgs.BytesTransferred);
                        
                    if (_sendQueue.Count > 0)
                        RegisterSend();
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompeleted Failed {e}");
                }
            }
            else
            {
                Disconnect();
            }
        }
    }

    void RegisterRecv()
    {
        bool pending = _socket.ReceiveAsync(_recvArgs);
        if (pending == false)
            OnRecvCompleted(null, _recvArgs);
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
                RegisterRecv();
            }
            catch(Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
                
        }
        else
        {
            Disconnect();
        }
    }
    #endregion
}
  • ServerCore\Listener.cs
class Listener
{
    Socket _listenSocket;
    // Define Session by _sessionFactory
    Func<Session> _sessionFactory;

    public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _sessionFactory = sessionFactory;

        _listenSocket.Bind(endPoint);

        _listenSocket.Listen(10);

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
        RegisterAccept(args);
    }

    void RegisterAccept(SocketAsyncEventArgs args)
    {
        args.AcceptSocket = null;

        bool pending = _listenSocket.AcceptAsync(args);
        if (pending == false)
            OnAcceptCompleted(null, args);
    }

    void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.SocketError == SocketError.Success)
        {
            // For all type of session
            Session session = _sessionFactory.Invoke();
            session.Start(args.AcceptSocket);
            session.OnConnected(args.AcceptSocket.RemoteEndPoint);
        }
        else
            Console.WriteLine(args.SocketError.ToString());

        RegisterAccept(args);
    }
}
  • ServerCore\Program.cs
// Event Handler
class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected: {endPoint}");

        byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
        Send(sendBuff);

        Thread.Sleep(1000);

        Disconnect();
    }

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

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

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

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);

        // Create GameSession by rambda func
        _listener.Init(endPoint, ()=> { return new GameSession(); });
        Console.WriteLine("Listening...");

        while (true) { }
    }
}

Test

C# Network

Connector

  • avoid blocking method

  • Connector
    • Listener is for listening clients from server
    • So, Connector is for connecting server from client
  • ServerCore
    • from now, this project is class library
    • You should change start project to DummyClient and Server and delete Program.cs in ServerCore
C# Network

ServerCore

  • ServerCore\Connector.cs
public class Connector
{
    Func<Session> _sessionFactory;

    public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _sessionFactory = sessionFactory;

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += OnConnectCompleted;
        args.RemoteEndPoint = endPoint;
        // connect with socket
        // This is for multiple clients
        args.UserToken = socket;

        RegisterConnect(args);
    }

    void RegisterConnect(SocketAsyncEventArgs args)
    {
        // args.UserToken is obejct type
        // You need to convert this to Socket
        Socket socket = args.UserToken as Socket;
        if (socket == null)
            return;
        bool pending = socket.ConnectAsync(args);
        if (pending == false)
            OnConnectCompleted(null, args);
    }

    void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.SocketError == SocketError.Success)
        {
            Session session = _sessionFactory.Invoke();
            session.Start(args.ConnectSocket);
            session.OnConnected(args.RemoteEndPoint);
        }
        else
        {
            Console.WriteLine($"OnConnectCompleted Fail: {args.SocketError}");
        }
    }
}
  • ServerCore\Listener.cs
public class Listener
{
    ...
}
  • ServerCore\Session.cs
public abstract class Session
{
    ...
}

Server

  • Server\Program.cs
class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected: {endPoint}");

        byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
            Send(sendBuff);

            Thread.Sleep(1000);

            Disconnect();
        }

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

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

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

    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 GameSession(); });
            Console.WriteLine("Listening...");

            while (true)
            {

            }
        }
    }
}

DummyClient

  • DummyClient\Program.cs
// for using Session
class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected: {endPoint}");
        for (int i = 0; i < 5; i++)
        {
            byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i} ");
            Send(sendBuff);
        }
    }

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

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

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

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);

        // for using Connector with Session
        Connector connector = new Connector();
        connector.Connect(endPoint, () => { return new GameSession(); });

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

            Thread.Sleep(100);
        }
    }
}

Test

C# Network

TCP vs UDP

  • TCP
    • There is a logical path assigned for connection
    • The sequence of transmissions is guaranteed
    • If loss occurs, responsible and retransmitted (reliability)
    • Send only part of the item unless it is in a situation where it is exchanged (flow/confusion control)
    • Speed is bad
  • UDP
    • There is not connection
    • The sequence of transmissions is not guaranteed
    • If loss occurs, non responsible(non reliability)
    • Just sending
    • Speed is good

Buffer

Recieve Buffer

  • SeverCore\RecvBuffer.cs
public class RecvBuffer
{
    ArraySegment<byte> _buffer;
    // cursor
    int _readPos;
    int _writePos;

    public RecvBuffer(int bufferSize)
    {
        _buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
    }

    // amount of data
    public int DataSize { get { return _writePos-_readPos; } }
    // amount of remained buffer
    public int FreeSize { get { return _buffer.Count - _writePos; } }

    public ArraySegment<byte> ReadSegment
    {
        // Source, Start point, size
        get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _readPos, DataSize); }
    }

    public ArraySegment<byte> WriteSegment
    {
        // Source, Start point, size
        get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _writePos, FreeSize); }
    }

    public void Clean()
    {
        int dataSize = DataSize;
        if (dataSize == 0)
        {
            // If there is no remained data, not copy, just reset cursor location
            _readPos = 0;
            _writePos = 0;
        }
        else
        {
            // If there is remained data, copy data on start buffer location
            Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize);
            _readPos = 0;
            _writePos = dataSize;
        }
    }

    public bool OnRead(int numOfBytes)
    {
        // if reading data is bigger then data size, return
        if (numOfBytes > DataSize)
            return false;

        _readPos += numOfBytes;
        return true;
    }

    public bool OnWrite(int numOfBytes)
    {
        // if writing data is bigger then free size, return
        if (numOfBytes > FreeSize)
            return false;

        _writePos += numOfBytes;
        return true;
    }
}
  • ServerCore\Session.cs
public abstract class Session
{
    ...
    public abstract int OnRecv(ArraySegment<byte> buffer);
    ...

    public void Start(Socket socket)
    {
        _socket = socket;

        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompeleted);

        RegisterRecv();
    }

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

        bool pending = _socket.ReceiveAsync(_recvArgs);
        if (pending == false)
            OnRecvCompleted(null, _recvArgs);
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                // Write cursor move
                if(_recvBuffer.OnWrite(args.BytesTransferred) == false)
                {
                    Disconnect();
                    return;
                }

                // send data to contents and recieve how many data was handled
                int processLen = OnRecv(_recvBuffer.ReadSegment);
                if (processLen < 0 || _recvBuffer.DataSize < processLen)
                {
                    Disconnect();
                    return;
                }

                // Read cursor move
                if(_recvBuffer.OnRead(processLen) == false)
                {
                    Disconnect();
                    return;
                }

                RegisterRecv();
            }
            catch(Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            Disconnect();
        }
    }
}
  • Server\Program.cs
class GameSession : Session
{
    public override int OnRecv(ArraySegment<byte> buffer)
    {
        string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
        Console.WriteLine($"[From Client] {recvData}");
        return buffer.Count;
    }
}
  • DummyClient\Program.cs
class GameSession : Session
{
    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;
    }
}

Send Buffer

  • ServerCore\SendBuffer.cs
// to avoid thread compitition
public class SendBufferHelper
{
    // TLS
    public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; });

    public static int ChunkSize { get; set; } = 4096 * 100;

    public static ArraySegment<byte> Open(int reserveSize)
    {
        // Initialize
        if (CurrentBuffer.Value == null)
            CurrentBuffer.Value = new SendBuffer(ChunkSize);

        // reset
        if (CurrentBuffer.Value.FreeSize < reserveSize)
            CurrentBuffer.Value = new SendBuffer(ChunkSize);

        return CurrentBuffer.Value.Open(reserveSize);
    }

    public static ArraySegment<byte> Close(int usedSize)
    {
        return CurrentBuffer.Value.Close(usedSize);
    }
}

public class SendBuffer
{
    byte[] _buffer;
    int _usedSize = 0;

    public int FreeSize { get { return _buffer.Length - _usedSize; } }
        
    public SendBuffer(int chunkSize)
    {
        _buffer = new byte[chunkSize];
    }
        
    // reserveSize is maximal reaserving size(not real using size)
    public ArraySegment<byte> Open(int reserveSize)
    {
        if (reserveSize > FreeSize)
            return null;

        return new ArraySegment<byte>(_buffer, _usedSize, reserveSize);
    }

    // usedSize is real using size
    public ArraySegment<byte> Close(int usedSize)
    {
        ArraySegment<byte> segment = new ArraySegment<byte>(_buffer, _usedSize, usedSize);
        _usedSize = usedSize;
        return segment;
    }
}
  • ServerCore\Session.cs
public abstract class Session
{
    ...
    Queue<ArraySegment<byte>> _sendQueue = new Queue<ArraySegment<byte>>();
    ...
    public void Send(ArraySegment<byte> sendBuff)
    {
        ...
    }

    void RegisterSend()
    {
        while (_sendQueue.Count > 0)
        {
            ArraySegment<byte> buff = _sendQueue.Dequeue();
            _pendingList.Add(buff);
        }
        ...
    }
    ...
}
  • Server\Program.cs
class Knight
{
    public int hp;
    public int attack;
    // size is changable
    public string name;
    public List<int> skills = new List<int>();
}

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

        Knight knight = new Knight() { hp = 100, attack = 10 };

        ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
        // convert int to byte array
        byte[] buffer = BitConverter.GetBytes(knight.hp);
        byte[] buffer2 = BitConverter.GetBytes(knight.attack);
        // Source, index, destination Array, destination Index, length
        Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
        Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
        ArraySegment<byte> sendBuffer = SendBufferHelper.Close(buffer.Length + buffer2.Length);
        ...
    }
    ...
}

Test

C# Network

Packet Session

  • you have to check sended data is whole data

  • ServerCore\Session.cs

public abstract class PacketSession: Session
{
    public static readonly int HeaderSize = 2;

    // sealed: other class cannot use this class
    // instead, they can use abstract class
    public sealed override int OnRecv(ArraySegment<byte> buffer)
    {
        int processLen = 0;

        while (true)
        {
            // At a minimum, verify that headers can be parsed
            if (buffer.Count < HeaderSize)
                break;

            // Verify that the packet has arrived as a complete
            ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
            if (buffer.Count < dataSize)
                break;

            // assemble Packet
            OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));

            processLen += dataSize;
            // move buffer from current buffer to next buffer
            buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
        }

        return processLen;
    }

    public abstract void OnRecvPacket(ArraySegment<byte> buffer);

}
  • Server\Program.cs
class Packet
{
    // to save memory
    public ushort size;
    public ushort packetId;
}

class LoginOkPacket: Packet{ }

class GameSession : PacketSession
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected: {endPoint}");
        Thread.Sleep(5000);
        Disconnect();
    }

    public override void OnRecvPacket(ArraySegment<byte> buffer)
    {
        ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
        ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + 2);
        Console.WriteLine($"RecvPacketId: {id}, RecvPacketSize: {size}");
    }

    ...
    // you cannot use OnRecv because it's sealed
    // public override int OnRecv(ArraySegment<byte> buffer)
    // {
    //     tring recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
    //     Console.WriteLine($"[From Client] {recvData}");
    //     return buffer.Count;
    // }
    ...
}
  • DummyClient\Program.cs
class Packet
{
    public ushort size;
    public ushort packetId;
}

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

        Packet packet = new Packet() { size = 4, packetId = 7 };

        for (int i = 0; i < 5; i++)
        {
            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, openSegment.Offset + buffer.Length, buffer2.Length);
            ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);

            Send(sendBuff);
        }
    }
    ...
}

Test

C# Network

Download



C#Server Share Tweet +1