[c#] 소켓프로그래밍 확장 2( Session, receive)

TNT·2024년 10월 6일

C#

목록 보기
3/5

저번 포스트 에서는 Listener 부분을 수정 해서 비동기 형식으로 동작 하게끔수정 했는데 이번에도
남은 부분 데이터 주고 받는 부분도 역시 비동기로 처리 해야 더 많은 인원들의 동작을 처리할수 있기 때문에 여기를 수정하는 작업을 할예정이다.
먼저 코드에서 listen 이랑 send 부분중에서 send가 더 까다롭고 어렵기 때문에 2개로 나눠서 작성 할예정이다.

listen

먼저 데이터를 받는 부분 부터 빼낼 예정인데 클래스 하나를 만들어 줘야한다.

Session.cs 라는 이름 으로 하나 만들었다.
이제 session 에서 receive 이랑 send 코드를 빼서 연결해주자.

그러면 그러면 Session.cs부터 확인해보자

Session.cs

using System.Net.Sockets;
using System.Text;

namespace ServerCore
{
    internal class Session
    {
        Socket _socket;
        int _disconnected = 0;

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

            SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
            recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRevCompleted);
            recvArgs.SetBuffer(new byte[1024], 0, 1024);
            
            RegisterRecv(recvArgs);
        }
        public void Send(byte[] sendBuff)
        {
            _socket.Send(sendBuff);
        }

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

            _socket.Shutdown(SocketShutdown.Both); // 연결 종료
            _socket.Close();
        }

        #region 네트워크 통신
        void RegisterRecv(SocketAsyncEventArgs args)
        {
            bool pending = _socket.ReceiveAsync(args);
            if (pending == false)
                OnRevCompleted(null, args);

        }

        void OnRevCompleted(object sender, SocketAsyncEventArgs args)
        {
            // 가끔 데이터 보내는중에 byte 크기가 0 으로 올수 있어서 체크 해줘야한다.
            if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                //데이터 받기 처기
                try
                {
                    string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred); // 바이트로된 데이터 string으로 변환
                    Console.WriteLine($"[Received] {recvData}");

                    RegisterRecv(args);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnRecvCompleted Failed {e}");
                }
               
            }
            else
            {
                // 연결 취소
            }
        }
        #endregion

       

    }
}

기본적인 모습은 저번 포스트에 있는 Listener.cs랑 매우 비슷하다 init은 start 로 바뀐 모습이지만 안에있는건
소켓 등록하고 사용하는 모습이다.

코드를 볼때 먼저 중요하게 봐야하는것이 이제 부터 비동기로 처리 하기 때문에 다 주의 해야하지만 특히
Disconnect 함수아래 보면 흔히 말하는 멀티 스레드에 lock을 걸어둔 모습을 볼수있다.
Interlocked 위에 보면 _disconnected 플레그를 하나 세워 두고 나서
그걸 1로 바뀌면 이미 종료된 세션으로 보고 그냥 리턴해주는것이다.

그러면 이제 메인 쪽도 같이 수정해주자.

Program.cs

using ServerCore;
using System.Net;
using System.Net.Sockets;
using System.Text;

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

    static void OnAcceptHandler(Socket clientSocket)
    {
        try  // 소켓관련 작업은 문제가 생기면 동작 멈출확률이 높기때문에 try catch 로 감사주면 좋다.
        {
            Session session = new Session();
            session.Start(clientSocket);

            byte[] sendBuff = Encoding.UTF8.GetBytes("Server Send String data"); // 클라이언트로 보낼 데이터 생성
            session.Send(sendBuff);

            Thread.Sleep(1000);
            
            session.Disconnect();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    static void Main(String[] args)
    {
        // Dns 얻기
        string host = Dns.GetHostName(); // 로컬 호스트 이름 찾기
        IPHostEntry ipHost = Dns.GetHostEntry(host); // 호스트 ip 얻기
        IPAddress ipAddr = ipHost.AddressList[0]; // ip리스트중에 첫번째 획득
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip 특정 포트 번호 접근

        _listener.init(endPoint, OnAcceptHandler);
        Console.WriteLine("대기중");

        while (true)
        {
            ; // 프로그램 종료 방지
        }
    }
}

연결 성공 했을때 함수 호출하는곳이 수정 된 모습을 보인다.
OnAcceptHandler 부분을 보면 session 을 생성하고 난뒤에 start 해주면 자동으로 receive 부분이 비동기로 동작하고 데이터 받고 send 해주고 연결 종료 되는 모습인데 여기서
session.Disconnect();
session.Disconnect();
만약에 2번 불러 와 진경우에는 에러가 난다. 하지만 아까 위에서 플레그 세워두고 lock 걸고 처리한부분이 있기 때문에 에러 없이 처리 될것이다 테스트를 해보고 싶으면 lock 부분을 주석으로 처리하고 확인 해보면 될것이다.

클라이언트도 약간 수정을 하겠다.

client.cs

        for(int i = 0; i < 5; i++)
        {
            byte[] buffer = Encoding.UTF8.GetBytes($"Client Send String data{i}"); // 서버로 보낼 데이터 생성
            int sendByte = socket.Send(buffer); // 서버로 보내기
        }

딱히 수정 할건 없고 send 해주는 부분만 수정 했다.
그러면 실행 시켜서 확인해보자.

결과


잘 동작하는걸 확인할수있다.
이렇게 receive 하는 부분 까지 비동기 처리를 했다.

send 관련 된건 다음 포스트에 이어서 작성 하겠다.

참조

강의 주소

profile
개발

0개의 댓글