#pragma comment(lib, "ws2_32") #include #include #include ///////////////////////////////////////////////////////////////////////// CRITICAL_SECTION g_cs; // 스레드 동기화 객체 SOCKET g_hSocket; // 서버의 LISTEN 소켓 std::list g_listClient; // 연결된 클라이언트 소켓 리스트 ///////////////////////////////////////////////////////////////////////// // 새로 연결된 클라이언트의 소켓을 리스트에 저장한다. // BOOL : winAPI 와의 최적화 (일반 c++ 의 경우 bool 사용) BOOL AddUser(SOCKET hSocket) { ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 이 때 이 안의 코드는 오직 한 스레드만 수행해야만 한다 ! g_listClient.push_back(hSocket); ::LeaveCriticalSection(&g_cs); // 임계영역 끝 return TRUE; } ///////////////////////////////////////////////////////////////////////// // 연결된 클라이언트 모두에게 메시지를 전송한다. void SendChattingMessage(char* pszParam) { int nLength = strlen(pszParam); std::list::iterator it; ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 연결된 모든 클라이언트들에게 같은 메시지를 전달한다. for (it = g_listClien"> #pragma comment(lib, "ws2_32") #include #include #include ///////////////////////////////////////////////////////////////////////// CRITICAL_SECTION g_cs; // 스레드 동기화 객체 SOCKET g_hSocket; // 서버의 LISTEN 소켓 std::list g_listClient; // 연결된 클라이언트 소켓 리스트 ///////////////////////////////////////////////////////////////////////// // 새로 연결된 클라이언트의 소켓을 리스트에 저장한다. // BOOL : winAPI 와의 최적화 (일반 c++ 의 경우 bool 사용) BOOL AddUser(SOCKET hSocket) { ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 이 때 이 안의 코드는 오직 한 스레드만 수행해야만 한다 ! g_listClient.push_back(hSocket); ::LeaveCriticalSection(&g_cs); // 임계영역 끝 return TRUE; } ///////////////////////////////////////////////////////////////////////// // 연결된 클라이언트 모두에게 메시지를 전송한다. void SendChattingMessage(char* pszParam) { int nLength = strlen(pszParam); std::list::iterator it; ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 연결된 모든 클라이언트들에게 같은 메시지를 전달한다. for (it = g_listClien"> #pragma comment(lib, "ws2_32") #include #include #include ///////////////////////////////////////////////////////////////////////// CRITICAL_SECTION g_cs; // 스레드 동기화 객체 SOCKET g_hSocket; // 서버의 LISTEN 소켓 std::list g_listClient; // 연결된 클라이언트 소켓 리스트 ///////////////////////////////////////////////////////////////////////// // 새로 연결된 클라이언트의 소켓을 리스트에 저장한다. // BOOL : winAPI 와의 최적화 (일반 c++ 의 경우 bool 사용) BOOL AddUser(SOCKET hSocket) { ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 이 때 이 안의 코드는 오직 한 스레드만 수행해야만 한다 ! g_listClient.push_back(hSocket); ::LeaveCriticalSection(&g_cs); // 임계영역 끝 return TRUE; } ///////////////////////////////////////////////////////////////////////// // 연결된 클라이언트 모두에게 메시지를 전송한다. void SendChattingMessage(char* pszParam) { int nLength = strlen(pszParam); std::list::iterator it; ::EnterCriticalSection(&g_cs); // 임계영역 시작 // 연결된 모든 클라이언트들에게 같은 메시지를 전달한다. for (it = g_listClien">
#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#include <windows.h>
#include <list>
#include <iterator>

/////////////////////////////////////////////////////////////////////////
CRITICAL_SECTION g_cs; // 스레드 동기화 객체
SOCKET	g_hSocket; // 서버의 LISTEN 소켓
std::list<SOCKET>	g_listClient; // 연결된 클라이언트 소켓 리스트

/////////////////////////////////////////////////////////////////////////
// 새로 연결된 클라이언트의 소켓을 리스트에 저장한다.
// BOOL : winAPI 와의 최적화 (일반 c++ 의 경우 bool 사용)
BOOL AddUser(SOCKET hSocket) {
	::EnterCriticalSection(&g_cs); // 임계영역 시작
	// 이 때 이 안의 코드는 오직 한 스레드만 수행해야만 한다 !
	g_listClient.push_back(hSocket);
	::LeaveCriticalSection(&g_cs); // 임계영역 끝

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////
// 연결된 클라이언트 모두에게 메시지를 전송한다.
void SendChattingMessage(char* pszParam) {
	int nLength = strlen(pszParam);
	std::list<SOCKET>::iterator it;

	::EnterCriticalSection(&g_cs); // 임계영역 시작
	// 연결된 모든 클라이언트들에게 같은 메시지를 전달한다.
	for (it = g_listClient.begin(); it != g_listClient.end(); ++it) {
		::send(*it, pszParam, sizeof(char) * (nLength + 1), 0);
	}
	::LeaveCriticalSection(&g_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);

		// winsock 해제
		::WSACleanup();

		exit(0);
		return TRUE;
	}
	return FALSE;
}

