[c#/네트워크] TCP와 UDP 차이
TCP와 UDP의 차이에 대해 알아보자 📌 TCP와 UDP 차이 프로토콜 종류 연결 지향성 신뢰성 속도 특징 TCP 연결형 서비스 좋음 느림 1) 연결을 위해 할당되는 논리적인 경로가 있다. 2) 전송 순서가 보장된다. 3) 분실이 일어나면 책임지고 다시 전송한다(신뢰성 👍) 4) 물건을 주고 받을 상황이 아니면 일부만 보낸다(흐름/혼잡 제어) 5) 고려할 것이 많으니 속도가 느리다 UDP 비연결형 서비스 나쁨 빠름 1) 연결이라는 개념이 없다. 2) 전송 순서가 보장되지 않는다. 3) 분실에 대한 책임이 없다(신뢰성 👎) 4) 일단 보내고 생각한다 5) 단순하기 때문에 속도가 빠르다 데이터 전송 보장 및 순서 보장을 위해서는 tcp를 주로 사용한다. tcp는 3way handshake 방식이다. 🔎 3..
2022.12.22
no image
[c#/네트워크] listener 클래스를 활용한 비동기 소켓 프로그래밍
앞서 진행한 소켓 프로그래밍 구현에서 ServerCore 폴더에 Listener 파일을 추가한다. 문지기 즉, 소켓을 만드는 것을 따로 파일로 빼서 관리할 예정이다. 생성된 Listener 클래스 안에 소켓을 생성한다. Socket _listenSocket; 소켓을 초기화 하는 함수는 따로 만들 것이다. 기존 코드를 보면 (빨간 줄 무시) endPoint를 받아서 소켓을 생성하고 Bind도 하는 것을 볼 수 있다. (*기존 코드 : 전 글 확인..) 따라서 Init함수 인자로 endPoint를 넣어주며 Init() 함수를 다음과 같이 만들어 준다. Init() public void Init(IPEndPoint endPoint) { _listenSocket = new Socket(endPoint.Addre..
2022.12.13
no image
[c#/네트워크] 소켓 프로그래밍
소켓 프로그래밍을 구현해 보겠다. 다음과 같이 파일 구조를 만든다.. 그 다음 솔루션 우클릭 후, 속성에 들어가서 클라이언트와 서버가 한 번에 실행되도록 다음과 같이 설정한다. 이제 서버부터 구현해 보자. 일단 연결을 시도하려는 자에 대한 정보를 얻어야 한다. //DNS (Domain Name System) //도메인 등록 후 -> IP주소 찾게끔 만들면 유지보수가 용이함 //PC의 호스트명을 받는다 (호스트명은 IP 주소 대신 사용할 수 있는 식별 이름) string host = Dns.GetHostName(); IPHostEntry ipHost = Dns.GetHostEntry(host); //해당 호스트의 IP 관련 주소들을 받는다. IPAddress ipAddr = ipHost.AddressLis..
2022.12.12
[c#/유니티] 네트워크 기초 - 스위치와 라우터
1️⃣ 라우터 : 서로 다른 네트워크를 연결하는 역할을 한다. (네트워크 간) IP주소를 사용하여 데이터 전송을 수행한다. 2️⃣ 스위치 : 같은 네트워크 안에서 데이터 전송을 한다. (네트워크 내) LAN 포트 사이에서 데이터 전송을 하며 MAC 주소를 사용한다. 🤔 MAC와 IP 차이? 📌 MAC 주소 (Media Access Control Address) 하드웨어 고유 주소, 48비트 16진법을 사용한다. 하드웨어 제조 업체가 지정. DC-21-5C-3C-6B-C6 다음과 같은 주소에 해당한다. 📌 ip 주소(Internet Protocol) : 네트워크 관리자 혹은 인터넷 서비스 공급자(ISP)에 의해 제공되는 주소 네트워크 연결을 위해 제공되는 주소 IPv4 주소는 32 비트 주소이고 IPv6 ..
2022.12.12
320x100
728x90

 

TCP와 UDP의 차이에 대해 알아보자

 

 

 

📌 TCP와 UDP 차이 

프로토콜 종류 연결 지향성 신뢰성 속도 특징
TCP 연결형 서비스  좋음 느림 1) 연결을 위해 할당되는
     논리적인 경로가 있다.

2) 전송 순서가 보장된다.

3) 분실이 일어나면 책임지고
    다시 전송한다(신뢰성 👍)

4) 물건을 주고 받을 상황이 아니면
    일부만 보낸다(흐름/혼잡 제어)

5) 고려할 것이 많으니 속도가 느리다
UDP 비연결형 서비스 나쁨  빠름 1) 연결이라는 개념이 없다.

