프로그래밍/소켓 프로그래밍 입문

멀티스레드 채팅 서버: 구조와 이론

데일리 백수 2025. 1. 14. 21:39

채팅 서버는 여러 클라이언트가 서로 메시지를 주고받을 수 있도록 설계된 네트워크 애플리케이션입니다. 기존의 멀티스레드 에코 서버를 확장하면 기본적인 채팅 서버를 만들 수 있습니다. 하지만 동시성 문제와 부하 처리 등을 고려해야 하므로 보다 정교한 설계와 구현이 필요합니다.

 

1. 멀티스레드 채팅 서버의 기본 구조

1.1 기본 원리

  • 스레드 생성:
    • 각 클라이언트가 연결되면, 이를 처리하기 위해 별도의 스레드를 생성합니다.
  • 메시지 브로드캐스트:
    • 한 클라이언트가 보낸 메시지를 서버가 받아 다른 모든 클라이언트에 전송.

1.2 주요 자료구조

  • 연결 리스트 (Linked List):
    • 클라이언트 소켓 정보를 저장하기 위해 사용.
    • 각 노드에는 소켓 핸들과 클라이언트 ID 등의 정보가 포함.
  • STL 컨테이너:
    • 예: std::list 또는 std::vector를 사용하여 클라이언트 목록 관리.

2. 멀티스레드에서 동기화의 중요성

2.1 동기화란?

멀티스레드 환경에서는 여러 스레드가 동시에 같은 자원(예: 연결 리스트)에 접근할 수 있습니다. 이를 제어하지 않으면 다음과 같은 문제가 발생합니다:

  • 레이스 컨디션 (Race Condition):
    • 두 개 이상의 스레드가 동시에 자원에 접근하여 비일관성 데이터가 발생.
  • 데이터 손실:
    • 연결 리스트에 클라이언트를 추가/삭제할 때 충돌 발생.

2.2 동기화의 두 가지 목적

  1. 임계 구역 보호:
    • 특정 메모리나 자원에 한 번에 하나의 스레드만 접근 가능하도록 제한.
    • 예: 크리티컬 섹션 (Critical Section) 사용.
  2. 스레드 간 타이밍 조율:
    • 특정 작업이 완료될 때까지 다른 스레드가 기다리도록 설정.
    • 예: 이벤트(Event) 사용.

3. 채팅 서버 설계 단계

3.1 클라이언트 관리

  • 클라이언트마다 고유의 소켓 핸들을 저장.
  • STL 연결 리스트(std::list)를 사용하여 클라이언트 정보를 관리.
  • 각 클라이언트가 연결될 때:
    • 소켓 정보를 연결 리스트에 추가.
  • 클라이언트가 종료될 때:
    • 소켓 정보를 연결 리스트에서 삭제.

3.2 메시지 브로드캐스트

  • 한 클라이언트가 서버에 메시지를 보내면:
    • 연결 리스트를 순회하며 모든 클라이언트 소켓으로 메시지를 전송.

 

4. 서버 부하와 도배 문제

4.1 부하 문제

  • 연결된 클라이언트 수가 증가할수록 서버의 부하가 증가.
  • 한 클라이언트의 메시지를 N명의 클라이언트에게 브로드캐스트:
    • 메시지 송신 작업이 O(N) 시간 복잡도를 가짐.

4.2 도배 방지

  • 클라이언트가 너무 많은 메시지를 빠르게 전송하면 서버 과부하 발생.
  • 해결 방법:
    1. 속도 제한:
      • 클라이언트가 특정 시간 내에 전송할 수 있는 메시지 개수 제한.
    2. 세션 차단:
      • 도배하는 클라이언트의 연결을 차단.
void CheckSpamming(SOCKET clientSocket, const std::string& message) {
    static std::unordered_map<SOCKET, int> messageCount;
    static std::chrono::steady_clock::time_point lastCheckTime = std::chrono::steady_clock::now();

    messageCount[clientSocket]++;

    auto now = std::chrono::steady_clock::now();
    if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheckTime).count() > 1) {
        lastCheckTime = now;
        for (auto& [socket, count] : messageCount) {
            if (count > 10) {
                closesocket(socket);
                messageCount.erase(socket);
                std::cout << "Client disconnected due to spamming.\n";
            } else {
                count = 0;
            }
        }
    }
}

 

5. 멀티스레드 채팅 서버의 한계와 개선

5.1 한계

  1. 스레드 개수 제한:
    • 클라이언트 수가 많아질수록 스레드 수가 증가하여 시스템 자원 고갈 가능.
  2. 브로드캐스트 효율:
    • 메시지를 모든 클라이언트에 전송할 때 부하가 기하급수적으로 증가.

5.2 개선 방안

  1. 스레드 풀 사용:
    • 스레드 수를 제한하고, 필요할 때 기존 스레드를 재사용.
  2. 비동기 I/O 모델:
    • 스레드 대신 비동기 I/O를 사용하여 효율적으로 클라이언트를 처리.
  3. 로드 밸런싱:
    • 서버를 여러 대로 분산하여 부하를 줄임.

 

멀티스레드 채팅 서버는 기본적인 동작 원리는 단순하지만, 동시성 문제와 서버 부하 관리가 중요합니다. 동기화 메커니즘도배 방지 전략을 통해 안정성을 높이고, 스레드 풀이나 비동기 I/O를 활용하여 확장성을 확보해야 합니다.