본문 바로가기

스크랩

홀펀칭

 카페 > CPPWorld | 오호호
 http://cafe.naver.com/cppworld/1026
 

 


홀펀칭에 대해서 앞에서 조금 이야기를 했지만,
홀펀칭에서 가장 중요한 역할은, 바로 Server이다.
Server에서 각 Peer들에게 다른 Peer들의 정보를 제공해 줘야 하기 때문이다.

뭐 홀펀칭을 구현 하는 방법은 아주 다양하다.
Server에 UDP 패킷을 보내어, Server에서는 Connect된 Socket의 IP및 Port정보와
UDP 패킷으로 넘어온 정보를 비교하여, NAT에 속해 있는지 아닌지를 판별하는 곳도
있다고는 하는데, ( 뭐 그 효율은 안해봐서 모르겠다. )

우리는 그렇게 복잡하지 않은 방법을 설명하도록 하겠다.

원리는 간단하다.
Server는 각 peer들의 ip와 port를 알고 있다.
이러한 정보를 각 Peer들에게 보내주고, peer끼리 통신을 시도한후,
그 결과 값만 서버에게 보내주면 된다.
그리고 서버는 peer간의 통신이 이루어지지 않는 peer들에게만
패킷을 보내어 알려주기만 하면 되는 것이다.

순서는 다음과 같다.

1. Server에선 다른 Peer들의 정보를 peer에게 보내줌.
2. 제공 받은 다른 Peer들에게 UDP패킷을 보냄.
3. 다른 Peer들에게 UDP패킷을 받았다면 해당 peer와는 통신이 되었다고 판단.
4. 통신이 되지 않은 Peer들의 정보를 Server에게 보냄.
5. Server에서는 각 peer들의 통신이 되지 않은 peer들의 list를 작성.
6. 작성된 list를 토대로, 통신이 되지 않은 peer들의 정보를 각 peer들에게 보냄.


즉, 홀펀칭 자체는 크게 6단계로 나뉘며,
한가지씩 설명해보도록 하겠다.
그전에, 대기방에 총 4명의 User가 접속해 있다고 치고,
각 유저의 상황은 다음과 같다.

A. NAT에 속함.
B. NAT에 속하지 않음.
C. NAT에 속하지 않음.
D. NAT에 속하지 않음.

1. Server에선 다른 Peer들의 정보를 peer에게 보내줌.

네명의 Peer들에게 서로 다른 Peer들의 정보를 보내준다.
즉,

A 에게는 B, C, D
B 에게는 A, C, D
C 에게는 A, B, D
D 에게는 A, B, C


2. 제공 받은 다른 Peer들에게 UDP패킷을 보냄.

정보를 받은 Peer들은, 각기 제공 받은 peer들에게
UDP패킷을 보내어 자신의 존재를 알린다.

한 1초에서 2초 동안 패킷을 보내며 패킷을 받으면서
홀펀칭을 진행한다.

3. 다른 Peer들에게 UDP패킷을 받았다면 해당 peer와는 통신이 되었다고 판단.

보내면서 대기하던 중에 패킷이 도착하면,
자신이 가지고 있는 리스트에 있는 user인지를 검사하고,
없다면 잘못된 패킷으로 간주. 리스트에 있다면 해당 Peer와는 통신이 되었다고,
체크 하도록 한다.

보통 1초에 5~15번 사이로 보내는 것이 무난.

단, 이때 모든 Peer들에게 패킷을 받았다고 해서 절대로,
보내는 것을 중단해서는 아니된다.
나는 패킷을 받았지만 상대방은 받지 못한 상황이 될 수도 있기 때문이다.

4. 통신이 되지 않은 Peer들의 정보를 Server에게 보냄.

주어진 시간동안 통신을 시도하고,
리스트를 돌면서 체크 되지 않은 peer들의 정보를,
server에게 보낸다. 이때, 각 peer들의 고유 아이디등을 보내는 것이 제일 무난할 것이다.


5. Server에서는 각 peer들의 통신이 되지 않은 peer들의 list를 작성.

위의 상황에서 예상되는 결과는 다음과 같다.