2) 전송 순서가 보장되지 않는다

3) 분실에 대한 책임이 없다(신뢰성 👎)

4) 일단 보내고 생각한다

5) 단순하기 때문에 속도가 빠르다

 

 

 

데이터 전송 보장 및 순서 보장을 위해서는 tcp를 주로 사용한다.

tcp3way handshake 방식이다.

 

 

 

🔎 3-way-handshake 

송신 A쪽에서 connect request를 보내면, 
수신 B 쪽에서 Ok 신호를 보냄 


이 때, 보낸 Ok 신호가 잘 갔는 지 의문이 생길 수 있다 
그 때 송신한 A가 잘 받았다는 신호를 전달한다. 


이렇게 세 번 전송하는 것이 3-way handshake이다

 

 

 

🔎 연결이 끊기는 경우 2가지

 

  • 응답이 안오는 경우

B에서 Ok 신호를 보내고 응답이 없어서 다시 한번 Ok 신호를 보내도 답이 없다면

이 상황을 연결이 끊어졌다 라고 보는 것이다. 

 

  • Disconncect를 사용하는 경우

A 쪽에서 전송을 끊는 경우 

 

 

👉 연결이 되고 끊어지고를 프로그램이 정확히 알 수 있다. 

 

 

 

 

🔎 송,수신 보장 

 

송신 쪽에서 4개의 패킷을 보낸다 하자.

수신 쪽에서 어떤 순서로 왔는 지 알 수 없기 때문에 

1,2,3,4 라는 순서를 적어 보낸다. 

 

만약 수신 B가 1,2,4만 받았다면 

3번을 받지 못했다고 송신 A에게 알려준다. 

 

👉단점 : 지연 시간 김

 

 

 

 

udp 

위 3-way handshake는 3번을 거치기 때문에 3배 느리다는 단점이 있다. 

하지만 게임에서는 반응이 빨라야 하므로 UDP를 쓰고 싶어 한다. (지연 시간 짧음)

but.. 순서 보장이 안됨. 데이터 유실 가능성 있음 

 

 

 

 

 

⭐이를 해결하기 위해 게임 개발자들이 만든 것이 reliable udp 

tcp 비효율적인 속도 면을 향상시키고, UDP의 불안정성을 해결한다. 

 

 

728x90
반응형
320x100
728x90

 

앞서 진행한 소켓 프로그래밍 구현에서 ServerCore 폴더에 

Listener 파일을 추가한다. 

 

 

문지기 즉, 소켓을 만드는 것을 따로 파일로 빼서 관리할 예정이다. 

 

 

 

 

 

생성된 Listener 클래스 안에 소켓을 생성한다. 

Socket _listenSocket;

 

 

 

 

소켓을 초기화 하는 함수는 따로 만들 것이다. 

기존 코드를 보면 (빨간 줄 무시)

 

 

 

 

endPoint를 받아서 소켓을 생성하고 Bind도 하는 것을 볼 수 있다. 

(*기존 코드 : 전 글 확인..)

 

 

 

따라서 Init함수 인자로 endPoint를 넣어주며 Init() 함수를 다음과 같이 만들어 준다. 

 

 

 

