멀티스레드 채팅 서버 구조와 이론
- 멀티스레드 기반 에코 서버의 확장형
- 한 클라이언트 TCP 세션을 관리하는 개별 스레드가 클라이언트 수 만큼 존재
- 연결된 모든 클라이언트를 관리하기 위한 자료구조가 필요하며 스레드 동기화
- 보다 강도 높은 안정성이 요구됨
멀티스레드 기반 채팅 서버
//새로 연결된 클라이언트의 소켓을 리스트에 저장한다.
BOOL AddUser(SOCKET hSocket)
{
::EnterCriticalSection(&g_cs); //임계영역 시작
//※ 이 코드는 오직 한 스레드만 수행한다는 것이 보장된다!
g_listClient.push_back( hSocket );
::LeaveCriticalSection(&g_cs); //임계영역 끝
return TRUE;
}
client socket들을 보관하는 list에 접근할 대는 CS를 이용해서 락을 걸어 한 스레드만 진입하도록 한다.
//Ctrl+C 이벤트를 감지하고 프로그램을 종료한다.
BOOL CtrlHandler(DWORD dwType)
{
if (dwType == CTRL_C_EVENT)
{
std::list<SOCKET>::iterator it;
//연결된 모든 클라이언트 및 리슨 소켓을 닫고 프로그램을 종료한다.
::shutdown(g_hSocket, SD_BOTH);
::EnterCriticalSection(&g_cs); //임계영역 시작
for (it = g_listClient.begin(); it != g_listClient.end(); ++it)
::closesocket(*it);
//연결 리스트에 등록된 모든 정보를 삭제한다.
g_listClient.clear();
::LeaveCriticalSection(&g_cs); //임계영역 끝
puts("모든 클라이언트 연결을 종료했습니다.");
//클라이언트와 통신하는 스레드들이 종료되기를 기다린다.
::Sleep(100);
::DeleteCriticalSection(&g_cs);
::closesocket(g_hSocket);
//윈속 해제
::WSACleanup();
exit(0);
return TRUE;
}
return FALSE;
}
sleep (100)을 하게 되는데 종료를 제대로 인식해야하는 프로그램을 만들어야 한다면 Event를 사용하여 구현할 수 있다.
//Ctrl+C 키를 눌렀을 때 이를 감지하고 처리할 함수를 등록한다.
if ( ::SetConsoleCtrlHandler(
(PHANDLER_ROUTINE)CtrlHandler, TRUE) == FALSE )
puts("ERROR: Ctrl+C 처리기를 등록할 수 없습니다.");
메인에서 위 CtrlHandler를 등록한다.
멀티스레드 기반 채팅 클라이언트
- 채팅 메시지 사용자 입력 및 송신처리 전담 스레드 (메인 스레드)
- 서버가 보내주는 채팅 메시지 수신 및 처리 전담 스레드 분리
- client 입장에서 다른 client들이 언제 메시지를 보낼지 모르겠으니 비동기적으로 별도의 스레드에서 처리해야
GUI가 멈추는것을 방지 할 수 있다.
- client 입장에서 다른 client들이 언제 메시지를 보낼지 모르겠으니 비동기적으로 별도의 스레드에서 처리해야
//서버가 보낸 메시지를 수신하고 화면에 출력하는 스레드 함수.
DWORD WINAPI ThreadReceive(LPVOID pParam)
{
SOCKET hSocket = (SOCKET)pParam;
char szBuffer[128] = { 0 };
while (::recv(hSocket, szBuffer, sizeof(szBuffer), 0) > 0)
{
printf("-> %s\n", szBuffer);
memset(szBuffer, 0, sizeof(szBuffer));
}
puts("수신 스레드가 끝났습니다.");
return 0;
}
hSocket이 close 되면 0을 반환하고 스레드가 종료될 것이다.
이 경우는 서버쪽에 close를 호출한 경우이다.

현재는 loopback을 사용해서 빠르게 처리되어 25000 port의 TIME_WAIT을 확인할 수 없다.
우아하지 않은 비정상 종료
- 연결된 클라이언트 혹은 서버 프로세스가 비정상 종료 되면 발생하는 일
- 프로세스 종료 시 OS는 할당해준 자원을 강제 회수 하며 여기에는 개방한 파일과 소켓이 포함됨
- 프로세스 강제 종료보다 더 심각한 경우는 무엇인가?

1 server + 3 client가 잘 연결된 상태에서 client1개를 죽이니까 서버에게 RST가 날라온다.
In TCP (Transmission Control Protocol), RST stands for "Reset."
It's a control message sent by one endpoint (a device or application) to abruptly terminate a connection.
OS가 자원을 다가져가고 강제회수가 일어난후에 RST를 보내는것이다.
더 심각한 경우는 LAN 선 분리 했을 때나 , BlueScreen처럼 강력한 상황이 일어난것이다.
이는 서버가 알 길이 없다. 서버는 아무런 신호가 날라오지 않았으니 마치 정상 세션처럼 취급하지만 일을 제대로 못할 것이다.
서버에서는 연결을 신뢰하지 않고 heart beat를 통해 주기적으로 실제 연결을 체크해본다.
MFC 채팅 서버와 클라이언트
- 비동기 입/출력 처리를 지원
- 비동기 처리를 위해 GUI 메시지 큐 활용
- 내부적으로 이벤트 감시 방식을 채택하고 있으며 동시에 감시할 수 있는 수가 매우 제한적
- CAsyncSocket
event select 방식을 서서 CAsyncSocket이 여러 client들의 메시지들의 유무를 감시한다.
싱글 스레드 이벤트 루프 방식이다.
CAsyncSocket이랑 연관이있는 스레드가 반드시 존재한다. 해당 스레드가 관리하는 MQ가 있어야하기 때문이다.
MFC 기반 채팅 클라이언트
- 비동기 입/출력 처리를 지원
- 비동기 처리를 위해 GUI 메시지 큐 활용
- CSocket 사용시 입/출력 스레드를 분리한다면 소켓이 생성된 스레드에서
처리 스레드로 핸들 넘겨야 하는 문제가 있음
'Network > Windows 소켓 프로그래밍 입문에서 고성능 서버까지!' 카테고리의 다른 글
파일 송/수신과 프로토콜 설계 (0) | 2023.11.27 |
---|---|
파일 송/수신 (0) | 2023.11.23 |
TCP 채팅 서버 - 성능 개선 (0) | 2023.11.22 |
소켓 옵션과 필수 이론 (0) | 2023.11.19 |
TCP 소켓 프로그래밍 입문 (0) | 2023.11.16 |