꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by denoil


안녕하세요. 처음 이렇게 글을 올리게 됩니다.
저는 네오플에 다니고 있는 프로그래머 denoil 입니다.
다들 실력이 쟁쟁하신 분들이라서 제가 이런 글을 올려도 되나 하는 망설여지는데 개발자들끼리 공유하는 것은 좋다고 생각하여 이렇게 글을 올려봅니다.^^

 제가 오늘 발표할 내용은 홀펀칭 입니다. 현재 P2P 게임을 만드시는 분들께서는 모두들 아는 내용이시겠지만 처음 P2P를 접하거나 만드시게 되는 분들께는 도움이 되지 않을까 하여 이렇게 남겨 봅니다. 제가 지금부터 하는 말들은 실전에서 사용 했던 방법을 근거로 이야기 하는 것이지 네트워크 이론을 막 신경써서 하는것이 아님을 미리 말씀드립니다.

 일단 P2P를 사용하는 가장 큰 이유는 역시 빠른 속도 겠지요. 패킷이 서버를 거치지 않고 Peer에게 바로 전송이 되니 그만큼 빠른 것도 없겠지요.
방법에는 TCP를 사용할 수도 있고 UDP를 사용할 수도 있겠습니다. TCP를 사용한다고 P2P가 아닌건 아니지 않습니까? Peer to Peer이기 때문에 서로 직접 연결만 되어 있으면 되는 것 입니다.
허나! TCP를 사용하지 않는 이유는 UDP가 TCP보다 빠르기 때문 입니다. TCP는 패킷을 보장하기 위해 헤더가 더 붙고 뭐 순서를 보장하기 위해 block 하기도 하고 이것 저것 한다고 합니다.(자세한 내용은 책을 참고 하세요) 
UDP는 패킷을 보장하진 않지만 빠르다는 장점이 있습니다. 패킷 손실률이 있어 이부분은 직접 프로그래머가 제어를 해줘야 하는 단점이 있지만 어찌됐든 일반적으로 UDP는 TCP보다 빠르다고 합니다. (중국에서는 UDP보다 TCP가 더 빠르다는 이야기가 있습니다.) 
그래서 최신 P2P게임에서는 UDP를 많이 사용 합니다.

 Peer들 간의 통신을 하기 위해선 서로의 IP, PORT에 패킷을 전송하면 그만 입니다. 그러나 우리는 하나의 고정 IP를 가지고 공유기나 방화벽의 NAT( Network Adresss Translation )라는 장애물을 만나게 됩니다. NAT를 거치게 되면 사설 IP( 192.168.0.2 이런식의 IP )가 지급? 되게 됩니다.


 실제 공인IP 가 아니지요. 사설망에서만 서로간의 통신이 가능한 가상 IP라고 생각 하시면 됩니다. 그래서 NAT단에서 방화벽으로 IP, PORT등이나 인터넷을 막아버리면 아예 인터넷이 불가능 한 그런 상황도 만들어낼 수도 있고 인터넷 연결 없이 사설망에서만 돌아가게 인트라넷을 만들기도 합니다. 사설 IP로 서로 통신을 하게 되면 같은 NAT안에서의 통신은 가능 하지만 서로 다른 NAT에서는 당연히 통신이 되지 않습니다. NAT안에서만 존재하는 가상 IP이기 때문 입니다.
NAT의 종류에도 여러가지가 있습니다.
1. Full Cone NAT
2. Restricted Cone NAT
3. Port Restricted Cone NAT
4. Symmetric NAT
이런것들이 있다고 합니다. 어떤 NAT는 먼저 패킷을 쏴서 포트를 뚫어놔야 하고 어떤 NAT는 먼저 패킷이 와야 포트가 뚤리고 뭐 어떤 NAT는 어쩌구 저쩌구해서 저쩌구 하고 제가 오늘 여기서 설명 드릴 것은 저런 NAT 타입에 대해서는 전혀 모르셔도 됩니다. 왜냐하면 저도 모르니깐요. :D
모로 가도 서울만 가면 된다고 합니다. 이제부터 실전에서 배워보는 홀펀칭에 대해 알아보기로 하겠습니다. 이전 회사에서나 현재 회사에서나 모두 보니 홀펀칭의 방법은 동일 했습니다.

 지금까지 설명드리지 않은 홀펀칭! A4 용지 파일을 정리할때 구멍을 뚫는 펀칭도구를 홀펀칭이라고 부르지요? 말그대로 NAT로 인해 막혀있는 홀( 포트? )를 네트워크 패킷이 지나갈 수 있게 뚫는 것입니다.
