지난번에는 더미클라이언트를 강제종료했을 때 session이 소멸되지 않고 남아있는 문제를 해결했다.

테스트를 하다가 다른 문제도 발견했다.


문제

지금 채팅서버는 받은 데이터를 그대로 모든 클라이언트에게 다시 보내준다.

그러면 2명의 클라가 Hello 를 보내면 서버는 2명의 클라에게 2번씩 Hello를 보내니 더미클라 입장에서는 각 클라당 2번, 총 4개의 Hello를 recv 해야한다.

그런데 보내는건 2번만 보내는데 받을때는 6번을 받는다.


원인 파악

Recv 문제인가?

처음에 확인한건 더미클라이언트 측 Session의 _recvBuffer에 서버가 이전에 보낸 데이터가 출력 이후에도 남아있어서 그런가? 하고 확인해봤다. 

 

Session::ProcessRecv

_recvBuffer.OnWrite(numOfBytes);
SendBufferRef sendBuffer = make_shared<SendBuffer>(_recvBuffer.ReadPos(), numOfBytes);
_recvBuffer.OnRead(numOfBytes);
_recvBuffer.Clean();

if (dynamic_pointer_cast<ClientService>(_service.lock()))
{//더미 클라이언트 서비스라면.
	string str((char*)sendBuffer->GetBuffer(), sendBuffer->GetDataLen());
	Utils::LockPrint("recv from server : ", str);
	RegisterRecv();
	return;
}

recv 완료통지 이후에 실행되는 ProcessRecv에서 recvBuffer의 내부 포인터를 관리함수를 호출하는데,

OnWrite는 "데이터가 이만큼 쓰여졌음" 을 기록하고

OnRead는 "버퍼에서 데이터 이만큼 사용했음" 을 기록한다.

 

여기서 받은 데이터 길이와 recvBuffer에 남아있는 길이를 비교했는데, 다르면 이전 recv버퍼가 제대로 비워지지 않았다는 말이니까 이걸 확인하면 ProcessRecv 쪽 문제인지 확인이 가능하다.

_recvBuffer.OnWrite(numOfBytes);
SendBufferRef sendBuffer = make_shared<SendBuffer>(_recvBuffer.ReadPos(), numOfBytes);
Utils::LockPrint(_recvBuffer.DataSize(), ' ', numOfBytes);
_recvBuffer.OnRead(numOfBytes);
_recvBuffer.Clean();

recvBuffer에 "데이터 쓰여졌음" 을 기록한 뒤에 datasize와 numOfBytes를 출력하게 해봤다.

비교해봤는데 같다. 그러면 실제로 서버가 클라에게 너무 많이 보냈다는 말. recv쪽 문제는 아니고 아무래도 sendBuffer의 관리가 잘못된 것 같다.


Send 문제인가?

ProcessSend에서 문제를 확인했다.

정상적으로 Send가 이루어졌을 때 SendEvent를 Clear하지 않아서 생긴 문제... 생각보다 별거 아니었다.


해결

void Session::ProcessSend(int32 numOfBytes)
{
	if (numOfBytes == 0)
	{
		_sendEvent.Clear();
		RegisterDisconnect();
		return;
	}
	// 보낸 바이트 수 < 보내려 했던 바이트 일 경우 처리.
	if (numOfBytes < _sendEvent.GetWantSendBytes())
	{
		uint32 sendedBytes = numOfBytes;
		deque<SendBufferRef>& sendBuffers = _sendEvent.GetSendBuffers();
		while (!sendBuffers.empty())
		{
			if (sendedBytes >= sendBuffers.front()->GetDataLen())
			{
				sendedBytes -= sendBuffers.front()->GetDataLen();
				sendBuffers.pop_front();
			}
			else
			{
				BYTE* pos = sendBuffers.front()->GetPosPtr(sendedBytes);
				int32 dataLen = sendBuffers.front()->GetDataLen() - sendedBytes;

				SendBufferRef newone = make_shared<SendBuffer>(pos, dataLen);
				sendBuffers.pop_front();
				_sendEvent.PushFront(newone);
				RegisterSend();
				return;
			}
		}
		return;
	} else {
        _sendEvent.Clear();
        {
            lock_guard<mutex> lock(_m);
            if (_sendBuffers.empty())
            {
                _sendRegistered.store(false);
                return;
            }
        }
        RegisterSend();
    }
}

성공적으로 모든 데이터를 send했을 때 _sendEvent를 clear하는걸로 쉽게 해결됐다.

 

그리고 이걸 넣기 전에는 send작업을 한 다음에 터미널을 닫으면 또 sendEvent의 owner가 nullptr로 set되지 않아서 순환참조 문제로 session이 소멸하지 않는 문제가 있었는데, 이걸 넣으면서 그 문제도 해결됐다.

이제 클라이언트 2명이 서버로 메시지를 1개씩 보내면, 정상적으로 4개 돌아온다.

(A가 보낸 메시지를 A B에게 send, B가 보낸 메시지를 A B에게 send해서 4개가 맞음)

 

새로운 문제(?)

클라이언트가 4개일 경우

여전히 종종 메시지가 겹쳐서 오는데, 이건 정확히 말하면 문제는 아니고 send작업 시 session의 sendbuffers에 모아서 한번에 보내기때문에 어쩌다가 메시지가 쌓이면 모인 메시지가 한번에 보내지는 정상적인 상황이다.

 

단, 패킷 헤더를 만들어 어디서부터 어디까지가 하나의 메시지인지는 구분해야 할 필요가 있다. 그러니까 다음에는 패킷헤더를 만들어보자.


현재까지의 git 버전

 

Fix: ProcessSend에서 Clear 안하는 문제 수정 · Dodontak/Project_Island_GameServer@ac6c6f0

Session - ProcessSend에서 정상작동 시 SendEvent.Clear를 해줘야했는데, 안하고있어서 send가 중복으로 이루어지는 문제가 있었다. 해결 완료.

github.com

 

'프로젝트 > Project_Island' 카테고리의 다른 글

35. Protobuf 추가하기  (0) 2026.04.02
34. PacketHeader, PacketSession 추가  (0) 2026.04.02
32. 세션 소멸 안되는 문제 해결  (0) 2026.04.01
31. 더미 클라이언트  (0) 2026.03.31
30. Connect, Disconnect 구현  (0) 2026.03.31

+ Recent posts