기존의 “단일 쓰레드 + blocking(while) accept() 구조” 의 예제에서 Server 의 서버 소켓을 열고, 여러 Client 를 실행시키면, 첫 번째 클라이언트와 통신하는 동안 서버는 accept() 로 돌아가지 않기 때문에
⇒ 실제 Server 의 서버 소켓에서는 하나의 Client 와만 연결이 성립되고, 해당 통신 소켓을 통해 데이터를 주고 받는다.
전체 코드 분석
#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
// **! 연결된 클라이언트와 통신(Echo 서비스)하기 위한
// worker thread 함수**
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) {
// 수신한 문자열을 그대로 Echo 전송
::send(hClient, szBuffer, sizeof(szBuffer), 0);
puts(szBuffer);
memset(szBuffer, 0, sizeof(szBuffer));
}
puts("클라이언트 연결이 끊겼습니다.");
::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;
}
// 1. 접속 대기 소켓 생성
SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET) {
puts("ERROR: 접속 대기 소켓을 생성할 수 없습니다.");
return 0;
}
// 2. 포트 바인딩
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(hSocket, (SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR) {
puts("ERROR: 소켓에 IP 주소와 포트를 바인드 할 수 없습니다.");
return 0;
}
// 3. 접속 대기 상태로 전환
if (::listen(hSocket, SOMAXCONN) == SOCKET_ERROR) {
puts("ERROR: 리슨 상태로 전환할 수 없습니다.");
return 0;
}
// 4. 클라이언트 접속 처리 및 대응
SOCKADDR_IN clientaddr = { 0 };
int nAddrLen = sizeof(clientaddr);
SOCKET hClient = 0;
DWORD dwThreadID = 0;
HANDLE hThread;
// 4.1. 클라이언트 연결을 받아들이고 새로운 소켓 생성(개방)
while ((hClient = ::accept(hSocket, (SOCKADDR*)&clientaddr, &nAddrLen)) != INVALID_SOCKET) {
**// ! 4.2. 새 클라이언트와 통신하기 위한 스레드 생성
// 클라이언트 마다 스레드가 하나씩 생성되는 것 !!!**
hThread = ::CreateThread(
NULL, // 보안속성 상속
0, // 스택 메모리 기본 크기(1mb) 적용
ThreadFunction, **// 스레드로 실행할 함수 이름**
(LPVOID)hClient, **// 새로 생성된 클라이언트 소켓**
0, // 생성 플래그는 기본값 사용
&dwThreadID); // 생성된 스레드 ID가 저장될 변수 주소
::CloseHandle(hThread);
}
// 5. 리슨 소켓 닫기
::closesocket(hSocket);
// winsock 해제
::WSACleanup();
return 0;
}
멀티스레드 Echo 서버 실행 후 Echo Client 여러 개 실행 후 확인