Init()

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

    //문지기 교육 
    _listenSocket.Bind(endPoint);

    //영업 시작
    //backlog : 최대 대기수 
    _listenSocket.Listen(10);

}

 

 

 

그 후 Accept는 따로 Accept 함수를 만들어 빼준다. 

 

 

public Socket Accept()
{
    return _listenSocket.Accept();
}

 

 

 

 

이제 Program.cs 파일로 돌아가서 문지기 역할을 하는 Listener를 생성해준다. 

그리고 문지기를 초기화 하는 코드는 listener의 Init 함수와 Accept 함수 호출로 대체해준다. 

class Program
{

    static Listener _listener = new Listener();

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

        _listener.Init(endPoint,OnAcceptHandler);
        Console.WriteLine("Listening...");

        while (true)
        {
            Socket clientSocket = _listener.Accept();
        }
        
    }
    
}

 

하지만 현재 형태는 블로킹 함수로 이루어진 상황이다. 

이런 경우 사용자가 10000명이라고 가정했을 때, 무한 대기를 계속 하고 있는 상황이 발생할 수 있으므로

논 블로킹 형태로 구현해야 한다. 

 

 

 

(*블로킹 : 요청받는 함수가 작업을 모두 마치고 나서야 요청자에게 제어권을 넘겨줌 (그동안 요청자는 아무것도 하지않고 기다림)

                                                                    논블로킹 : 요청받은 함수가 요청자에게 제어권을 바로 넘겨줌 (그동안 요청자는 다른 일을 할 수 있음)

 

 

 

 

 

따라서 Accept , Receive, Send는 모두 비동기 형식 (논 블로킹) 으로 바꿔보자 

 

 

 

 

 


 

 

 

 

AcceptAsync()로 비동기 형태로 만들어준다.

이렇게 되면, 클라이언트 요청이 올때까지 무작정 기다리는 것이 아니라 다른 작업을 쭉 수행하다가

요청이 들어오면 그 때 CallBack 방식으로 알려주는 형태이다. 

public Socket Accept()
{
    _listenSocket.AcceptAsync();
    return _listenSocket.Accept();
}

 

 

 

좀 더 세부적으로 함수를 만들어보자.

Accpet 메소드 안에 있는 _listenSocket.AcceptAsync() 코드를 지우고 RegisterAccept 매소드 안에 다음과 같이 넣는다.

요청 여부에 대한 리턴값을 pending에 할당하여 확인하면서 그 값이 false이면 상황을 종료하면 된다. 

 

void RegisterAccept(SocketAsyncEventArgs args)
{
    bool pending = _listenSocket.AcceptAsync(args);
    if (pending == false)
        OnAcceptCompleted(null, args);

}

void OnAcceptCompleted(SocketAsyncEventArgs args)
{
    ...
}

 

 

 

pending이 true일 때는, 아까 말한 거꾸로 메시지를 알려주도록 하는 작업을 해보자. 

초기화 함수에 넣어주어 처음 시작 때 무조건 한번 실행되도록 할 것이다. 

public void Init(IPEndPoint endPoint)
{
    ...      //(위 코드 참고)

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

}

 

 

 

EventHandler를 사용하기 위해 콜백 함수인 OnAcceptCompleted를 넣어주고, 

형식을 맞춰주기 위해 object sender라는 인자를 추가한다. 

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

    RegisterAccept(args);
}

 

 

 

 

accept가 되면 그 이후에 데이터를 보내고 받는 작업을 TODO에서 진행한다. 

아래 작업을 수행한다고 보면 된다. 

 

 

 

 

 

accept가 완료 됐을 때 이를 처리하는 변수를 Action으로 만들어준다. 

다음과 같이 _onAcceptHandler를 만들자. 

 

 

 

 

 