방법에는 여러가지가 있습니다. 위에 설명드린 것처럼 누가 먼저 보내고 받고도 있고 포트 번호의 구간을 정해 놓은 후에 하나씩 시도 해보는 방법( 예전 기억엔 그렇습니다. -_- )도 있습니다.
하지만 여기서 포트번호는 한개로 고정 하겠습니다. 포트번호는 임의로 정하되 방화벽으로 막혀 있지 않는 포트 번호를 선택 합니다.
현재 저희 게임에서 사용하고 있는 방법이고 문제 없이 잘 사용 하고 있는 방법 입니다.
그리고 포트 번호 비교를 통해서 현재 어떤 NAT환경인지 체크할 수 있는 내용도 있지만 현재 주제가 길어 질 수 있으므로 생략 합니다. 

간단하게 3단계로 정리 해 보겠습니다.

1. 사설(private) IP로 패킷을 전송해 본다.
2. 안되면 공인(public) IP로 패킷을 전송해 본다.
3. 그래도 안되면 어쩔수 없이 Relay Server로 패킷을 전송해 대신좀 전송해 달라고 한다.

( 여기서 릴레이 서버는 패킷을 중계 해주는 서버입니다. 클라이언트로 부터 받은 패킷을 다른 클라이언트에서 전송만 해주는 역할을 담당하고 다른 일은 하지 않습니다. 지금까지 만난 프로젝트에서는 TCP를 사용했는데 전 회사의 다른 팀 게임에서는 UDP를 사용해서도 했다고 합니다. )

위의 세가지 입니다. 현재 나와있는 넷텐션의 ProudNet 같은 경우 다양한 테스트와 다양한 방법을 사용하여 홀펀칭을 하여 99%의 성공률 보인다고 합니다. ( 끼로님한테 들은 이야기 입니다. -_-)a 문제가 된다면 내용을 삭제 하겠습니다. ) 하지만 여기서 다루는 내용은 홀펀칭을 이용해서 99%의 성공율을 해주지는 못할 것입니다. 릴레이서버의 도움으로 99%까지는 가능 합니다. 기본적인 내용이고 현재 상용게임에서 사용하는 내용임을 거듭 강조 드립니다.

다시 돌아와서 천천히 방식을 전개해 보겠습니다. 위의 3단계를 시행하기 위해선 패킷을 보내려는 상대방의 사설, 공인 릴레이서버의 IP를 알아야 합니다. 그러려면 누군가는 나에게 상대방의 IP 정보를 알려 주어야 겠지요. 그것은 게임 서버가 역할을 하게 됩니다. ( 다른 서버가 해도 되는데 일단 게임 서버라고 하겠습니다. ) 게임 서버에게 나의 IP 정보를 통보하여 서버에서는 정보를 저장하고 있다가 해당 정보를 다른 유저에게 알리는 방식이 되겠습니다.

 이제 서버에게 통보를 할 정보를 수집해 보도록 하겠습니다. 처음 해야 할일은 자신의 IP를 알아내야 합니다. gethostname 함수를 이용하게 되면 자신의 IP를 알 수 있게 됩니다. 그러나 NAT환경에 있게 된다면 현재 나의 IP가 사설 IP 인지 공인 IP 인지 알 수 없게 됩니다. 그러면 공인 IP를 어떻게 알 수 있을까요? NAT환경 밖에 있는 서버는 알 수 있습니다. NAT환경 밖에 있게 되는 서버는 패킷을 받을때 어떤 IP로 왔는지 알 수 있기 때문입니다. 서버와의 통신을 한번 해야 합니다. UDP로 하도록 하지요. TCP로 하면 바인딩하고 커넥션하고 귀찮고 UDP로 간단하게 쏘고 받기만 하면 되는 일이니까요.

자 그러면 공인 IP를 알기 위해서 1바이트짜리의 빈 패킷을 서버로 보냅니다. ( 예를 들어서 빈 패킷일 뿐 제작하시는 분 마음대로 패킷에 내용을 채우셔도 상관 없습니다. )
그리고 서버는 패킷을 받았다면 받은 IP, PORT를 패킷내용에 실어서 패킷을 보내온 클라이언트에게 다시 전송을 해 줍니다. ( 이 과정에서 아마 일단 PORT가 열리는 것으로 알고 있습니다. 자세한 내용은..NAT 관련 문서를 찾아보아야 할 것 같습니다. )

