서론
졸업작품 프로젝트를 하면서 서버가 가끔씩 죽는 현상이 있었다.
이 현상은 부하가 적든 크던 어떤 메시지를 보내던 낮은 확률로 갑자기 죽는 현상이었다.
또한 클라이언트 측에서 수신할 때 패킷이 제대로 받아지지 않는 현상도 있었다.
분명 패킷의 손상은 L4에서 받을 때 체킹이 내부적으로 된다고 하였는데 말이다.
이 두개의 오류가 공존해서 어떻게 해야 할까, 감도 안 와서 무작정 디버깅을 열심히 했었는데 디버깅을 하다 보니 알게 된 신기한 일이 있었다.
그것은 바로 "패킷이 나눠져서 들어올 수 있다"는 것이었다.
패킷의 분리 현상
RCV_BUF 옵션(수신 버퍼의 최대 크기)을 100으로 설정했다고 가정하자.
그리고 메세지의 헤더에다가 메시지의 크기를 int형으로 담았다고 가정하자.
그럼 정상적인 패킷은 아래와 같이 들어올 것이다.
버퍼를 넘어서면 어떻게 들어올까?
수신 버퍼를 넘어섰다면 recv를 한번 더 해서 끊어진 데이터를 받아와야 한다.
이렇게 되면 패킷을 파싱 할 때 문제가 생기기 때문에 잘린 패킷을 delay하고 다음 패킷 앞에 합쳐줘야 한다.
이상한 패킷의 분리 현상
하지만 과연 수신 크기를 넘어선 패킷만 절단될까?
그냥 멀쩡한 크기의 패킷도 절단되어 들어오는 경우가 있다.
왜 지정한 버퍼의 크기보다 작은 패킷이 잘려서 들어오는지는 잘 모르겠다.
아마도 서버 또는 클라이언트가 메시지를 급격하게 주고받을 경우 패킷이 절단될 확률이 높아지는 것 같다.
어쨌든 이 현상을 보면 이 패킷의 병합처리는 필수불가결적인 요소인 것은 분명하다.
나는 이 패킷의 절단을 인지하고 합쳐주는 로직을 졸작을 하면서 이렇게 구현했다.
패킷 Delay, 패킷 합치기 구현
bool CTCPReceiveProcessor::ReceiveData(std::shared_ptr<CPlayer>& player, char* recvBuf, int receiveLen)
{
// 딜레이된 버퍼가 있는지 확인하고 있다면 꺼내서 현재 버퍼에 합쳐준다.
if (player->delayedTCPDataLen > 0) {
player->PopDelayData(recvBuf, receiveLen, true);
}
int cursor = 0, bufLen = 0;
FPacketStruct packetStruct;
while (cursor < receiveLen) {
// ENUM을 제외한 길이를 계산한다.
// cursor는 Int 이후의 배열을 바라보게 된다.
bufLen = IntDeserialize(recvBuf, &cursor);
// 수신량을 넘어서 데이터가 넘어온다면 남은 데이터를 저장하고 리턴한다.
if (cursor + bufLen > receiveLen) {
cursor -= (int)sizeof(INT32);
player->PushDelayData(recvBuf, cursor, receiveLen, true);
return true;
}
.......
위 함수는 TCP 메세지를 Receive 받았을때 해석하기위해 호출되는 함수이다.
Player의 구조체에는 넘쳐서 Delay되는 내용을 담도록 하였고,
이 함수에서 해석하기 전에 이전 ReceiveData에서 Delay된 내용이 있는 지 확인하여 꺼내주는 작업을 하고,
함수를 해석하다가 길이가 초과되면 Delay시켜주는 작업을 했다.
꺼내주고 연기시키는 함수는 아래처럼 구현했다.
// 해석전 연기된 데이터를 꺼내는 함수
int CPlayer::PopDelayData(char* buf, int& ref_len, bool isTCP)
{
// TCP UDP 통합처리를 위해 사용하는 변수.
char* targetDelayedData;
int* targetDelayedLen;
if (isTCP) {
targetDelayedData = delayedTCPData;
targetDelayedLen = &delayedTCPDataLen;
}
else {
targetDelayedData = delayedUDPData;
targetDelayedLen = &delayedUDPDataLen;
}
if (*targetDelayedLen == 0) return 0;
// 꺼내고 없애주기만 되는 경우?
if (*targetDelayedLen + ref_len < PACKET_BUFSIZE) {
// 기존 내용을 밀어낸다.
memmove(buf + *targetDelayedLen, buf, ref_len);
// 앞에다가 딜레이된 내용을 붙여준다.
memcpy(buf, targetDelayedData, *targetDelayedLen);
// 이전 내용은 없애준다.
*targetDelayedLen = 0;
// 새로워진 길이를 리턴.
ref_len = ref_len + *targetDelayedLen;
return ref_len + *targetDelayedLen;
}
// 합치면 버퍼보다 많을 경우
else {
char newBuf[PACKET_BUFSIZE];
// 수신받은 오버랩에서 짤리는 전 부분 (자르는 길이)
int preSplitLen = PACKET_BUFSIZE - *targetDelayedLen;
// 수신받은 오버랩에서 짤리는 후 부분 (잘린 후 길이)
int postSplitLen = (*targetDelayedLen + ref_len) - PACKET_BUFSIZE;
// 딜레이된 버퍼를 덮는다.
memcpy(newBuf, targetDelayedData, *targetDelayedLen);
// 기존 버퍼를 최대 패킷 사이즈 만큼 만든다.
memcpy(newBuf + *targetDelayedLen,
buf,
preSplitLen // 최대 패킷을 넘기지 않도록.
);
// 딜레이 오버랩을 다시 맞춰준다.
memcpy(targetDelayedData,
buf + preSplitLen, // 잘린 부분 이후부터
postSplitLen // 잘린 부분의 길이 까지
);
// newBuf로 버퍼를 다시 맞춰준다.
memcpy(buf, newBuf, PACKET_BUFSIZE);
ref_len = PACKET_BUFSIZE;
*targetDelayedLen = postSplitLen;
return PACKET_BUFSIZE;
}
}
// 해석하다 끊겨서 도착한것이 파악될경우 연기시켜주는 함수.
void CPlayer::PushDelayData(char* buf, const int& cursor, const int& recvLen, bool isTCP)
{
char* targetDelayedData;
int* targetDelayedLen;
if (isTCP) {
targetDelayedData = delayedTCPData;
targetDelayedLen = &delayedTCPDataLen;
}
else {
targetDelayedData = delayedUDPData;
targetDelayedLen = &delayedUDPDataLen;
}
// 이미 딜레이가 존재한다면 새로운 버퍼에다가 앞에는 남은 데이터 뒤에는 이미 존재하는 딜레이를 붙여 다시 만든다.
if (*targetDelayedLen > 0) {
// 잘릴 데이터 만큼 앞자리를 비워준다.
memmove(targetDelayedData + (recvLen - cursor), targetDelayedData, recvLen - cursor);
// 앞에다가 잘린 데이터를 넣어준다.
memcpy(targetDelayedData, buf + cursor, recvLen - cursor);
// 딜레이 오버랩을 교체해준다.
*targetDelayedLen = recvLen - cursor + *targetDelayedLen;
}
// 아니라면 남는것만 딜레이에 넣는다.
else {
memcpy(targetDelayedData, buf + cursor, recvLen - cursor);
*targetDelayedLen = recvLen - cursor;
}
}
'졸업 작품 > 서버' 카테고리의 다른 글
졸작서버 AWS 사용기 (0) | 2019.06.08 |
---|---|
CMake 사용기 (0) | 2019.06.08 |
C++ IOCP 서버 3. IOCP 구현 (작동부) (6) | 2019.05.16 |
C++ IOCP 서버 3. IOCP 구현 (초기화) (2) | 2019.05.15 |
C++ 오브젝트풀 (4) | 2019.05.13 |