-
IOCP게임서버 동기화 패킷처리프로그래밍/네트워크 프로그래밍 2016. 10. 12. 06:02반응형
윈도우로 c++게임서버를 만들고 api정도에서 움직임 처리를 패킷으로 처리하는 포트폴리오입니다.
클라이언트는 2개만 띄워서 테스트를 하였고 많은 패킷을 받아서 빠르게 처리를 하는 서버를
구현하는데 목적을 두었습니다.
IOCP이용해서 게임서버를 만들었습니다.
클라이언트 C++
서버 C++
Winsock
VisualStudio2015 ,IOCP,c/c++
NetLIS ServerManual
0. NetLIS 서버 프로그램 소개.
서버프로그램 NetLIS는 IOCP를 사용한 멀티 쓰레드 환경의 서버 프로그램입니다.
코드생성을 하실 때 다중쓰레드(/Mtd)를 사용하여 주십시오.
IOCP를 사용하였으므로 XP이하의 운영체제에서는구동이 불가능 합니다.
함수호출에 따라서 서버기능을 수행하기도하고 클라이언트 기능을 수행하기도 합니다.
매우 단순한 구조로 되어있으며, 확장성을 고려하여 최소한의 기능만을 집어넣었습니다.
-라이센스-
이 프로그램의 저작권은 팀 크리에이터서버팀에게 있으며 문의는 carrylose@naver.com으로 해주시기 바랍니다.(그렇지만 향후 2년간 제가 군대를 가므로 불가능합니다만…;;)
마음대로 수정/삭제 하셔도 무방합니다만, 코드의 맨 위쪽의 주석은 삭제하시면 안됩니다.
상업적 사용을 금합니다.
1. 사용 준비
l 이름공간
Using namespace NetLIS;
NetLIS라는 이름의 이름공간을 사용합니다.
모두 사용하시려면 위의 코드를 넣어주십시오.
l 객체의 선언
INetLIS* g_pNetLIS = NULL;
INetLIS라는 객체의 포인터를 선언합니다.
이 포인터를 사용하여 서버의 모든 조작(서버 시작, 서버종료, 패킷 전송 등)
2. 객체 생성과 파괴
*생성
g_pNetLIS = createNetLISDevice();
createNetLISDevice()함수를 호출하여 객체를 생성과 초기화를 합니다. 객체의 포인터는 위의 INetLIS를 사용합니다.
*파괴
g_pNetLIS->drop();
drop() 함수를 호출하여 객체를 파괴합니다.
이것으로 모든 쓰레드 종료, 메모리 삭제등을 모두 수행합니다. 프로그램 종료시 반드시 호출하여 주십시오.
3. 본격적인 사용
이 프로그램은 함수 호출에 따라서 서버프로그램이 되기도 하고 클라이언트 프로그램이 되기도 합니다.
객체의 생성과 초기화를 완료한 후 다음 함수를 호출하여 주십시오.
g_pNetLIS->CreateServer();
서버의 생성에 성공하면 TRUE, 실패시 FALSE를리턴합니다.
이 함수를 호출하면 해당 어플리케이션은 서버로 동작합니다. 호출즉시 IOCP객체의 생성, 접속 수락 쓰레드를 깨워서 기동시킵니다.
서버/클라이언트를구분하는 플래그 (이름공간 NetLIS 안의 전역 변수 g_bIsMaker)가 TRUE가 됩니다.
g_pNetLIS->ConnectServer(“127.0.0.1”, 9200);
서버에 접속하는 함수 입니다.
접속에 성공하면 TRUE, 실패시 FALSE를리턴합니다.
첫번째 인수는 서버의 IP, 두 번째 인수는 포트번호(9200번으로고정)를 넣어주시면 됩니다.
4. 패킷의 송신/ 수신
모든 준비가 끝났으므로 패킷을 송/수신하는 과정에 대해서 알아보겠습니다.
NetLIS프로그램은 각 클라이언트들을 Index를 사용하여 구분합니다.
인덱스는 서버에 접속하는 즉시 부여받습니다.
0번은무조건 마스터(서버)이며,접속 순서대로 1번 2번 3번… 이 됩니다.
서버와 접속이 해제되면 새로운 접속자는 해당 인덱스를 다시 사용할수있습니다.
- 패킷의 생성
패킷을 전송하려면 패킷을 일단 만들어야 합니다.
어떤 데이터를 보내고 받을 건인지를 생각 한 후에, 해당 데이터에 알맞은 이름으로
Structlist.h 파일에 구조체(클래스도 상관없습니다)를 만들어줍니다.
예)
Struct sItemPacket
{
SHEADERsHeader;
Int nItemIndex;
Int nUserIndex;
};
첫줄의 SHEADER sHeader;는 패킷의 헤더 입니다.
반드시 제일 첫줄에 써주셔야 합니다.
혹은 상속을 받으셔야 합니다.
이 사항을 지키지 않을 경우 정상적으로 작동되지 않습니다.
이 아랫줄에 보내고 받을 원하는 데이터를 선언해주십시오.
NetLIS 프로그램은패킷을 WorkerThread에서 수신 합니다.
따라서 수신된 패킷의 처리를 WorkerThread에서 바로 해주시거나,
혹은 main쓰레드와 공통적으로 사용하는 전역 변수를 만들어 처리하는 방법이 있습니다.
두 번째 방법을 사용하시려면 Structlist.h에sItemPacket(혹은 독자가 만든 패킷)을 전역으로 선언해 주십시오.
다음으로 패킷을 선언하였으니 이제 남은건 송신과 수신입니다.
먼저 송신하는 방법은 다음과 같습니다.
1. iNetLIS.h / NetWork.h 에 적절한 이름으로 함수를 만듭니다.
현재 예제의 경우 SendItemPacket() 정도가 적당하겠습니다.
사용하는 인자는 위에서 선언한 패킷 구조체 형을 사용하거나
전송에 필요한 정보를 사용하시면 되겠습니다.
Ex)
Void SendItemPacket ( sItemPacket& sData);
2. 함수의 구현
전송시 서버와 클라이언트의 경우 전송 대상이 다릅니다.
간단하게 if문을 사용합니다.
if(g_bIsMaker)// 서버로 동작하는 경우
{
for(int i=1; i<10; i++) // 전송대상 : 1~10까지의 인덱스를가진 소켓(클라)
{
if(m_pClientInfo[i].m_bJoin == TRUE)// 전송조건 : 해당 인덱스가 접속및 연결되어있는 경우
{
g_pPacketProcess->MakePacket(&m_pClientInfo[i],(char*)&data, sizeof(SUNIT_INFO), 0, GAME); // g_pPacketProcess객체를 사용하여 패킷을 생성한다
//첫번째 인수는 소켓, 두번째는 보낼 데이터, 세번째는 데이터의 사이즈, 네번째는 패킷의 번호, 다섯번째는 보내는 논리적 위치( 로비, 룸, 게임 으로 구분)
m_pClientInfo[i].SendPacket();// 준비가 끝났으므로 해당 소켓에 전송한다.
}
}
}
Else // 클라이언트로 동작하는 경우
{
g_pPacketProcess->MakePacket(m_pChannelServerConnecter,(char*)&data, sizeof(SUNIT_INFO), 0, GAME);// 위와 마찬가지 이지만 대상이 서버이다.
m_pChannelServerConnecter->SendPacket();//서버에 패킷 전송.
}
전송 함수는 대략 이런식으로 전송합니다.
수신의 경우는 조금 복잡할지도 모르겠네요.
PacketProc.h 파일을 열어봅시다.
그 중에서 중간쯤에 나오는 주석을 보면
로비 패킷 프로세스 함수 / 룸 패킷 프로세스 함수 /게임 패킷 프로세스 함수
이렇게 써있는데 이게 뭔가 하면, 저 위에 전송 부분에서 패킷 생성함수의 마직막 인자에따라서 수신 시 다른 함수를 호출하게 되는 원리 입니다.
그러므로 해당 논리적 위치( 로비/룸/게임 )에 패킷 수신 함수를 만들어 줍시다.
여기서 주의할 사항은 다음 양식을 맞춰 주셔야 합니다.
// 로비
int PacketLogIn();
// 룸
int PacketConnectRoom(cConnection*pServer, cConnection* pClient, int index);
// 게임
int PacketCharacterInfo(cConnection*pServer, cConnection* pClient, int index);
위 함수들과 같이, 논리적 위치에 따라서 리턴형과 인수를 동일하게 해주셔야 합니다. 이름은 원하는대로 지어도 상관 없습니다.
수신 함수의 구현.
예제로는 캐릭터 정보 패킷을 사용하도록 하겠습니다.
//! ReceiveCharacter Information Packet
int CPacketProc::PacketCharacterInfo(cConnection*pServer, cConnection* pClient, int index)
{
{
//! 이건 동기화 코드 입니다.
cSync::Ownerlock(m_csInterger);
/*
이 코드를 통해서 메인쓰레드와 워커 쓰레드가공통으로 사용하는 전역 데이터 g_sUnitInfo의 내용
을 업데0이트 해줍니다.
여기서 업데이트된 데이터는메인 루프에서 렌더링할 때 사용하게 됩니다.
워커 쓰레드에서 수신 즉시처리하고 싶은 경우 이 코드 대신에 데이터 처리 코드를 넣어주시면 됩니다.
*/memcpy(&g_sUnitInfo,&m_szBuffer, m_nDataLen);
}
if(g_bIsMaker) . 서버(방장) 이라면
{
//!Send!
// ! 모든 클라이언트에게 받은 데이터를 그대로 가공없이 재전송 해줍니다.
for(inti=1; i<10; i++)
{
if(pClient[i].m_bJoin== TRUE && i != index)
{
//!make packet and send to it!
MakePacket(&pClient[i],(char*)&g_sUnitInfo, sizeof(SUNIT_INFO), 0,
GAME);
pClient[i].SendPacket();
}
}
}
//! 클라이언트라면 그냥 끝납니다.
return 0;
}
기본적인 구조는 다음과 같습니다.
패킷 정의
서버의 경우
패킷 전송(모든클라이언트에게) -> 클라이언트수신 -> 데이터 처리 -> 종료
클라이언트의 경우
패킷 서버에게 전송 -> 서버 수신 -> 수신한데이터 처리 -> 다른 모든 클라이언트에게 패킷 전송 ->다른 클라이언트가 패킷 수신 -> 다른 클라이언트가 수신된 패킷을 처리 -> 종료.
대략 이런 과정을 거치게 되는데 이 순서나 처리 과정을 미리 생각 하신 후 적절한 함수의 호출을 해주시면 에러 없이 될거라 생각됩니다.(아마도..-_-..;;)
반응형'프로그래밍 > 네트워크 프로그래밍' 카테고리의 다른 글
파일패치 서버 (1) 2016.10.12 html와 Node.js 웹소켓서버 (3) 2016.10.12 [포트폴리오]리니지 네트워크 (4) 2014.07.05 게임서버 만들기 3 (0) 2014.06.25 서버만들기 2 (0) 2014.06.25