(C# : Server) Session - 4

Posted by : at

Category : Charp-Server



이벤트 핸들러를 통해 수신된 데이터를 처리해보자
우선 아래는 현재까지 완성된 코드이다.

// ServerCore
// Session.cs

class Session
{
    Socket _socket;
    int _disconnected = 0;

    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
    Queue<byte[]> _sendQueue = new Queu<byte[]>();
    bool _pending = false;
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
    object _lock = new object();

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

        SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvComplete);
        _recvArgs.SetBuffer(new byte[1024], 0/*시작인덱스*/, 1024);

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

        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;

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

    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)
            OnSendCompleted(null, _sendArgs);
    }

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

    void OnRecvComplete(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
        {
            try{
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                RegisterRecv();
            }
            catch()
            {

            }
        }
        else
        {
            Disconnect();
        }
    }

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

                    if(_sendQueue.Count > 0)
                        RegisterSend();
                    
                }
                catch()
                {

                }
            }
            else
            {
                Disconnect();
            }
        }
    }
}

송신/수신 완료 연결, 연결해제 처리를 위해 아래의 네 함수의 정의가 필요하다

// 상속으로 처리하기 위해 Session 클래스 자체를 abstract로 만든다
abstract class Session
{
    // ...

    // 상속을 통해 처리해보자
    public abstract void OnConnected(EndPoint endPoint);
    public abstract void OnRecv(ArraySegment<byte> buffer);
    public abstract void OnSend(int numOfBytes);
    public abstract void OnDisconnected(EndPoint endPoint);

    // ...

    // 엔진과 컨텐츠를 분리하는 작업이라 생각하면 된다
}
class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {

    }

    public override void OnRecv(ArraySegment<byte> buffer)
    {
        string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
    }

    public override void OnSend(int numOfBytes)
    {

    }

    public override void OnDisconnected(EndPoint endPoint)
    {

    }
}
// ServerCore
// Session.cs

class Session
{
    Socket _socket;
    int _disconnected = 0;

    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
    Queue<byte[]> _sendQueue = new Queu<byte[]>();
    bool _pending = false;
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
    object _lock = new object();

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

        SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvComplete);
        _recvArgs.SetBuffer(new byte[1024], 0/*시작인덱스*/, 1024);

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

        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;

        // abstract 함수 호출 시점
        OnDisconnected(_socket.RemoteEndPoint);

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

    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)
            OnSendCompleted(null, _sendArgs);
    }

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

    void OnRecvComplete(object sender, SocketAsyncEventArgs args)
    {
        if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
        {
            try{
                // abstract 함수 호출 시점
                OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));

                RegisterRecv();
            }
            catch()
            {

            }
        }
        else
        {
            Disconnect();
        }
    }

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

                    // abstract 함수 호출 시점
                    OnSend(_sendArgs.BytesTransferred);

                    if(_sendQueue.Count > 0)
                        RegisterSend();
                    
                }
                catch()
                {

                }
            }
            else
            {
                Disconnect();
            }
        }
    }
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if (args.SocketError == SocketError.Success)
    {
        // Session을 Listener에서 만들자
        // 여기서 다시 문제점은 GameSession을 Listener(ServerCore)에서는
        // Gamesession일지 ClientSession일지 모름
        // 컨텐츠단에서 session을 만들고 전달하는 방식으로 해야한다
        GameSession session = new GameSession();
        session.Start(args.AcceptSocket);
        session.OnConnected(args.AcceptSocket.RemoteEndPoint);
    }
    else
    {
        Console.WriteLine(args.SocketError.ToString());
    }

    RegisterAccept(args);
}

개선해보자

public class Listener
{
    Socket _listenSocket;

    // Session을 컨텐츠단에서 만들어 전달받기위함
    Func<Session> _sessionFactory;

    public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 
        /* TCP로 사용하겠다는 선언 */

        // session을 전달받는다
        _sessionFactory += sessionFactory;

        _listenSocket.Bind(endPoint);

        _listenSocket.Listen(10);
        // 10 : 최대 대기수, 초과시 입장을 제한한다

        // for(int i = 0; i < 10; i++) // 이렇게 리스너를 여러개 두어 동시에 여러 유저의 입장을 처리가능
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
        RegisterAccept(args);
    }

// ...

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if (args.SocketError == SocketError.Success)
    {
        Session session = _sessionFactory.Invoke();
        session.Start(args.AcceptSocket);
        session.OnConnected(args.AcceptSocket.RemoteEndPoint);
    }

// ...

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links