Session #1~2

Eunho Bae·2022년 5월 3일
0
namespace ServerCore
{
    internal class Session
    {
        Socket _socket;
        int _disconnected = 0;
        SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
        Queue<byte[]> _sendQueue = new Queue<byte[]>();
        bool _pending = false;
        object _lock = new object();

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

            RegisterRecv(recvArgs);
        }

        // Send의 경우는 언제 메시지를 보낼지 모르니
        // 원하는 타이밍에 호출되는 함수
        public void Send(byte[] sendBuff)
        {
            lock (_lock) // 동시에 Send 호출 금지!
            {
                _sendQueue.Enqueue(sendBuff);
                if (_pending == false)
                    RegisterSend();
            } 
        }

        // 여러 클라이언트 쓰레드가 동시에 disconnect하거나 두번연속 끊으면 문제이기 때문에 락
        public void Disconnect()
        {
            // 리턴 1이면 누군가 이미 Disconnected했다는 말
            if (Interlocked.Exchange(ref _disconnected, 1) == 1)
                return;

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

        // 내부에서만 사용
        #region 네트워크 통신

        // 누군가 RegisterSend하고 있으면 Send에서 RegisterSend() 호출하는게 아니라 큐에다만 쌓아놓는다.
        void RegisterSend()
        {
            _pending = true;
            byte[] buff = _sendQueue.Dequeue();
            _sendArgs.SetBuffer(buff, 0, buff.Length);

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

        // Send 함수에서 뿐만 아니라 콜백으로 호출될 수 있기 때문에 lock 처리
        void OnSendCompleted(object sender, SocketAsyncEventArgs args)
        {
            lock (_lock)
            {
                if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
                {
                    try
                    {
                        // Send -> RegisterSend에서 pending이 false라 OnSendCompleted실행 안되고 Send에서 lock을 빠져나왔는데(_pending = true인 상태)
                        // 다른 놈이 Send를 호출해서 큐에다 메시지 넣은 후 바로 OnSendCompleted가 호출되었다면
                        // Count는 현재 1인 상태일 것이다. _pending이 true이기 때문에 RegisterSend()는 호출하지 못했기 때문에
                        // 여기서 해준다.
                        if (_sendQueue.Count > 0)
                            RegisterSend();
                        else 
                            _pending = false;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"OnSendCompleted Failed {ex}");
                    }
                }
                else
                    Disconnect();
            }
        }


        void RegisterRecv(SocketAsyncEventArgs args)
        {
            // 클라이언트로부터 메시지 받으면 pending = false
            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) // 상대방이 연결을 끊는 등 0이 올 수 있다.
            {
                try
                {
                    string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                    Console.WriteLine($"[From Client] {recvData}");
                    RegisterRecv(args);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"(OnRecvCompleted Failed {ex}");
                }
            }
            else
            {
                Disconnect();
            }
        }
    }
    #endregion
}
profile
개인 공부 정리

0개의 댓글

관련 채용 정보