티스토리 뷰
개요
패킷을 보낼 때는 해당 패킷의 내용과 관계없이 send함수를 호출해서 패킷을 보내기만 하면 된다. 하지만 패킷을 받을 때는 상황이 좀 다르다. 받은 패킷이 어떤 패킷이냐에 따라 호출할 함수 또는 수행할 연산 등이 달라지므로, 그 때 그 때 적절한 동작을 수행할 수 있도록 만들어 주어야 한다.
고민을 하던 중 cocos2d-x 엔진의 KeyboardListener가 생각이 났다.
KeyboardListener는 내 class의 instance를 등록시켜 놓으면 내 class에 있는 KeyboardListener함수로 callback이 되도록 구성되어있다.
덕분에 key event들을 한곳에서 처리하지 않고 필요한 객체에서 따로 처리할 수 있다.
SocketListener를 구현해 보자.
수신받은 패킷에서 꼭 필요한 부분(어떤 함수를 호출하는 패킷인지)을 분리해서 등록된 Listener함수로 넘겨주는 기능을 구현해보자.
Spider - SocketListener
네트워크 매니저인 Spider는 Listener들을 관리하기 위해 Node 리스트를 가진다.
* Node는 게임 내에서 사용되는 모든 오브젝트들의 최상위 클래스로 모든 오브젝트 클래스들은 이 Node를 상속받아야 한다.
WSAAsyncSelect로 FD_READ 이벤트를 받았다면 Spider::Broadcast()로 받은 패킷의 내용을 넘겨주며, Broadcast()는 패킷에서 필요한 부분을 분리해서 모든 Listener들에게 패킷을 알려준다.
SocketListener는 다음 같은 코드를 가능하게 해준다.
1 2 3 4 5 6 7 8 9 10 11 12 | // 게임을 위해 임의로 만든 Tank class void Tank::SocketListener(Packet packet){ switch(packet.func){ case MOVE: // move에 적절한 대응. break; case HIT: // hit에 적절한 대응. break; } } | cs |
Node class를 상속받은 임의의 class가 자기자신을 Listener로 등록하고 상속받은 SocketListener함수를 통해 socket event를 받을 수 있도록 하기 위해서 Spider class에 다음 코드들을 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // Spider.h class Spider{ private: std::list<Node*> _listeners; public: void AddSocketListener(Node* listener); /** Select * 우리 엔진에서는 socket을 하나만 열고 사용한다는 가정을 하고있으므로, * 어떤 socket에서 발생한 이벤트인지는 구별하지 않는다. **/ void Select(int); /** Recv * packet을 수신하여 모든 listener들에게 방송(broadcast) 한다. **/ void Recv(); }; void Spider::AddSocketListener(Node* listener){ _listeners.push_front(listener); } void Select(int msg){ switch(msg){ case FD_READ: Recv(); break; case FD_CLOSE: break; } } void Spider::Recv(){ int size = 0; Packet packet; size += recv(_socket, (char*)&packet, sizeof(packet), 0); // recv가 실패하면 종료. if(size < 0) return; // 바이트 순서를 바꿔준다. packet.func = ntohl(packet.func); packet.count = ntohl(packet.count); // broadcast to all listener for(std::list<Node*>::iterator it = _listeners.begin(); it != _listeners.end(); it++){ (*it)->SocketListener(packet); } } |
코드가 너무 길어져서 Recv()함수만 가지고 왔지만, udp통신을 위한 Recvfrom, SendTo 등 같은 원리로 동작하는 송수신 함수들이 있다.
Spider::Select는 누가 호출하는 것일까? 우리 엔진은 WSAAsyncSelect를 사용하기 때문에 윈도우 메세지 콜백함수로 이벤트가 발생한다.
이 이벤트를 받아서 사용할 것이다.
1 2 3 4 5 6 7 8 | LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){ switch(msg){ case WM_SOCKET: // WM_USER+1 Spider::Instance()->Select(WSAGETSELECTEVENT(lparam)); break; // ... } } | cs |
마침
c++과 winAPI만으로 게임을 만들다가, 네트워크 동기화 기능을 넣고 싶어서 socket에 대해 공부를 해 보았다.
생각보다 재미있어서 만들던 게임에 쉽게 적용할 수 있게 엔진 형태로 만들어보기로 했다. 엔진이라고 할 수 있을지도 의심스러운 모습이지만 오픈소스 게임 엔진인 cocos2d-x에서 스타일을 많이 가져와서 만들었고 나름 잘 사용하고 있는 엔진이다.
다음 글에서는 p2p게임에서 실제로 이 엔진을 사용해 위치와 속도 등을 udp로 동기화하는 예제 볼 수 있다.
'Programming > Socket' 카테고리의 다른 글
socket으로 네트워크 엔진 만들기 (2) - socket 생성 (0) | 2017.07.13 |
---|---|
socket으로 네트워크 엔진 만들기 (1) - Packet class 정의 (0) | 2017.07.11 |
socket : 기본 서버 프로그램 제작 (0) | 2017.02.02 |
socket : socket() (0) | 2017.02.02 |
socket : socket이란? (0) | 2017.01.30 |
- Total
- Today
- Yesterday
- C/C++
- 수학
- 운영체제
- rxswift
- scala
- 자료구조
- database
- SHADER
- JSP
- ue4
- mongoDB
- SOCKET
- 드라마
- swift
- 알고리즘
- 데이터베이스
- ios
- machine learing
- Git
- C
- Cocos2d-x
- 국내여행
- game
- DesignPattern
- C++
- Java
- Spring
- winsock
- SwiftUI
- OS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |