이전 글에서는 단순히 WinSock이 연결되는지만 확인하고 실습을 마무리 지었다. 이번 글에서는 서버와 클라이언트 프로그램을 생성하고, 서버가 접속을 받을 수 있는 상태까지 만들어보고자 한다.
서버 코드
#include <iostream>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "WSAStartup failed" << std::endl;
return 1;
}
// 1. 소켓 생성
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
{
std::cout << "socket failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 2. 주소 구조체 설정
SOCKADDR_IN serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET; // IPv4
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 IP에서 접속 허용
serverAddr.sin_port = htons(9000); // 포트 번호
// 3. 바인드 (소켓에 주소를 붙임)
if (bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
std::cout << "bind failed: " << WSAGetLastError() << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
// 4. 리슨 (접속 대기 상태로 전환)
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
{
std::cout << "listen failed: " << WSAGetLastError() << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
std::cout << "Server is listening on port 9000..." << std::endl;
// 5. 접속 수락 (클라이언트가 올 때까지 여기서 멈춰 기다림)
SOCKADDR_IN clientAddr;
int clientAddrSize = sizeof(clientAddr);
SOCKET clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET)
{
std::cout << "accept failed: " << WSAGetLastError() << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
std::cout << "Client connected!" << std::endl;
// 6. 메시지를 받아서 그대로 돌려보내기 (에코)
char buffer[512];
while (true)
{
int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived <= 0)
{
std::cout << "Client disconnected." << std::endl;
break;
}
buffer[bytesReceived] = '\0';
std::cout << "Received: " << buffer << std::endl;
send(clientSocket, buffer, bytesReceived, 0);
}
// 7. 정리
closesocket(clientSocket);
closesocket(listenSocket);
WSACleanup();
return 0;
}
코드 양이 확실히 방대해진 것이 느껴진다. 천천히 하나씩 알아가보자.
1. 소켓 생성
서버와 클라이언트 구분 없이 통신에 쓸 소켓을 만드는 단계이다. 인자 세 개는 각각 IPv4 주소 체계, 스트림 방식(연결형), TCP 프로토콜을 뜻한다. 채팅은 메시지가 순서대로 빠짐없이 도착해야 하니 TCP를 사용한다. 성공하면 소켓 핸들을 돌려주고, 실패하면 INVALID_SOCKET을 반환해 예외 처리를 해야 한다.
2. 주소 구조체 설정
서버가 쓸 주소 정보로, 아래 세 가지를 채운다.
sin_family = AF_INET: IPv4를 쓰겠다는 표시sin_addr.s_addr = htonl(INADDR_ANY): 이 컴퓨터가 가진 어떤 IP로 들어와도 다 받겠다는 의미sin_port = htons(9000): 포트 번호로, 9000은 임의로 정한 값
3. 바인드
만든 소켓(listenSocket) 위에 주소(serverAddr)를 붙인다. 9000번 포트는 이제 이 소켓이 담당한다고 운영체제에 등록하는 것.
4. 리슨
소켓을 접속 대기 상태로 바꾼다. 클라이언트가 접속할 수 있도록 길을 뚫어놓는 것이다.
5. 접속 수락
클라이언트가 실제로 접속해 올 때까지 프로그램이 이 줄에서 멈춰 기다린다(블로킹). 클라이언트가 접속하면 accept()는 대기 큐에서 클라이언트 하나를 꺼내 그 클라이언트와 통신할 새 소켓(clientSocket)을 만들어 돌려준다. 이후 데이터 송수신은 listenSocket이 아니라 이 clientSocket으로 한다.
6. 메시지를 받아서 그대로 돌려보내기
받은 메시지를 그대로 다시 보내는 에코(echo) 동작이다. recv도 데이터가 올 때까지 멈춰 기다리고, 반환값이 0 이하이면 상대가 연결을 끊은 것이므로 루프를 빠져나간다.
코드는 길어보이지만, 서버가 하는 일을 다이어그램으로 그려보면 이렇게 5단계로 나눌 수 있다. 코드가 길어 보이는 건 예외 처리가 붙어있기 때문이고, 실제 동작은 크게 어렵지 않다.

클라이언트 코드
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "WSAStartup failed" << std::endl;
return 1;
}
// 1. 소켓 생성 (전화기 들이기)
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET)
{
std::cout << "socket failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 2. 서버 주소 적기 (어디로 걸지)
SOCKADDR_IN serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 서버 IP
serverAddr.sin_port = htons(9000); // 서버 포트
// 3. 전화 걸기
if (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
std::cout << "connect failed: " << WSAGetLastError() << std::endl;
closesocket(clientSocket);
WSACleanup();
return 1;
}
std::cout << "Client: connected to server!" << std::endl;
system("pause");
closesocket(clientSocket);
WSACleanup();
return 0;
}
다음은 클라이언트 코드를 살펴보도록 하자. 소켓을 생성하는 단계까지는 서버 코드와 비슷하게 생겼음을 알 수 있다.
1. 소켓 생성
서버에서 본 것과 글자 하나 다르지 않다. IPv4 + TCP로 통신할 전화기를 하나 더 만들어달라는 뜻이다.
2. 서버 주소 적기
서버에서는 자신의 번호를 정하겠다는 용도였다면, 클라이언트에서는 해당 번호로 전화를 걸겠다는 의미이다.
>sin_family = AF_INET: IPv4를 쓰겠다는 표시, 서버와 동일sin_addr.s_addr = inet_addr("127.0.0.1"): 걸 상대의 IP 주소 (내 컴퓨터 자신)sin_port = htons(9000): 포트 번호로, 9000은 임의로 정한 값
3. 전화 걸기
내 전화기(clientSocket)로, 방금 적은 주소(serverAddr)에 전화를 거는 것이다. 인자 세 개는 '어떤 전화기로 / 어디로 / 주소 정보 크기는 얼마'라는 뜻. 한쪽이 accept로 기다리고, 다른 쪽이 connect로 걸면 둘이 연결이 된다.
실행 방법
이전 글에서는 서버 한 프로그램만 실행했지만, 이번엔 전화를 받을 서버가 먼저 켜져 있어야 클라이언트가 전화를 걸 수 있다.
먼저 서버 솔루션을 우클릭해 [시작 프로젝트로 설정]을 누르고 먼저 실행해준다. 그 이후 클라이언트 솔루션을 우클릭해 마찬가지로 [시작 프로젝트로 설정]을 누르고 실행해 준다.
성공하면 서버 창과 클라이언트 창에 다음과 같은 메시지가 띄워진다. 양쪽 동시에 보이면 성공이다!

'프로그래밍 언어 > C++' 카테고리의 다른 글
| [소켓 통신] 멀티스레드 구현 (0) | 2026.07.14 |
|---|---|
| [소켓 통신] 메시지 주고받기 (0) | 2026.07.13 |
| [소켓 통신] Winsock 라이브러리 연결 (0) | 2026.07.09 |
| 불안정 정렬과 안정 정렬 (Stable Sort) (1) | 2024.08.30 |
| map과 unordered_map 비교 (2) | 2024.07.22 |
