이전 글이 PacketHandler인데, 사실 DBConnectionPool을 더 먼저 구현했다.
패킷핸들러로 넘긴 결과물이 db작업을 해야하는데 DBConnectionPool이 어떻게 동작하는지 모르니까 패킷핸들러 만드는데 계속 '이렇게 해도 상관없나?' 하고 근심이 들었기 때문... 근데 만들고나니 서로 의존적이지 않아서 괜한 걱정이었다.
DBConnection
인증서버에서 postgres와 redis를 사용하는데 이번엔 postgres와의 연결만 만들어보자.
libpq 라이브러리 사용에 대한 내용은 아래 글을 참조하자.
postgresql libpq C/C++
도커 debian:trixie 컨테이너에서 작성된 게시글입니다.헤더#include DB접속PGconn* conn = PQconnectdb("host=postgres user=postgres port=5432 dbname=postgres password=password");host에 적힌 postgres는 docker compose 파일에서 설정
dodontak.tistory.com
만약 매번 db에 CRUD를 할때마다 연결을 해야한다면 큰 비용이 들것이다. tcp연결부터 해서 커넥션 구조체에 메모리할당에 카피에 db작업을 계속할텐데 이걸 반복하면 cpu에 손실이 클 것. 그래서 DBConnectionPool을 만들어 사용한 커넥션은 다시 풀에 집어넣고, 필요할 때 꺼내서 쓰는식으로 사용한다.
커넥션 풀을 만들기 전에 먼저 커넥션부터 만들어보자.
DBConnection.h, cpp
#pragma once
#include "Utils.h"
#include <libpq-fe.h>
class DBConnection
{
public:
DBConnection(const char* connectionString);
~DBConnection();
PGresult* ExecuteSQL(const char* sql);
private:
PGconn* _connection = nullptr;
};
#include "DBConnection.h"
DBConnection::DBConnection(const char* connectionString)
{
_connection = PQconnectdb(connectionString);
if (PQstatus(_connection) != CONNECTION_OK)
handle_error("PQconnectdb error", 1);
}
DBConnection::~DBConnection()
{
PQfinish(_connection);
}
PGresult* DBConnection::ExecuteSQL(const char* sql)
{
return PQexec(_connection, sql);
}
우선 가장 단순한 형태로 만들어봤다.
커넥션 스트링을 받아 커넥션만들어 멤버변수로 들고있고, ExecuteSQL로 sql문을 받아 실행해서 결과를 리턴하는 식.
"host=postgres user=postgres port=5432 dbname=postgres password=password connect_timeout=3"
- 커넥션 스트링
DBConnectionPool
그리고 pool에서는 DBConnectionRef 벡터를 들고있다가 요청하면 꺼내서 준다. 작업을 마치고 풀에 다시 넣는걸 잊지 말자.
여러 스레드에서 사용할 것이기 때문에 전역변수로 설정하고, 뮤택스락도 적절하게 사용하자.
CoreGlobal.h
전역변수에 DBConnectionPoll 추가.
extern class DBConnectionPool* GDBConnectionPool;
CoreGlobal.cpp
DBConnectionPool* GDBConnectionPool = nullptr;
class CoreGlobal
{
public:
CoreGlobal()
{
GDBConnectionPool = new DBConnectionPool();
}
~CoreGlobal()
{
delete GDBConnectionPool;
}
} GcoreGlobal;
DBConnectionPool.h, cpp
#pragma once
#include "Types.h"
#include "DBConnection.h"
#include <vector>
#include <mutex>
#include <string>
class DBConnectionPool
{
public:
DBConnectionPool() {}
~DBConnectionPool() {}
bool Connect(int connectionCount, const char* connectionString);
void Push(DBConnectionRef conn);
DBConnectionRef Pop();
private:
std::mutex _m;
std::vector<DBConnectionRef> _connections;
};
#include "DBConnectionPool.h"
bool DBConnectionPool::Connect(int connectionCount, const char* connectionString)
{
_connections.reserve(connectionCount);
for (int i = 0; i < connectionCount; i++)
{
DBConnectionRef conn = make_shared<DBConnection>(connectionString);
_connections.push_back(conn);
}
return true;
}
void DBConnectionPool::Push(DBConnectionRef conn)
{
std::lock_guard<std::mutex> lock(_m);
_connections.push_back(conn);
}
DBConnectionRef DBConnectionPool::Pop()
{
DBConnectionRef connection = nullptr;
std::lock_guard<std::mutex> lock(_m);
if (_connections.empty())
return connection;
connection = _connections.back();
_connections.pop_back();
return connection;
}
간단하게 테스트도 해보자.
int main()
{
GDBConnectionPool->Connect(10,
"host=postgres user=postgres port=5432 dbname=postgres password=password connect_timeout=3");
DBConnectionRef con = GDBConnectionPool->Pop();
PGresult* result = con->ExecuteSQL("SELECT user_id, emailaddress FROM public.users WHERE name='dodontak';");
cout << PQgetvalue(result, 0, 0) << endl;
cout << PQgetvalue(result, 0, 1) << endl;
}

잘 된다!
우선은 대충 dbconnection을 만들어봤는데, 완성본은 아니다. 지금은 쿼리에 대한 처리을 하드코딩 해야하고, sql인젝션에 취약한 형태다.
다음엔 테스트를 위한 더미클라이언트를 만들고, 실제로 테스트해보면서 지금까지 만든것들을 전체적으로 개량해보자.
'프로젝트 > Project_Island' 카테고리의 다른 글
| 11. DummyClient (1) (0) | 2026.03.06 |
|---|---|
| 10. 프로젝트 리펙토링 + 모듈화 (0) | 2026.03.06 |
| 8. PacketHandler (0) | 2026.03.04 |
| 7. Protobuf, PacketHeader, PacketSession (0) | 2026.03.04 |
| 6. 멀티스레드 작업 (0) | 2026.03.03 |