만약 클라이언트가 일정 시간동안 서버로 부터 패킷을 받지 못했다면 다시 시도를 합니다. UDP는 패킷을 보장해주지 않기 때문에 손실이 되었을 수도 있기 때문입니다.
클라이언트는 패킷을 받게 되면 정보수집은 끝 입니다. 
사설IP, 공인IP 모두 알게 되었습니다. 릴레이서버IP는 처음 로그인할때 게임서버에서 받기로 하지요.
이제 수집한 정보를 게임서버에 로그인 한 후에 서버에게 보내주면 준비 끝 입니다.

위의 과정과 앞으로의 과정을 한눈으로 알아볼 수 있게 그림으로 표현 합니다.



내정보 보냈으니 서버에서는 데이터를 가지고 있겠고 각각의 Peer들은 상대방의 IP 정보를 알 수 있게 되었습니다. 사설 IP, 공인 IP, 릴레이서버 IP 모두 알 수 있게 되었습니다. 
이제 파티가 이뤄지기 시작하면 위의 정보를 가지고 처음 위에서 설명 했던 3가지를 시도 합니다.

 

일정 시간 동안 일정한 간격으로 Peer A는  Peer B에게 더미 패킷을 사설 IP로 전송 해 봅니다. 만약 Peer B가 해당 패킷을 받게 되면 상대방에게 받았다고 알려 줍니다. Peer A는  패킷을 받았다면 앞으로 사설 IP로 패킷을 보내면 됩니다.
그러나 일정 시간 동안 받지 못했다면 이번에는 공인 IP로 패킷을 보내 봅니다. 공인 IP로 쐈을때 다시 패킷을 돌려 받았다면 앞으로 공인 IP로 쏘면 됩니다.
일정 시간 동안 또 받지 못했다면 서로 NAT환경에서는 통신이 불가능 합니다. 결국엔 릴레이 서버를 이용해야 하는 상황이 발생하게 됩니다. ( 최대한 릴레이 서버와의 연결은 안붙는게 좋습니다. 한단계를 더 거치기 때문에 느려지기 때문이죠. 그리고 여러명이 붙게 되면 그만큼 릴레이서버에 부담을 주기 때문입니다. )

처음부터 간단하게 다시 정리 해 드리겠습니다.

1. 클라이언트는 gethostname으로 사설 or 공인 IP의 주소를 저장함.
2. NAT환경이 아닌 게임서버 혹은 스턴서버에게 dummy Packet을 보냄. 
3. 서버는 받은 패킷의 IP, PORT로 IP, PORT를 패킷 내용에 담아 클라이언트에게 보냄.
4. 클라이언트는 사설 IP, 공인 IP 모두 저장.
5. 클라이언트는 수집된 사설 IP, 공인 IP를 서버에게 보냄.
6. 파티가 이루어질 경우 서버는 각각의 Peer에게 상대방의 사설,공인 IP를 전송.
7. 각각의 Peer들은 일정시간동안 사설 IP로 패킷 전송.
8. 사설 IP로 패킷 전송 실패시 공인 IP로 패킷 전송.
9. 공인 IP로 패킷 전송 실패시 최후의 보루 릴레이서버에게 맡김.

참 쉽죠잉?

여기서 주의 할점은 서로 패킷통신이 가능해지더라도 일정 시간동안 패킷을 주고 받지 않으면 포트가 막힐 수 있다고 합니다. 그러니 일정 시간동안 패킷이 오가지 않았다면 더미 패킷 혹은 핑 패킷이라도 쏴주면서 포트를 유지 시켜주면 좋겠습니다.

현재 상용게임에서 쓰고 있는 방식입니다. 얼마전 해외 서비스 통계상으로 봤을때 80%이상의 P2P성공율을 보였고 나머지 안되는 NAT 환경은 릴레이 서버를 함께 사용할 경우 98~99%의 P2P성공율을 보이고 있습니다.

많은 P2P 게임을 만드신 분들은 모두들 아는 내용일 것이라고 생각합니다. 각각의 회사마다 방식은 모두 조금씩 다르겠지만 일반적으로 저 방식을 통해서 홀펀칭을 하는 것으로 알고 있습니다. 저는 전문적으로 네트워크를 공부한 입장이 아닌 클라이언트 프로그래머 입니다. 잘못된 내용이 있다면 따끔히 지적해주세요. 저도 소스만 보고 익히고 공부한 내용이라서 틀린 부분도 분명 있을 것입니다. ^^
아무쪼록 여러분들께 좋은 자료가 되었으면 하며 이만 물러 나겠습니다.
긴글 읽어주셔서 감사합니다.

-denoil-

댓글을 달아 주세요

  1. 이전 댓글 더보기