지금까지 서버코드를 짜면서 클라이언트를 딱히 고려하지 않았기때문에 뜯어고쳐야할 부분이 많다.

하나하나 고쳐가면서 클라이언트가 서버로 패킷을 보내고, 받을 수 있도록 만들어보자. 그리고 이번에 만든 클라이언트로 나중에 성능테스트도 할 수 있게 최대한 잘 만들어보자.


 

서버측 accept 과정 수정.

클라이언트가 connect를 하는 부분을 만들기 전에 서버의 accept부분을 살펴봤는데 문제가 있었다.

기존 accept과정 - (블로킹, 서버측에서 먼저 핸드셰이크 요청)

  1. listener에서 accept로 tcp/ip 연결 성공
  2. 서버가 SSL_accept 호출 (SslObject생성자에서 블로킹으로 실행됨)
  3. 핸드셰이크 완료시 소켓 논블로킹으로 변경 후 epoll에 fd등록

기존의 방법은 블로킹 상태로 서버가 먼저 ssl_accept를 하기때문에 만약 클라이언트가 connect만 하고 ssl_accept를 안해주면 영원히 멈춰버리는것. 그래서 실제ssl핸드셰이크 순서를 참고하여 클라이언트가 먼저 ssl 핸드셰이크를 요청하는 방식으로 수정했다. 또 기존에는 논블로킹으로 만드는게 어려워서 일단 블로킹으로 ssl연결만 하고 논블로킹으로 변경했는데, connect이후 즉시 논블로킹으로 변경하도록 수정했다.

 

수정된 accept과정 - (논블로킹, 클라측에서 먼저 핸드셰이크 요청)

  1. listenser에서 accept로 tcp/ip 연결 성공.
  2. SslObjectRef 생성, session에 저장만 해둠.
  3.  소켓 논블로킹으로 변경 후 epoll에 fd 등록. EventType::HandShaking (이벤트 발생시 핸드셰이크 함)
  4. read 이벤트 발생하면 SslObject->SslHandShake 호출. ssl 없으면 만들고, SSL_accept 호출함.
    성공시 EventType::Read로 변경. 실패 시 리턴값에 따라 재시도 or disconnect

ThreadManager Launch 수정

기존에는 main문에서

for (int i = 0; i < 5; i++)
		GThreadManager->Launch();

이렇게 실행하면 ThreadManager의 멤버함수 WorkerThread를 호출하는 방식이었다.

이 방법은 서버만 구현할때는 상관없었지만, 클라이언트와 서버가 스레드에서 할 일이 다르기때문에 수정해야한다.

Launch함수의 인자로 콜백 함수를 넣어서, 스레드를 실행하면 콜백함수를 호출하도록 만들고, 콜백함수는 클라나 서버에서 커스텀해서 넣자.

void	ThreadManager::Launch(std::function<void()> callback)
{
	_workerThreads.push_back(std::thread([=](){
		InitTLS();
		callback();
		DestroyTLS();
	}));
}

 

기존의 ThraedManager::WorkerThread()

void	ThreadManager::WorkerThread()
{
	while (true)
	{
		JobRef job = nullptr;
		{
			std::unique_lock<std::mutex> lock(_m);
			_cv.wait(lock, [this]() { return !GJobQueue->Empty(); });
			job = GJobQueue->PopJob();
		}
		std::cout << "thread No." << LThreadId << " Awaken!" << std::endl;
		job->Execute();
	}
}

그런데 문제가 있다. 단순히 멤버함수를 지우고, 외부로 꺼내는걸로는 해결이 안된다.

내부에서 멤버 mutex도 사용하고, 멤버 condition_variable도 사용하기 때문. 일단은 _m과 _cv를 public으로 바꿔서 해결했다.

덤으로 왜 있었는지 모르는 캡쳐 [this]도 제거해줬다.

AuthServer main.cpp

void	WorkerThread()
{
	while (true)
	{
		JobRef job = nullptr;
		{
			std::unique_lock<std::mutex> lock(GThreadManager->_m);
			GThreadManager->_cv.wait(lock, []() { return !GJobQueue->Empty(); });
			job = GJobQueue->PopJob();
		}
		std::cout << "thread No." << LThreadId << " Awaken!" << std::endl;
		job->Execute();
	}
}
for (int i = 0; i < 5; i++)
    GThreadManager->Launch(WorkerThread);

이제 이렇게 해주면 커스텀 함수를 스레드에서 실행되게 만들 수 있다.


Service.h 수정하기

service에서 모든 로직을 실행시키는데, 지금은 service가 오직 인증서버만을 위해 만들어져있다.

인증서버용, 더미클라이언트용으로 분리시킬 수 있게 수정하자.

 

Service.h