그리고 Init 함수에서 onAcceptHandler를 인자로 추가하고 핸들러를 연결해준다. 

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


    //문지기 교육 
    _listenSocket.Bind(endPoint);
    

    //영업 시작
    //backlog : 최대 대기수 
    _listenSocket.Listen(10);
    
    
    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
    RegisterAccept(args);

}

 

 

 

그리고 OnAcceptCompleted 매소드를 다음과 같이 수정한다.  

_onAcceptHandler를 Invoke 시키고 소켓을 넣어주면 된다. 

 

Socket clientSocket = listenSocket.Accept();

 

원래 Accept할 때 받은 소켓을 전달해 주는 것이다. 

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

    RegisterAccept(args);
}

 

 

 

_onAcceptHandler의 Invoke 결과는 다시 프로그램 클래스에게 전달해 주어야 한다. 

다음과 같이 프로그램안에 넣어준다. 

 

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.ToString());
    }
}

 

 

 

Main 문은 다음과 같이 수정한다. 

리스너를 통해 초기화 한 부분에 OnAcceptHandler를 넣어준다. 

 

클라이언트가 들어오면 onAcceptHandler를 통해 알려달라는 의미이다. 

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

    _listener.Init(endPoint,OnAcceptHandler);

}

 

 

중요한 것..! 

이벤트를 재사용 하기 위해서는 소켓을 null로 초기화 해 주어야 한다. 

RegisterAccept 메소드에 다음과 같이 추가하자 

 

args.AcceptSocket = null; //이벤트 재사용 시, 초기화 필수

 

 


 

 

 

최종 코드 

 

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.ToString());
            }
        }
        static void Main(string[] args)
        {
            //DNS (Domain Name System)
            //도메인 등록 후 -> IP주소 찾게끔 만들면 유지보수가 용이함 
            string host = Dns.GetHostName();    //PC의 호스트명을 받는다 (호스트명은 IP 주소 대신 사용할 수 있는 식별 이름) 

            IPHostEntry ipHost = Dns.GetHostEntry(host);  //해당 호스트의 IP 관련 주소들을 받는다. 
            IPAddress ipAddr = ipHost.AddressList[0];   //여러개의 주소를 담고있는 리스트를 반환하며 이중 첫번째 값을 할당한다(여러개의 주소를 뱉어주는 경우도 있음) 
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);  //7777은 포트 번호 , IP 주소와 포트 번호를 할당한다. 



            _listener.Init(endPoint,OnAcceptHandler);
            Console.WriteLine("Listening...");

            while (true)
            {
                ;
                   
            }
            
            }

        }

 

 

Listener.cs

더보기
class Listener
    {
        Socket _listenSocket;
        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);

            //영업 시작
            //backlog : 최대 대기수 
            _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)
            {
                //TODO
                _onAcceptHandler.Invoke(args.AcceptSocket);
            }else
                Console.WriteLine(args.SocketError.ToString());

            RegisterAccept(args);
        }


        public Socket Accept()
        {
            return _listenSocket.Accept();
        }
    }

 

 

클라이언트는 동일  

 

 

 

출처 : https://joooing.tistory.com/entry/동기비동기-블로킹논블로킹

https://www.inflearn.com/course/유니티-mmorpg-개발-part4

728x90
반응형
320x100
728x90

 

 

 

소켓 프로그래밍을 구현해 보겠다. 

 

 

다음과 같이 파일 구조를 만든다..

 

 

그 다음 솔루션 우클릭 후, 속성에 들어가서 

클라이언트와 서버가 한 번에 실행되도록 다음과 같이 설정한다. 

 

 

 


 

 

 

 

 

이제 서버부터 구현해 보자.

일단 연결을 시도하려는 자에 대한 정보를 얻어야 한다.

 

 //DNS (Domain Name System)
 //도메인 등록 후 -> IP주소 찾게끔 만들면 유지보수가 용이함
 //PC의 호스트명을 받는다 (호스트명은 IP 주소 대신 사용할 수 있는 식별 이름) 