/////////////////////////////////////////////////////////////////////////
// 클라이언트에게 채팅 메시지 서비스를 제공하는 스레드 함수
// 연결된 각각의 클라이언트마다 한 스레드가 생성된다
DWORD WINAPI ThreadFunction(LPVOID pParam) {
	char szBuffer[128] = { 0 };
	int nReceive = 0;
	SOCKET hClient = (SOCKET)pParam;

	puts("새 클라이언트가 연결되었습니다.");
	while ((nReceive = ::recv(hClient, szBuffer, sizeof(szBuffer), 0)) > 0) {
		puts(szBuffer);
		// 수신한 문자열을 연결된 전체 클라이언트들에게 전송
		SendChattingMessage(szBuffer);
		memset(szBuffer, 0, sizeof(szBuffer));
	}

	puts("클라이언트가 연결을 끊었습니다.");
	::EnterCriticalSection(&g_cs); // 임계영역 시작
	g_listClient.remove(hClient);
	::EnterCriticalSection(&g_cs); // 임계영역 끝

	::closesocket(hClient);
	return 0;
}

/////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[]) {
	// winsock 초기화
	WSADATA wsa = { 0 };
	if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		puts("ERROR: 윈속을 초기화 할 수 없습니다.");
		return 0;
	}

	// 임계영역 객체 생성
	::InitializeCriticalSection(&g_cs);

	// Ctrl + C 가 눌렸을 때 이를 감지하고 처리할 함수 등록
	if (::SetConsoleCtrlHandler(
		(PHANDLER_ROUTINE)CtrlHandler, TRUE) == FALSE) {
		puts("ERROR: Ctrl+C 처리기를 등록할 수 없습니다.");
	}

	// 접속 대기 소켓 생성
	g_hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (g_hSocket == INVALID_SOCKET) {
		puts("ERROR: 접속 대기 소켓을 생성할 수 없습니다.");
		return 0;
	}

	// 포트 바인딩
	SOCKADDR_IN svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(25000);
	svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	if (::bind(g_hSocket, (SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR) {
		puts("ERROR: 소켓에 IP 주소와 포트를 바인드할 수 없습니다.");
		return 0;
	}

	// 접속 대기 상태로 전환
	if (::listen(g_hSocket, SOMAXCONN) == SOCKET_ERROR) {
		puts("ERROR: 리슨 상태로 전환할 수 없습니다.");
		return 0;
	}
	puts("*** 채팅 서버를 시작합니다. ***");

	// 클라이언트 접속 처리 및 대응
	SOCKADDR_IN clientaddr = { 0 };
	int nAddrLen = sizeof(clientaddr);
	SOCKET hClient = 0;
	DWORD dwThreadID = 0;
	HANDLE hThread;

	// 클라이언트 연결을 받아들이고 새로운 소켓 생성
	while ((hClient = ::accept(g_hSocket, (SOCKADDR*)&clientaddr, &nAddrLen)) != INVALID_SOCKET) {
		if (AddUser(hClient) == FALSE) {
			puts("ERROR: 더 이상 클라이언트 연결을 처리할 수 없습니다.");
			CtrlHandler(CTRL_C_EVENT);
			break;
		}

		// 클라이언트로부터 문자열을 수신함
		hThread = ::CreateThread(NULL, // 보안속성 상속
			0, // 스택 메모리 기본 크기(1MB)
			ThreadFunction, // 스레드로 실행할 함수 이름
			(LPVOID)hClient, // 새로 생성된 클라이언트 소켓
			0, // 생성 플래그는 기본값
			&dwThreadID); // 생성된 스레드ID가 저장될 변수 주소

		::CloseHandle(hThread);
	}

	puts("*** 채팅서버를 종료합니다. ***");
	return 0;
}

  • 추가 (Win API 에서 지원하는 키보드 이벤트를 signal 로 프로그램을 안전하게 종료할 수 있는 slot 함수를 수행할 수 있도록 했다.)

    image.png