AuthService와 ClientService를 추가하고, Service를 상속받는다.

Auth는 이전에 쓰던 생성자 파라메터를 그대로 가져오고, Client는 이중 인증서와 키는 필요없으니 빼자.

class AuthService : public Service
{
public:
	AuthService(const char* ip, int port, const char* certFile, const char* keyFile, SessionFactory factory);
	virtual ~AuthService();

	virtual int	Start();
};

class ClientService : public Service
{
public:
	ClientService(const char* ip, int port, SessionFactory factory);
	virtual ~ClientService();

	virtual int	Start();
};

 

 base Service 생성자도 client와 auth가 겹치는것만 필요하도록 수정하자.

Service::Service(const char* ip, int port, SessionFactory factory)
	: _addr(ip, port), _sessionFactory(factory)
{
	_ctx = std::make_shared<SslCtx>();
}

 

Service의 Start함수를 추상함수로 바꾸고, 기존의것은 AuthService::Start로 가져오자.

클라이언트의 Start는 일단 비워두고 나중에 손보자. 일단은 클라이언트용와 인증서버용 서비스를 분리하는데에 의의를 두자.

int	AuthService::Start()
{
	signal(SIGPIPE, SIG_IGN);
	ListenerRef		listener = make_shared<Listener>(shared_from_this(), _addr.GetPort());
	EpollEvent*		listenEvent = new EpollEvent(listener, EventType::Accept);

	_epollCore = make_shared<EpollCore>();
	_epollCore->Register(listenEvent);
	_epollCore->StartEpollWait();
	return 1;
}

int	ClientService::Start()
{
	signal(SIGPIPE, SIG_IGN);
	return 1;
}

 

그리고 이미 바꿔놨지만 기록하지 않은게 있는데 세션 생성자를 바꿨다.

기존엔 Listener에서 accept를 한 뒤에 Session을 만들어서 클라이언트를 관리했다. 그런데 지금은 PacketSession, AuthSession까지 생겼고, 클라이언트에서 사용할 세션은 또 다를것이기 때문에 각각의 세션에 맞는 생성자로 세션을 생성해줘야 한다.

 

Service.h

using SessionFactory = function<SessionRef(int, struct sockaddr_in, ServiceRef)>;

class Service : public enable_shared_from_this<Service>
{
public:
	Service(const char* ip, int port, SessionFactory factory);
	virtual ~Service() {}

	SessionRef	MakeSession(int clinetSocket, struct sockaddr_in addr, ServiceRef service);
	/*...*/
	SessionFactory		_sessionFactory;
};

SessionRef	Service::MakeSession(int clinetSocket, struct sockaddr_in addr, ServiceRef service)
{
	return _sessionFactory(clinetSocket, addr, service);
}
/*  AuthService 생성자  */
AuthService(const char* ip, int port, const char* certFile, const char* keyFile,
	SessionFactory factory);

/*-----------------------  main.cpp  --------------------------------*/

main()
{
	ServiceRef	service = make_shared<AuthService>(
		"127.0.0.1",
		stoi(av[1]),
		"TLS/server.crt",
		"TLS/server.key",
		[](int clientSocket, sockaddr_in addr, ServiceRef service) {
        	return std::make_shared<CAuthSession>(clientSocket, addr, service);
    	} // CAuthSessionRef를 생성해서 리턴해주는 함수 (생성자 역할로 넣음)
	);
}

이렇게 Service에 세션 생성자를 _sessionFactory에 저장해서 Listener에서 SessionRef를 만들때 사용한다.

SessionRef	session = _service->MakeSession(clientSocket, client_addr, _service);

일단 이렇게 DummyClient를 만들기 위해서 몇가지 작업을 마쳤다.

  • SSL_accept 클라이언트가 먼저 요청하는 형식으로 변경
  • ThreadManager가 커스텀 함수를 처리할 수 있도록 수정
  • 인증서버용 Service와 클라이언트용 Service의 분리

좌 서버 우 openssl 클라이언트

일단 이것저것 변경한 서버쪽은 잘 돌아간다.

근데 막상 여기까지 했는데 epoll로 클라이언트를 어떻게 여럿 접속시킬지 잘 안떠오른다. 좀 고민이 필요한 문제같다. 원래는 게임서버 강의를 참고해서 만들면 되겠지 싶었는데 IOCP랑 epoll이 좀 많이 달라서 생각보다 어려울 것 같다.

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

13. DummyClient <-> AuthServer 통신  (0) 2026.03.07
12. DummyClient (2)  (0) 2026.03.06
10. 프로젝트 리펙토링 + 모듈화  (0) 2026.03.06
9. DBConnectionPool 기초다지기  (0) 2026.03.05
8. PacketHandler  (0) 2026.03.04

+ Recent posts