string host = Dns.GetHostName();    


IPHostEntry ipHost = Dns.GetHostEntry(host);  //해당 호스트의 IP 관련 주소들을 받는다. 
IPAddress ipAddr = ipHost.AddressList[0];   //여러개의 주소를 담고있는 리스트를 반환하며 이중 첫번째 값을 할당한다(여러개의 주소를 뱉어주는 경우도 있음) 
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);  //7777은 포트 번호 , IP 주소와 포트 번호를 할당한다.

 

  • IPHostEntry : DNS 클래스의 GetHostEntry의 반환 값을 담는 컨테이너 클래스. 호스트 관련 정보와 IP관련 정보를 받는다.
  • IPAddress : 하나의 인터페이스에 대한 주소를 포함하는 클래스. 첫번 째 인덱스에 담긴 IP 주소를 할당한다.
  • IPEndPoint : IP 주소와 포트를 바인딩하여 소켓과 원격 주소를 연결한다. 

 

 

 

 

 

서버 코드 

 

    //문지기 
    Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

    try{

        //문지기 교육
        listenSocket.Bind(endPoint);


        // 영업 시작
        // backlog : 최대 대기수
        listenSocket.Listen(10);

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

             // 손님을 입장시킨다
             Socket clientSocket = listenSocket.Accept();

             // 받는다 
             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.Write(e.ToString());
    }
  1. 문지기를 만든다 -> 서버 소켓을 만든다 (IP관련 정보와 소켓 타입, 연결 방법(프로토콜 설정)을 결정한다. 
  2. endPoint를 바인드 한다 (* endPoint : IP주소와 포트 번호의 조합)
  3. 클라이언트를 입장 시키고, 클라이언트가 전달하려는 내용을 받는다. (문자열을 전달하는 간단 예제)
  4. 클라이언트에게 서버 응답을 전달한다 
  5. 후 연결을 종료한다                                                                                                                                                                   (* shut down : 현재 연결된 프로세스의 개수에 상관없이 연결을 종료한다. close: 연결된 개수가 0이 되어야지만 연결이 종료된다. 소켓을 메모리 상에서 완전 해제하려면 shutdown 후에 close를 해 줘야 한다)

 

 

 

 

 

⭐ 클라이언트 코드

 

    //클라이언트 소켓 설정 
    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("Connected Client !");
        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.Write(e.ToString);
    }

 

 

실행 화면

 

서버 실행 화면

 

 

클라이언트 실행 화면

 

 

 

출처 : https://www.inflearn.com/course/유니티-mmorpg-개발-part4

728x90
반응형
320x100
728x90

 

 

1️⃣ 라우터  : 서로 다른 네트워크를 연결하는 역할을 한다. (네트워크 )

  • IP주소를 사용하여 데이터 전송을 수행한다. 

 

 

 

2️⃣  스위치 : 같은 네트워크 에서 데이터 전송을 한다.  (네트워크 )

  • LAN 포트 사이에서 데이터 전송을 하며 MAC 주소를 사용한다.

 

 

 

🤔 MACIP 차이?

 

 

📌  MAC 주소 (Media Access Control Address)

  • 하드웨어 고유 주소, 48비트 16진법을 사용한다. 
  • 하드웨어 제조 업체가 지정.
  • DC-21-5C-3C-6B-C6 다음과 같은 주소에 해당한다. 

 

 

 

📌  ip 주소(Internet Protocol) :   

  • 네트워크 관리자 혹은 인터넷 서비스 공급자(ISP)에 의해 제공되는 주소 
  • 네트워크 연결을 위해 제공되는 주소
  • IPv4 주소는 32 비트 주소이고 IPv6 은 128 비트 주소

 

 

 

 

👉  IP 주소는 네트워크에 있는 장치에 대한 연결을 식별, MAC 주소는 네트워크에 참여하는 장치 자체를 식별

 

 

 

728x90
반응형