저번 글에서 전통적인 두가지 패킷 처리 방법을 보았다
두 방법다 장점과 단점이 존재 하며 , 완벽한 방법은 없어 보인다
하지만 자연스럽게 합쳐질 수 있는 방법은 없을까?
안타깝게도 두 방법은 공존할수 없는 방법이다 , 하나는 컴파일타임 기반 코드이고
하나는 런타임에 기반코드이기에 공존해 쓸수가 없는것이다
솔직히 근본적으로 C++ 언어차원에서는 해결할 방법이없다
그래서 수많은 프로젝트에서 이 전통전인 방법들을 고수하고 있는게 아닌가?
이쯤 되고 보면 , 이렇게 된거 하나를 선택하고 단점을 줄일 방법을 연구하는게 빠를거 같다
"남들이라고 별수 있겠어?" 라고 속편히 생각하면 이게 가장 현명한 생각인것인 같다는 생각도
드는게 인지상정이다
하지만 구조체를 선택해도 가변적인 처리때문에 코드량 및 복잡도가 높아지고
스트림 처리를 선택하더라도 패킷이 복잡하고나 양이 많아지면 마찬가지로 코드량이
많아지고 복잡해지는건 매한가지다
"나와 내 동료들은 실수 따윈 하지 않아!" 라는 가정하더라도 결국 많은 양의 단순 노가다에 앞에는
장사가 없다. 노가다는 인간의 본성을 거슬리는 일이기때문이다
(공산주의가 망한 해묵은 이유를 설명하진 않아도 되겠지)
"그냥 몸으로 때워!"의 마인드를 가진 팀장이라면 , 팀원들이 생각하며 생산적인 일을
하기보단 노가다하는 시간이 많을것이다
(새벽2-3까지 매일같이 해도 답 안나오는 프로젝트가 아직도 기억이....)
게임 디자이너너 마저도 "단순 노가다는 죽기보다 싫다"며 프로그래머에게 자동화를 요구해
오는 광경은 어렵지 않게 볼수 있다
[ 심지어 맥스 스크립트로 자신의 작업을 자동화해버리는 디자이너도 있다 ]
결론적으로 말해 인간의 본성을 거슬르는 노가다로 프로젝트를 운영하게 되면
프로젝트가 제대로 돌아갈리가 없다
근데 잠시만 디자이너가 자동화?를 요구한다고?
갑자기 드는 생각으로 디자이너의 편의를 위해 자동화 처리를 해주면서 정작 왜 자신의 일인
코드는 자동화 시키지 못하는지 의아스럽기만 하다
굳이 변명을 하자면 C++언어밖에 모르는 입장에선 C++로 디자이너의 일은 자동화 해줄수 있지만
정작 자신은 C++언어의 문법에 묶여 C++ 언어에서 할수 있는거 밖에 생각할수 없지 않은가?
그래도 미약하지만 C언어의 표준 전처리기인 메크로를 써 조금이라도 더 편하게 쓸수 있지 않을까라는
생각이 든다
"오 메크로! 그럴듯한데?" 단순 반복작업을 어떻게든 줄여줄수 있는것처럼 느껴진다
메크로로 인해 코드가 어려워 보이겠지만 이 노가다만 줄일 수 있다면 영혼이라도 팔겠다는 심정으로
#define Oh_My_God_Packet ....
#define Damn_it ....
#define WASTE_OF_TIME....
이런 코드를 짜기 시작하지만 끝도 없이 정의해야 될것들이 생겨난다.
게다가 이건 내가봐도 외계어가 된 느낌이다 ,
메크로를 정의해 패킷 처리 노가다를 줄여보려 외계어 정의에 더 스트레스 받는 느낌이
이건 뭔가 아니다 싶다
역시 없쩔수 없이 노가다를 인정하겠다는 마음으로 패킷 처리 코드를 짜지만
, ctrl + V 를 한 코드에서 버그가 다시 발생하자 , 마음이 약해진다
그러는 문득! 실용주의 프로그래머에서 읽었던 자동화가 떠오른다
내용이 좋아 3-4번을 읽었으면서 막상 왜 그 자동화를 실천하지 못했을까?
[물론 그 책은 5년전에 읽었고 이 생각은 4년전부터 해왔다, 생각은 하고 있었으나 역시 실천이 T_T ]
실용주의 프로그래머 원칙
코드를 작성하는 코드를 작성하라
오오라 바로 이것인것이다
[신대륙을 발견한 콜롬버스의 달걀 깨기 처럼 C++ 문법의 한계를 넘어서는게 중요하다]
혹시 누군가 이런걸 만들어 놓지 않았을까 하는 마음에 구글에서 검색을 해보지만
C++에서 맘에 드는 방식으로 바로 쓸수 있는건 드물어 보였다
구글 코드에서 XML로 정의해 C++ 코드를 생성해 내는게 있었지만
XML로 복잡하게 정의해야 했고 , XML 정의가 직관적으로 바로 눈에 들어오진 않았다
그리고 그외 비슷한 것들을 보았지만 내맘에 쏙드는게 없었다
마치 fix_vector를 만들때 기분이랄까?
역시 직접 만들어야겠다는 생각에 프로토콜을 직관적인 구조체로 정의하고 그 코드를 파싱해서
stream 에 입출력하는 코드를 작성하는 생성기를 만들었다
그토록 원한 고전적인 패킷 처리 방법인 구조체 처리 방법과 스트림 처리의 장점을 동시에
가져갈수 있게 된것이다
분명 글 앞단에선 이것이 불가능하다면서 지금은 어떻게 가능하다고 하는 이야기일까?
다시 설명하면
전 처리 단계때 구조체를 보고 send recv 함수를 만들어 낸다면 (제대로 작성했따면)
컴파일 타임 체크 자체가 필요가 없고 , send recv 함수 내에는 구조체의 내부 데이터를
스트림에 담아 소켓 버퍼에 보내는 함수를 호출한다면 스트림의 장점을 다 취하는것이다
예를 들어 보겠다
// 파싱의 편의상 및 프로토콜이라는 직관성을 위해 struct 메크로로 재정의 했다
-----------------------------------------------------------------------
#define PT_INTERFACE struct
#define PG_VECTOR std::vector
// 이것이 패킷 정의다
PT_INTERFACE CS_LOG_IN
{
String id;
String password;
PG_VECTOR<String> itemList;
};
-----------------------------------------------------------------------
"이건 구조체 방법이 아닌가?" 싶은데 가변 크기를 지정하는 vector가 들어가 있다
vector를 봤을적엔 구조체 방법도 아닌것고 그렇다고 스트림 방법도 아닌거 같다
자 이걸 보고 만약 이런 코드를 생성한다면 어떨까?
void SendPacket(SOCKET socket,void* pBuffer,uint32 bufSize);
void SendPacket(SOCKET socket , const CS_LOG_IN& packet )
{
// 이 스트림 클래스엔 String과 Vector를 처리할수 있는 operator << 가 정의 되어 있다
PacketStream stream;
stream << packet.id;
stream << packet.password;
stream << packet.itemList;
SendPacket(socket,stream.GetBuffer(),stream.GetSize());
}
감이 있다면 바로 느꼈을것이다. 바로 이거라고....
구조체로 패킷을 정의하고 , 코드 생성기로 컴파일 타임체크의 역활 이상을 대신하며
스트림의 장점을 가지는 이런 코드 생성..... 아 내가 원한것이 바로 이런것인것이다
참고로
void SendPacket(SOCKET socket,void* pBuffer,uint32 bufSize) 이 함수는
은 선언만 되어 있고 구현은 없다
이유는 프로젝트마다 선택되는 소켓의 처리 방식이 다를수 있으며
암호화가 추가될수도 있기에 특정 코드의 종속성을 방지 하기 위해
어플리케이션 코드단에서 정의를 해줘야 하기때문이다
예를 들면
void SendPacket(SOCKET socket,void* pBuffer,uint32 bufSize)
{
//간단한 XOR 암호
XOR_Encode(pBuffer,bufSize);
// 현재는 간단히 버클리 소켓을 쓴다
send(socket,(char*)pBuffer,bufSize,0);
}
패킷 생성기가 유연하고 독립성을 가지려면 위와 같이 어플리케이션 종속적인 코드를
피해야 재활용 가능한 패킷 생성기를 이용할수 있게 된다
to be continue...
스트림처리 방식을 사용안해봐서 100% 공감하지는 않지만..
답글삭제앞으로 사용할 기회가 있다면 이렇게 해보고 싶군.
좋은 정보 감사!
-_-; 온라인게임을 안해본 너로써는 구조체 방법만 익혔을테니 도움이 될거다
답글삭제이참에 스트림 기법에 대해 이해한 계기가 되었으면 좋겠군
감사합니다. 많이 배우고 갑니다.
답글삭제