인프런 커뮤니티 질문&답변

literate_t님의 프로필 이미지
literate_t

작성한 질문수

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

SendBuffer Pooling

오류 원인을 찾기가 어렵네요.

작성

·

56

·

수정됨

0

안녕하세요.

거의 하루종일 보고 있는 문젠데요.

클라이언트에서 에코해줄 때 강사님은 OnRecvCompleted() 함수 안에서 인자로 받은 length가 아니라 sizeof send_buffer를 사용하고 계셔서 오류가 발생 안 했어요.

인자로 받는 length를 해주면 프로그램 구동하고 나서 얼마 안돼서 주고받는 데이터가 영원히 증가합니다. 물론 ASSERT_CRASH가 있기 때문에 메모리 할당해주는 부분에서 결국 멈추긴 합니다.

정확한 이유를 못 찾겠네요. 이리저리 중단점 찍어보는데, 한 가지 확실한 건 서버에서 WSASend() 해주고 클라에서 받을 때 갑자기 데이터 사이즈가 튑니다. 더미 클라에서 세션을 하나만 넣어줘도 재현돼요.

이럴 때 비동기 + 멀티스레드는 정말 헬이네요. ProcessSend()로 들어오는 sendBytes가 갑자기 26, 52..이렇게 되어버립니다. GSendBufferManager에서 반환하는 메모리 위치는 문제가 없어요. 어느 순간 데이터를 미친 듯이 이어 붙입니다.

image.png

위 이미지처럼 하나씩만 오고 가야 하는데(Hell, World!)
어느 순간 아래 이미지처럼 확 늘어납니다.

image.png

계속 누적돼요. 강사님 코드 거의 그대로입니다.

WSASend() 동작이 중첩됐나까지 의심하게 되네요. 그러나 송신 큐 부분에 락 걸고 이후 코드는 스택 변수라 강사님과 동일하게 진행됩니다.

void Session::RegisterSend()
{
  _send_event.Init();
  _send_event.owner = shared_from_this();

  // 보낼 데이터를 send_event에 등록
  // 레퍼런스 카운트 유지를 위해 SendEvent의 멤버변수를 이용한다
  {
    WRITE_LOCK;
    while (false == _send_queue.empty())
    {
	SharedSendBuffer send_buffer = 
        _send_queue.front();
	_send_queue.pop();
       _send_event.send_buffers.push_back(send_buffer);
     }
   }

  xvector<WSABUF> wsabufs;
  wsabufs.reserve(_send_event.send_buffers.size());
  for (SharedSendBuffer send_buffer : _send_event.send_buffers)
  {
    WSABUF wsabuf;
    wsabuf.buf = reinterpret_cast<char*>(send_buffer->Buffer());
    wsabuf.len = static_cast<LONG>(send_buffer->WriteSize());
  printf("wsabuf len: %d\n", wsabuf.len);
	wsabufs.push_back(wsabuf);
   }

  DWORD send_bytes = 0;

  int32 result = 
WSASend(_socket, wsabufs.data(), static_cast<DWORD>(wsabufs.size()), &send_bytes, 0, &_send_event, nullptr);
  int32 error = WSAGetLastError();
  if (SOCKET_ERROR == result && error != WSA_IO_PENDING)
	{
		HandleError(error);
		// release ref
		_send_event.owner = nullptr;
		_send_event.send_buffers.clear();
		_send_registered.store(false);
		return;
	}
}

 정상적인 에코 서버라면 주고 받는 데이터의 크기가 계속 일정해야 한다고 생각해서 테스트한 건데 고통의 시간을 겪고 있습니다..

참고로 GameSession에서 OnRecv 재정의할 때 1회만 브로드캐스트 해주고 있습니다.

int32 GameSession::OnRecvCompleted(BYTE* buffer, int32 length)
{
	// temp echo
printf("GameSession OnRecvCompleted len: %d\n", length);

SharedSendBuffer send_buffer = g_send_buffer_manager->Open(4096);
memcpy(send_buffer->Buffer(), buffer, length);
send_buffer->Close(length);

	
g_session_manager.Broadcast(send_buffer);	

return length;
}

답변 2

0

literate_t님의 프로필 이미지
literate_t
질문자

자문자답합니다.
더미 클라이언트에서 2인 이상의 세션을 넣었을 때만 해당 증상이 일어나는 걸 보고 고민고민하다가 발견했습니다. 아무리 봐도 코드는 문제 없었거든요.
이전 코드에서는 브로드캐스팅을 하지 않고 각각의 세션에게만 에코를 해주니까 클라이언트가 수신할 때 서버가 송신해준 것만 딱딱 받았어요.

여러 세션이 브로드캐스팅을 해주면, 서버가 여러 번 송신하는 데이터들이 세션에서 한 번에 받는 일들이 생기고 이게 누적되면서 눈덩이처럼 주고받는 데이터가 불어나더라고요.

이상입니다.

0

안녕하세요, 인프런 AI 인턴입니다. literate_t님, 비동기 네트워킹과 멀티스레드 환경에서 발생한 오류로 인해 어려움을 겪고 계신 것 같습니다. 이 문제는 특히 WSASend와 관련된 동작에서 빈번히 발생할 수 있는 복잡한 상황입니다.

귀하의 설명을 바탕으로 몇 가지 사항을 고려할 수 있습니다:

  1. sendEvent 객체 관리: sendEvent 객체가 잘못된 시점에서 해제되면 예상치 못한 동작이 발생할 수 있습니다. 이 객체의 수명 주기를 관리할 때, 적절한 타이밍에 객체가 해제(Release)되고 다시 필요 시 새로 할당될 수 있도록 확인해야 합니다.

  2. 길이 확인: 코드를 검토한 결과, WSASend가 데이터 길이를 처리할 때 sizeof(send_buffer)가 사용되어야 하며, 클라이언트에서 의도한 길이로 데이터가 전송되지 않는 것 같습니다. 이 부분을 좀 더 면밀히 살펴보실 필요가 있습니다.

  3. 큐에서 데이터 이어붙이기: 데이터가 잘못 이어 붙여지는 경우, _send_queue에 잘못된 상태에서 데이터가 쌓이다가 중복 전송되는 경우를 확인해 보셔야 할 것 같습니다.

비슷한 주제의 토론에서 유사한 문제를 겪은 다른 사용자들의 답변이 도움이 될 수 있습니다:

  • sendEvent 객체가 해제되어 버립니다.
    설명: 클라이언트와 서버 간의 송수신 문제를 다룬 질문으로, 메모리 관리가 주요 논점입니다. refCount 관련해 체크하라는 답변이 있습니다.

  • WSASend 관련 처리 문의
    설명: WSASend의 부분 성공 처리에 관한 질문으로, 실제 전송이 실패할 때 안전하게 처리하는 방법을 다루고 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

literate_t님의 프로필 이미지
literate_t

작성한 질문수

질문하기