성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>홀 펀칭(Hole Punching)을 이용한 Private IP 간 통신 - C#</h1> <p> <br /> 오... 재미있는 사실을 하나 알았습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 실전에서 알아보는 홀펀칭 방법. ; <a target='tab' href='http://www.gamedevforever.com/47'>http://www.gamedevforever.com/47</a> </pre> <br /> 간단한 예를 들어서, 가정에서 공유기를 이용하여 인터넷에 접속한 A, B 사용자가 있다고 가정할 때 대부분 공유기에 공용 IP가 할당되기 때문에 서로 간에 통신이 되지 않습니다. 그럴 때 홀 펀칭을 이용해주면 A, B 모두 Private IP를 사용하고 있는데도 불구하고 서로 간에 메시지를 보낼 수 있습니다. (사설 네트워크는 A, B, C 클래스 별로 각각 10.0.0.0, 172.16.0.0, 192.168.0.0을 예약)<br /> <br /> 테스트를 하기 위해 서버 측 역할을 하는 컴퓨터 한 대와, 각각의 영역에서 Private IP를 가지고 있는 PC 2대가 필요합니다. 실제로 어떻게 패킷이 이동하는지 따라가 볼까요? ^^<br /> <br /> 우선, 클라이언트가 서버 측에 UDP 메시지를 전송할 것입니다. 이 과정에서 다음과 같은 연결 통로가 구성됩니다.<br /> <br /> <img alt='hole_punch_1.png' src='/SysWebRes/bbs/hole_punch_1.png' /><br /> <br /> 실제로 위와 같은 그림의 통신이 발생하도록 C# 코드로 구성해볼까요? ^^<br /> <br /> 일단 서버의 UDP 소켓은 12000 번 포트로 입력을 대기하는 것으로 시작해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ===== 서버 측 소스 코드 ===== UdpClient _server; _server = new UdpClient(12000); _server.BeginReceive(udpReceiveCallback, _server); // 비동기 데이터 수신 void udpReceiveCallback(IAsyncResult ar) { try { UdpClient udpServer = ar.AsyncState as UdpClient; IPEndPoint remoteEndPoint = null; byte [] receiveBytes = udpServer.EndReceive(ar, ref remoteEndPoint); // 접속된 클라이언트의 IP 주소와 포트 출력 Console.WriteLine("Receive from " + remoteEndPoint.Address.ToString() + ":" + remoteEndPoint.Port); udpServer.BeginReceive(udpReceiveCallback, udpServer); } catch { } } </pre> <br /> 보시는 것처럼, 서버는 현재 단순하게 접속된 클라이언트의 IP 주소와 포트를 출력하는 기능만 있습니다.<br /> <br /> 이어서 클라이언트 측을 구현하면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ===== 클라이언트 측 소스 코드 ===== UdpClient _udpClient = new UdpClient(); private void Form1_Load(object sender, EventArgs e) { IPAddress ipAddress = IPAddress.Parse("124.137.26.136"); IPEndPoint holePunchServer = new IPEndPoint(ipAddress, 12000); string uid = Environment.MachineName; byte [] uidBytes = Encoding.UTF8.GetBytes(uid); _udpClient.Send(uidBytes, uidBytes.Length, holePunchServer); } </pre> <br /> 위와 같이 클라이언트를 실행하고 서버로 데이터가 전송되면 서버 측에는 어떤 메시지가 출력될까요? 당연히 "Receive from 175.194.21.149:60010"이 됩니다. 통신 패킷만으로 보면 서버 측에서는 절대 클라이언트의 Private IP와 포트를 알 수 없습니다.<br /> <br /> 이 때문에, 서버에서 클라이언트로 데이터를 보내야 할 일이 생기면 공유기에 열린 포트를 대상으로 데이터를 전송하게 됩니다. 아래의 그림에서 보는 것처럼, 다시 그 과정이 역으로 진행되는 것입니다.<br /> <br /> <img alt='hole_punch_2.png' src='/SysWebRes/bbs/hole_punch_2.png' /><br /> <br /> 이 때 공유기는 60010 포트로 들어온 데이터가 수신되어야 할 내부 IP의 컴퓨터에 대한 정보(192.168.50.10, 50010포트)를 가지고 있으므로 정상적으로 해당 컴퓨터로 UDP 패킷을 전송할 수 있게 되는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이론적으로는 저렇게 패킷이 오고가는 것은 알고 있었지만... "Hole Punching"까지는 생각할 수 없었습니다. 역시 ... 이런 맛에 공부해나가는 즐거움이 있겠지요. ^^<br /> <br /> "Hole Punching"에서는 공유기의 NAT에서 유지되는 매핑 테이블의 도움을 받아 이뤄집니다. 아이디어는 사실 매우 간단합니다. 즉, UDP 서버가 아니라 다른 컴퓨터에서 해당 공유기 IP의 60010 포트로 데이터를 전송하면 어떨까 하는 것입니다.<br /> <br /> <img alt='hole_punch_3.png' src='/SysWebRes/bbs/hole_punch_3.png' /><br /> <br /> 물론, 저 상황에서는 데이터를 보낸 측이 "124.137.26.36:12000" 주소가 아니기 때문에 공유기 측에서 버려집니다. 하지만, 저렇게 데이터를 보내는 와중에 클라이언트 측이 "100.100.100.100:15000" 주소로 데이터를 한번 전송해 주면 어떻게 될까요?<br /> <br /> <img alt='hole_punch_4.png' src='/SysWebRes/bbs/hole_punch_4.png' /><br /> <br /> 공유기 매핑 테이블에는 175.194.21.14:60010 포트에 대해 2가지 원격 주소지가 포함되어 데이터를 받을 수 있게 됩니다. 결과적으로, Private IP를 가지고 있는 PC 임에도 불구하고 마치 공용 IP에 연결할 수 있는 것처럼 데이터를 전달받을 수 있게 된 것입니다.<br /> <br /> 어떠세요? 처음 <a target='tab' href='http://www.gamedevforever.com/47'>이 글</a>을 봤을 때 '아~~~' 하는 감탄사가 나오더군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 일단, 원리는 그렇다 치고 정말 되는지 한번 확인을 해봐야 되지 않을까요? ^^<br /> <br /> 그래서, 테스트 환경을 준비해 봤습니다. 우선, 제가 테스트 해 볼 수 있는 network가 '회사 컴퓨터'와 '집'입니다. 물론, 집에 있는 컴퓨터는 Access Point를 통해서 연결하고 있기 때문에 Private IP입니다. 그런데... 다른 하나의 클라이언트를 대신해 줄 네트워크가 마땅치 않군요. 하지만, 마침 이전에 ^^ 만들어 두었던 아마존 EC2 무료 Windows 서버 Virtual Machine이 생각났습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 아마존 EC2에 새로 추가된 "1년 무료 Windows 서버 인스턴스"가 있다는데, 직접 만들어 볼까요? ^^ ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1224'>http://www.sysnet.pe.kr/2/0/1224</a> </pre> <br /> 이렇게 해서 3군데의 네트워크를 확보하고, 서버/클라이언트는 다음과 같이 정했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 회사 컴퓨터: UDP 서버 집 컴퓨터: Hole Punching을 하게 되는 클라이언트 A 아마존 가상 컴퓨터: Hole Punching을 하게 되는 클라이언트 B </pre> <br /> 아마존 가상 컴퓨터 역시 Private IP를 가지고 있습니다. 과연, 아마존 가상 컴퓨터와 집에 있는 컴퓨터 간에 UDP 통신이 가능할까요? 와~~~ 저도 궁금해집니다. ^^<br /> <br /> 우선, <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=684&boardid=331301885'>첨부 파일을 내려 받아서 빌드하면 다음의 2가지 프로젝트</a>가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HolePunchServer: UDP 서버 HolePunchClient: Hole Punching을 이용해 서로 통신할 UDP 클라이언트 </pre> <br /> 빌드한 후, HolePunchServer를 회사 컴퓨터에서 실행시킵니다. 기본값이 12800 포트로 대기하고 있기 때문에 만약 여러분들의 환경에 맞지 않다면 변경해 주시면 됩니다.<br /> <br /> <img alt='hole_punch_5.png' src='/SysWebRes/bbs/hole_punch_5.png' /><br /> <br /> 그다음, 각각 아마존과 집 컴퓨터에서 HolePunchClient를 실행시킵니다. (빌드하기 전에, app.config 안의 SERVER_HOST, SERVER_PORT 값을 자신이 테스트 하는 UDP 서버의 값으로 바꿔주어야 합니다.)<br /> <br /> <img alt='hole_punch_6.png' src='/SysWebRes/bbs/hole_punch_6.png' /><br /> <br /> 이제 양쪽에서 Connect 버튼을 누르면 회사의 UDP 서버로 데이터를 전송합니다. 이 과정에서 UDP 서버는 양쪽 컴퓨터의 Public IP와 포트를 취합하게 되고, 서로 통신을 할 양쪽의 IP/Port 정보를 클라이언트 측에 내려줍니다. <br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='hole_punch_7.png' src='/SysWebRes/bbs/hole_punch_7.png' /><br /> <br /> 여기까지야 뭐... 일반적인 Server / Client 통신일 뿐이니 신기할 것이 없습니다. 이제, 분배받은 상대방 NAT 장비에 지정된 포트로 데이터를 전송하기 위해 "Send" 버튼을 누릅니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='hole_punch_8.png' src='/SysWebRes/bbs/hole_punch_8.png' /><br /> <br /> 오~~~ 정말, 서로의 NAT 장비에 뚫어놓았던 UDP 서버에 연결된 포트 번호로 데이터를 전송하니 양측에서 데이터를 송/수신하고 있습니다. 게다가 서버 측에서 계속 내려주는 LIST 정보까지 받는 걸로 봐서 공유기가 다수의 컴퓨터에서 오는 데이터를 정상적으로 UDP 클라이언트에 전달해 주는 것을 알 수 있습니다.<br /> <br /> 직접 해보니.. 정말 신기하군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 아쉬운 점이라면, 이러한 Hole Punching이 100% 되는 것은 아닙니다. <a target='tab' href='http://www.gamedevforever.com/47'>원문에도 언급하고 있지만</a> 해외 서비스 통계상으로 80% 이상의 성공율을 보이고 있다고 합니다. 또한 공유기의 특성에 따라 매핑된 IP:포트 테이블을 3초만에 지우는 경우도 있다고 하고 지원되는 NAT의 다양한 방식으로 인해 (Full Cone, Restricted Cone, Port Restricted Cone, Symmetric) 대응도 다를 수 있다고 합니다.<br /> <br /> 물론, 안 되는 경우를 위해서 UDP 서버 측에서 직접 메시지 전달을 대행해 주는 코드를 만들어야 겠지만... 그래도 80% 정도의 클라이언트에 대한 네트워크 통신 부하를 서버로부터 없앨 수 있다는 것은 대단한 장점입니다.<br /> <br /> 좀 더 기술적인 부분에 대해서는 다음의 글을 참고하십시오.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Peer-to-Peer Communication Across Network Address Translators ; <a target='tab' href='https://docs.google.com/View?id=dc65vhw7_118g5dp5xf3'>https://docs.google.com/View?id=dc65vhw7_118g5dp5xf3</a> Hole Punching ; <a target='tab' href='http://sweeper.egloos.com/2431396'>http://sweeper.egloos.com/2431396</a> </pre> <br /> 참고로, 이 글에서는 UDP만 설명했지만 TCP도 가능합니다. 게다가 이 글에 첨부한 프로젝트는 간단하게 테스트를 위해 만들어본 코드라서 여러분들의 환경에 맞게 변경해야 하지만, 다음의 공개 C# 소스 코드를 이용하시면 그런 부담을 덜을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Introduction to SharpSTUNT ; <a target='tab' href='http://sharpstunt.codeplex.com/'>http://sharpstunt.codeplex.com/</a> </pre> <br /> 이렇게 재미있는 기법을 게임 프로그래머들은 이미 다 알고 있었군요. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1586
(왼쪽의 숫자를 입력해야 합니다.)