A.  => B, C, D에게 패킷이 오지 않음
B.  => 오지 않은 Peer는 없음 ( A, C, D에게 패킷이 왔음 )
C.  => 오지 않은 Peer는 없음 ( A, B, D에게 패킷이 왔음 )
D.  => 오지 않은 Peer는 없음 ( A, B, C에게 패킷이 왔음 )


이때, 게임이 진행될 때,
A는, B, C, D 에게 UDP패킷을 보내고,
B, C, D는, A를 제외한 peer들에게 UDP패킷을 보내며,
A에 대해서는 릴레이 서버를 거쳐서 보내게 해야 할 것이다.

그렇기 때문에 서버에선 리스트를 받은 후 새로운 리스트를 작성해야 한다.
해당 peer가 준 리스트는 보낼수 없다는 정보가 아닌 받을 수 없다는 정보기 때문에,
이를 보내지 않아도 된다는 리스트로 바꿔야 하기 때문이다.

보내도 받지를 못하는게 peer에서 보내는 것은 낭비이기 때문이다.
또한, 서버로부터 온 패킷을 중계 할때도, 받지 못한 peer에게만 주어야
서버에서의 낭비도 줄테니 말이다.

즉, 위의 리스트를 아래와 같이 바꾸어야 한다.

A. => 보낼 수 없는 Peer는 없음
B. => A에게 보낼 수 없음.
C. => A에게 보낼 수 없음.
D. => A에게 보낼 수 없음.

방법은 간단하다.
각 Peer의 리스트를 돌면서 패킷이 오지 않은 peer의 새로운 리스트에 자신을 추가하면된다.

// 해당 Peer를 검사.
for( int i = 0 ; i < GetRecvListCount() ; i++ )
{
  GetRecvPeer( i )->AddNode( this );
} //for

이렇게 각 Peer들에게 실시하면,
위의 리스트를 작성 할 수 있다.

6. 작성된 list를 토대로, 통신이 되지 않은 peer들의 정보를 각 peer들에게 보냄.

위에서 작성된 리스트에 한명이라도, Node가 존재하게 되면,
서버는 해당 Peer에게 서버에 패킷을 보내도록 패킷을 보낸다.

그리고 이후에 해당 peer에게 패킷이 오게 되면,
위의 리스트에 등록된 peer들에게만 패킷을 주면 완료.



위와 같이 간단한 상황이 아닌, 좀더 복잡한 상황에 대해서 검사해 보자.

A. NAT에 속함
B. NAT에 속함 ( A와 같은 NAT )
C. NAT에 속하지 않음
D. NAT에 속함 ( A와 같지 않은 NAT )

먼저 위의 경우 예상되는 첫번째 리스트.

A. => C, D로부터 패킷이 오지 않음 ( B로 부터 패킷을 받음. )
B. => C, D로부터 패킷이 오지 않음 ( A로 부터 패킷을 받음 )
C. => 오지 않은 패킷은 없음 ( A, B, D 로 부터 패킷을 받음 )
D. => A,B, C로부터 패킷이 오지 않음

그리고 서버에서 재 작성된 두번째 리스트.

A => D에게 패킷을 보낼수 없음.
B => D에게 패킷을 보낼수 없음.
C => A, B, D에게 패킷을 보낼 수 없음.
D => A, B에게 패킷을 보낼수 없음

이때, 게임중에
A는 B, C에게 P2P패킷을 보내며,
B는, A, C에게 P2P패킷을...
C는, 서버에게 패킷을...
D는, C에게만 패킷을 보내게 된다.

이때, A, B, C, D는 모두 서버에게 패킷을 보내며,
서버에선

A에게 패킷이 왔을 경우 -> D에게 패킷을 전달
B에게 패킷이 왔을 경우 -> D에게 패킷을 전달
C에게 패킷이 왔을 경우 -> A, B, D에게 패킷을 전달
D에게 패킷이 왔을 경우 -> A, B에게 패킷을 전달

을 하게 된다.
이러한 상황에서도 무난하게 통신이 이루어짐을 알 수 있다.

원문 : http://narew.net/blog/36