서버를 테스트할 수 있도록 더미클라이언트를 마저 만들어보자.
지난번엔 세가지정도 큰 수정을 했다.
- SSL_accept 클라이언트가 먼저 요청하는 형식으로 변경
- ThreadManager가 커스텀 함수를 처리할 수 있도록 수정
- 인증서버용 Service와 클라이언트용 Service의 분리
SSL_CTX 수정
Service 생성자에서 SSL_CTX를 생성할 때 인자를 받아서 서버용으로 만들지 클라용으로 만들지 선택할 수 있게 수정했다.
const SSL_METHOD* method;
if (serverMethod)
method = TLS_server_method();
else
method = TLS_client_method();
_ctx = SSL_CTX_new(method);
그리고 Service 기본생성자에서 SSL_CTX객체를 생성했었는데, 서버와 클라를 분리하기위해 각각의 생성자로 이동시겼다.
Connect 구현
이전까지는 서버가 다른곳에 connect를 할 일이 없어서 아예 구현도 안해놨었는데, 클라이언트가 써야해서 만들어줬다.
비동기 상태로 connect 함수를 호출하기때문에 실패 시에도 errno를 확인해 connect가 진행중이라면 write이벤트를 감지해서 connect작업을 이어가도록 만들었다.
bool Session::Connect()
{
int serverLen = sizeof(struct sockaddr);
if (connect(_socket, (struct sockaddr*)&_address.GetAddr(), serverLen))
{
if (errno == EINPROGRESS)
{
_epollEvent->SetEventType(EventType::Connect);
return true;
}
else
return false;
}
_epollEvent->SetEventType(EventType::HandShakingWrite);
return true;
}
그리고, connect를 성공적으로 마치면 (위 코드에서 if 문으로 들어가지 않으면) HandShakingRead (Write감지)로 이벤트를 바꿔준다. 그러면 다음 epoll wait에서 write를 감지하고 diapatch함수를 통해 ProcessHandShaking -> ProcessSslConnect 를 호출한다.
void Session::Dispatch(uint32_t events)
{
switch (_epollEvent->GetEventType())
{
case EventType::Connect :
ProcessConnect();
return;
case EventType::Disconnect :
ProcessDisconnect(true);
return;
case EventType::HandShakingRead :
case EventType::HandShakingWrite :
ProcessHandShaking();
return;
case EventType::Write :
ProcessWrite();
case EventType::Read :
if (events & EPOLLIN)
ProcessRead();
return;
}
ProcessDisconnect(false);
}
void Session::ProcessHandShaking()
{
if (_isClient)
ProcessSslAccept();
else
ProcessSslConnect();
}
void Session::ProcessSslConnect()
{
int ret = _sslObject->SslConnect();
switch (ret)
{
case 0 :
ModEvent(EventType::Read);
return ;
case 1 :
ModEvent(EventType::HandShakingRead);
return ;
case 2 :
ModEvent(EventType::HandShakingWrite);
return ;
default :
ProcessDisconnect(false);
return ;
}
}
_isClient 변수는 이 세션이 서버냐 세션이냐를 말하는것. 더미클라이언트에서 서버로 연결하기 위해 만든 서버세션이니 지금은 false이다. (좀 헷갈린다) 그리고 SSL_conenct 로 ssl연결을 요청한다. 여기서도 리턴값에 따라 이벤트 변경을 잘 해줘야한다.
여담으로 디스패치 함수도 if else에서 깔끔하게 스위치문으로 바꿔줬다.
참고로 세션의 멤버함수 ModEvent함수는 EventType을 넣으면 _epollEvent에 적힌 type을 바꿔주고, epoll_ctl로 이벤트 타입에 맞게 ev.events를 epoll에 적용시켜주도록 만들었다. 만들어놓고 _epollEvent에 적힌 type만 바꿔줬다가 갖은 버그에 시달렸다....
- Listener에서도 accept를 성공한 뒤 epoll event를 기존 read로 설정하지 않고 이제 HandShakingRead(read)로 설정함. (상대가 ssl_connect 할때까지 기다리겠다는 것)
ClientService의 Start함수 구현.
int ClientService::Start()
{
signal(SIGPIPE, SIG_IGN);
_epollCore = make_shared<EpollCore>();
for (int i = 0; i < _clientCount; ++i)
{
int socket = SocketUtil::CreateSocket();
int addrlen = sizeof(sockaddr_in);
SessionRef session = _sessionFactory(socket, _addr.GetAddr(), shared_from_this());
EpollEvent* epollEvent = new EpollEvent(session, EventType::Connect);
SocketUtil::MakeSocketNonblock(socket);
session->Connect();
_epollCore->Register(epollEvent);
InsertSession(session);
}
_epollCore->StartEpollWait();
return 1;
}
clientService 생성자로 설정한 클라이언트 수만큼 소켓 생성, 세션 생성, connect 시도하고 epoll에 등록한다.
여러 클라이언트 동시 접속시키기
어떻게하면 동시에 여러 클라이언트가 접속하고, 메시지를 보내게 만들지 고민을 좀 해봤다.
생각해낸 방법은 이렇다.
- 1초에 한번씩 모든 세션에 대해 session->Send(writeBuffer); 를 호출하는 스레드를 작동시킨다.
- send함수는 호출될때마다 세션의 SendBufferQueue에 SendBuffer를 집어넣고, ModEvent(EventType::Write) 한다.
- 메인스레드는 write 이벤트가 발생하면 해당 세션의 writeBufferQueue가 빌때까지 write를 해준다.
void Broadcast(ClientServiceRef service)
{
Protocol::C_SIGNUP pkt;
pkt.set_email("asd@naver.com");
pkt.set_nickname("Dodontak");
pkt.set_password("password123");
while (true)
{
WriteBufferRef writeBuffer = ServerPacketHandler::MakeWriteBuffer(pkt, PKT_C_SIGNUP);
std::this_thread::sleep_for(chrono::seconds(1));
service->broadcastfortest(writeBuffer);
}
}
void ClientService::broadcastfortest(WriteBufferRef writebuffer)
{
std::lock_guard<std::mutex> lock(_m);
for (auto session : _sessions)
{
static_pointer_cast<Session>(session)->Send(writebuffer);
}
}
int main()
{
/*...*/
GThreadManager->Launch(
[service](){
Broadcast(service);
}
);
}
문제는 더미클라이언트는 사실상 싱글스레드로 작동한다는 것. 애초에 epoll로 서버를 만들 때 워커스레드는 DB작업이나 해싱작업같이 무거운 작업을 하고, 그외의 read, 역직렬화, write 작업과은 모두 메인스레드가 담당하도록 만들어서 이건 더미클라이언트를 위해 서버구조를 뒤집어 엎지 않는 이상 변경이 불가능하다...
싱글스레드로 돌리더라도 더미클라이언트쪽에서 패킷을 보내면 서버에서는 상대적으로 무거운 db작업을 한 뒤에 response를 보낼테니 테스트용도로 쓰기에 큰 무리는 없을 것 같다. 정 진짜 여러개의 스레드로 하려면 그냥 쉘스크립트로 여러개의 clinet를 켜는것도 방법일 듯 싶다.
아니면
아무튼 이걸로 더미클라이언트 작업은 어느정도 마무리가 되었고, 테스트도 해보자.


더미클라이언트에서 세션을 5개 여니까 메시지가 5개씩 간다.
그리고 클라이언트에서 패킷에 적어 보낸 메시지를 서버에서 잘 읽는지도 확인해봤는데 아주 잘 된다!
'프로젝트 > Project_Island' 카테고리의 다른 글
| 14. 패킷 자동화 (1) (0) | 2026.03.08 |
|---|---|
| 13. DummyClient <-> AuthServer 통신 (0) | 2026.03.07 |
| 11. DummyClient (1) (0) | 2026.03.06 |
| 10. 프로젝트 리펙토링 + 모듈화 (0) | 2026.03.06 |
| 9. DBConnectionPool 기초다지기 (0) | 2026.03.05 |