지난번에 connect가 잘 되는지 테스트하기 위해 더미 클라이언트를 만들었다.
connect는 매우 잘 됐지만 이상하게 더미클라이언트 터미널을 닫으면 서버측에서 접속되어 있던 세션 중 일부만 닫히는 문제가 있어서 해결해보려 한다.
문제
우선 출력을 제대로 확인하기 Utils에 LockPrint를 만들었다.
#include <mutex>
class Utils
{
public:
/*...*/
template<typename... Args>
static void LockPrint(Args&&... args)
{
lock_guard<mutex> lock(m);
(cout << ... << args) << endl;
}
private:
static mutex m;
};
//cpp에서
mutex Utils::m;
Session::Session(SOCKET socket) : _socket(socket), _recvBuffer(BUFFER_SIZE)
{
Utils::LockPrint("Session ", _socket, " constructed");
}
Session::~Session()
{
Utils::LockPrint("Session ", _socket, " distructed");
if (_socket != INVALID_SOCKET)
SocketUtils::CloseSocket(_socket);
}

connect 성공 후에 wait를 걸어둔 사이에 더미클라이언트 터미널 닫은 상황.
확실히 생성된 세션보다 소멸된 세션이 더 적다.
원인
근데 클라이언트가 2개일때도 같은 문제가 생기길래 일단 2개로 놓고 디버깅을 하면서 원인이 무엇인지 파악해봤다.
이번에 vs를 쓰면서 처음으로 제대로 디버깅을 해봤는데 상당히 잘 되어있다. 특히 중단점에서 멈춘다음에 Ctrl + Alt + H 를 누르면 멀티스레드에서 스레드를 하나하나 멈춰놓고 한줄씩 코드를 실행시켜볼 수 있었는데 덕분에 정확한 원인을 찾을 수 있었다.
아무튼 원인은 이렇다.
지금 ProcessDisconnect를 할 때 접속한 다른 세션들에게 "유저 한명이 나갔습니다" 이렇게 브로드캐스트 하는데, 이 때 아직 Service의 sessions에서 erase되지 않은 세션들의 sendEvent의 owner가 set 되는데 이게 nullptr로 리셋 되려면 ProcessSend가 호출되어야한다. 그런데 이후로 ProcessSend가 호출되지 않아서 session의 sendEvent의 Owner도 리셋되지 않고, 그래서 session이 순환 참조 문제로 소멸되지 않는 거였다.
일단 누군가 나갔을 때 다른 모든 유저들에게 알리는 것 까지는 정상적인 동작이니 ProcessSend가 왜 호출되지 않는지 알아봤다.
if (SOCKET_ERROR == WSASend(_socket, wsaBufs.data(), static_cast<DWORD>(wsaBufs.size()),
OUT & numOfBytes, 0, (LPWSAOVERLAPPED)&_sendEvent, nullptr))
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
//TODO 적절한 처리
}
}
vs 디버깅툴을 사용해 확인해보니 위 코드 부분에서 WSASend를 호출할 때 실패한다. 원래도 논블로킹 비동기 함수니까 실패하지만, 에러 코드가 WSA_IO_PENDING 이 떠서, "비동기 정상종료" 를 알리는데, 이 경우엔 이미 일방적으로 닫혀있는 소켓이라 그런지 (서버측에서는 아직 소켓을 close하지 않았지만) WSA_IO_PENDING이 아닌 다른 에러가 떴다.
이 때 발생한 에러코드는 WSAECONNRESET 였고, 구체적인 에러 메시지는 아래와 같았다.
현재 연결은 원격 호스트에 의해 강제로 끊겼습니다.
해결
void Session::RegisterSend()
{
/*...*/
DWORD numOfBytes = 0;
if (SOCKET_ERROR == WSASend(_socket, wsaBufs.data(), static_cast<DWORD>(wsaBufs.size()),
OUT & numOfBytes, 0, (LPWSAOVERLAPPED)&_sendEvent, nullptr))
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
//TODO 적절한 처리
_sendEvent.Clear();
}
}
}
RegisterSend에서 WSA_IO_PENDING이 아닌 경우 _sendEvent의 owner를 nullptr로 리셋 해줬다.

이렇게 하니 세션 10개를 생성하고 더미클라이언트 터미널을 종료하여 비정상적으로 소켓을 닫아도 정확히 10개가 소멸된다.
현재까지의 git 버전
Fix: Session, IocpEvent 순환참조 문제 해결 · Dodontak/Project_Island_GameServer@f1c5e7e
Session - 모든 Register* 함수들에서 비동기 IO 작업 실패 에러코드가 WSA_IO_PENDING가 아닐 때 Event.Clean 호출해서 쉐어드포인터를 릴리즈 해줌. Utils - LockPrint 함수 추가. 출력 볼 때 마구잡이로 섞여 나
github.com
'프로젝트 > Project_Island' 카테고리의 다른 글
| 34. PacketHeader, PacketSession 추가 (0) | 2026.04.02 |
|---|---|
| 33. 서버가 send를 너무 많이 하는 문제 해결 (0) | 2026.04.02 |
| 31. 더미 클라이언트 (0) | 2026.03.31 |
| 30. Connect, Disconnect 구현 (0) | 2026.03.31 |
| 29. 멀티스레드 구현, Partial Send 문제 해결 (0) | 2026.03.30 |