출처 : http://nenunena.tistory.com/61




많은 사람들이 UDP 소켓 프로그램의 흐름은 아래 그림과 같다고 배웠을 것이고, 일부는 서버쪽에도 bind()함수가 없는 것으로 배웠을 것이다. 아닌가? ^^;




나는 UDP에 대해서는 이렇게만 알고 넘어간 뒤 TCP만 사용했었다. 

나중에 UDP를 사용한 소켓 프로그램을 직접 만들때가 되어서야 UDP는 서버와 클라이언트의 개념이 모호하다는 걸 알았다.

일단 UDP 소켓 프로그래밍에서는 Listen(), Accept() 함수를 사용하지 않는다.

Bind()는 UDP 서버에서 사용한다고 배웠을 것이고, 그럼 Connect() 함수는?
Connect()함수를 사용하는 경우도 있다. 그러나 UDP에서 Connect()함수는 접속과는 관련이 없는 함수이다.


Bind() : 프로세스에서 특정 포트를 명시적으로 쥐고 있기 위해 사용한다. "나 지금부터 9080 포트를 사용할거야" 라는 식의 의미다.
UDP 소켓 프로그램 흐름도에서 서버는 Bind()를 해줘야 한다고 되어 있지만 사실 해주지 않아도 된다.

일단 UDP 프로그램이 언제 어떻게 포트를 가지게 되느냐는 나중에 생각한다 치자.
SendTo()함수를 호출하여 데이터를 보내는 쪽에서 Bind()로 특정 포트를 정하지 않아도 RecvFrom()함수로 데이터를 받는 쪽은 함수 인자를 통해 보낸 쪽의 IP와 포트번호를 얻을 수 있지 않은가?


윈도우 MFC CAyncSocket 클래스에 있는 Create()함수는 내부적으로 Socket()함수와 Bind()함수를 호출한다.

Create()함수를 호출하지 않고 Socket()함수만을 직접적으로 호출하여 UDP소켓을 만든 뒤, 실제로 존재하는 유효한 IP(포트는 뭐든 상관없다.)로 SendTo를 하게 되면 그 순간 시스템에서 자동으로 포트를 할당해 준다.
단, 말도 안되는 이상한 IP를 적으면 SendTo()가 실패하면서 포트도 할당되지 않는다.

결과적으로 UDP 소켓 프로그램은 Socket(), SendTo(), ReceiveFrom()함수만으로 동작할 수 있다는 얘기가 된다.

그리고 1시간정도 확인결과, 시스템에 따라 다르겠지만 이렇게 SendTo()하는 순간 자동할당된 포트도 소켓이 Close()할때까지 유효하다. (계속 이 허접 UDP 프로그램이 할당받은 포트를 쥐고 있다.)
보장한다고는 못하겠다. 따라서 확실히 자신이 원하는 포트를 열고 있으려면 Create()함수나 Bind()함수를 사용하는 것이 좋겠다.



UDP에서 Connect()함수는 내부적인 속도 향상을 위해 사용한다. Connect()함수를 사용하면 UDP 소켓과 커널의 연결을 항상 open 하게 된다. 이렇게 되면 SendTo() ReceiveFrom() 호출할때마다 UDP소켓과 커널을 open 하는 과정이 생략되어 얼만큼인지는 몰라도 성능 향상이 있다고 한다.

* UDP에서 connect()함수에 대한 자세한 정보는 아래 두 링크를 참조하길 바란다.

- UDP 소켓의 성능향상
 http://thx4alice.tistory.com/40

- UDP 기반 서버/클라이언트 (아래부분에 connect에 관한 내용이 있다.)
http://pchero21.com/entry/6-UDP-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B2%84%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8



마지막으로 TCP와 UDP의 차이점중 하나가 TCP는 커넥션을 맺은 두 호스트끼리만 데이터를 주고 받을 수 있지만, UDP는 IP와 포트 번호만 알면 누구든지 데이터를 보낼 수 있다는 것이다.

내 IP가 192.168.0.1이고 UDP로 8000번 포트를 열어둔 뒤, 이 사실을 철수와 영희에게 알려주면 두 사람다 나에게 데이터를 보낼 수 있고 나도 데이터를 받은 순간 함수인자로 알아낸 철수와 영희의 IP, 포트번호로 그들에게 데이터를 전송할 수 있다.

이런 UDP의 특징을 이용하여 IP공유기 등이 사용하는 NAT기능이 작동중인 네트워크의 내부 호스트에게 데이터를 전달하는 방법으로 UDP 홀펀칭이라는 것이 있다.

TCP는 데이터를 주고 받기 위해서는 먼저 한사람(클라이언트)이 다른 사람(서버)의 정확한(공인된) IP와 포트번호를 알고 연결을 맺어야 한다. 우리가 보통 집에서 인터넷을 사용할때는 클라이언트입장에서 서버에 접속하여 데이터를 주고 받는 방식을 사용한다. IP공유기를 사용한다면 따로 공유기를 설정(포트포워딩)하지 않는 이상 자신이 TCP서버가 되어 외부 접속을 받아 들일 수 없다.

UDP 홀펀칭을 간단히 설명하면
먼저 외부에 있는 중계자(호스트)에게 NAT 네트워크 내부 호스트A가 UDP 패킷을 전송한다. 패킷을 받은 중계자는 NAT를 통해 바뀐 공인 IP와 포트 번호를 알게 된다.
다음으로 다른 호스트B가 중계자에게 접속하여(TCP든 UDP든) 중계자가 가지고 있는 NAT내부 호스트A의 바뀐 IP와 포트번호를 전달 받는다.
호스트B는 알아낸 IP와 포트번호로 UDP 패킷을 전송하면 공유기가 NAT 매핑정보를 보고 호스트A의 내부 IP와 포트번호로 패킷을 전달한다.

스타크래프트 배틀넷이 이런 UDP 홀펀칭을 사용한다고 한다.
배틀넷 서버가 중계자가 되는 것이고, 나머지 게이머들이 호스트A와 호스트B가 되는 것이다.


하아~ 이론과 실무를 연결시키는 일은 쉽지 않지만 재밌는 것 같다.