즉, 소켓은 “운영체제 안에서 만들어지는 네트워크용 파일 핸들”이다.
| 요소 | 역할 | 예시 |
|---|---|---|
| IP 주소 | 어떤 컴퓨터인가 | 192.168.0.10 |
| 포트 번호 | 어떤 프로그램인가 | 80(HTTP), 22(SSH), 7777(사용자 지정) |
| 프로토콜 | 어떤 규칙으로 대화하는가 | TCP, UDP |
이 세 가지가 합쳐져서 “어디로 연결할지”를 완전히 정의한다.
TCP는 신뢰성과 순서를 보장하지만 속도는 UDP보다 느리다.
1️⃣ 클라이언트 → 서버 : SYN (연결 요청)
2️⃣ 서버 → 클라이언트 : SYN + ACK (요청 수락)
3️⃣ 클라이언트 → 서버 : ACK (수락 확인)
→ 연결 성립, 데이터 교환 가능
이 절차는 모든 TCP 연결에서 반드시 거친다.
| 역할 | 동작 | 의미 |
|---|---|---|
| 서버(Server) | 포트를 열고 bind(), listen()으로 대기 | “문을 열어둔 쪽” |
| 클라이언트(Client) | 서버의 IP/포트로 connect() 호출 | “찾아가는 쪽” |
연결이 성립된 뒤에는 완전히 대등한 관계지만,
연결을 누가 먼저 시작했는가(initiator)라는 역할은 끝까지 유지된다.
send() 와 recv() 를 호출할 수 있다.1️⃣ 프로그램이 send() 호출 → OS 커널의 송신 버퍼에 데이터 복사
2️⃣ 커널이 네트워크 카드로 패킷 전송
3️⃣ 상대방 커널의 수신 버퍼에 데이터 적재
4️⃣ 상대방 프로그램이 recv()로 버퍼의 데이터를 읽음
즉, 우리가 직접 패킷을 다루는 게 아니라
OS의 버퍼를 통해 간접적으로 통신하는 구조다.
htons, htonl, ntohs, ntohl 함수로 변환해야 한다.| 모드 | 특징 | 예시 |
|---|---|---|
| 블로킹 | 결과가 나올 때까지 멈춤 | accept()는 클라이언트 연결 전까지 대기 |
| 논블로킹 | 바로 반환, 준비 안 되면 오류 코드 | 실시간 서버에 자주 사용 |
논블로킹 소켓은 보통 select(), poll(), epoll() 같은 이벤트 감시 함수와 함께 쓴다.
| 항목 | TCP | UDP |
|---|---|---|
| 연결 방식 | 연결형 | 비연결형 |
| 신뢰성 | 손실·순서 보장 | 보장 없음 |
| 속도 | 느림 | 빠름 |
| 용도 | 웹, DB, 파일 전송 | 게임, 스트리밍, 센서 데이터 |
127.0.0.1은 자기 자신을 가리키는 가상 네트워크 인터페이스WSAStartup()으로 Winsock 초기화 후 시작해야 한다.WSACleanup()으로 해제해야 한다.ws2_32.lib가 필요하다 (#pragma comment(lib, "ws2_32.lib") 또는 링커 설정에 추가).inet_addr() (IPv4만, 오류 구분 불명확)InetPton() (IPv4/IPv6, 스레드 안전, 명확한 반환값)#define _WINSOCK_DEPRECATED_NO_WARNINGS로 inet_addr()도 사용 가능.try/catch)를 이용해std::string)과 헤더 순서(winsock2.h → windows.h)만 주의.즉, C에서 배운 소켓 코드는 C++에서도 그대로 돌아간다.
즉, “말은 서로 주고받지만, 문은 여전히 서버가 열어둔 문이다.”
| 개념 | 설명 |
|---|---|
| 포트 충돌 | 같은 포트 번호를 다른 프로세스가 bind하면 에러 발생 |
| TIME_WAIT | TCP 연결 종료 후 일정 시간 포트 재사용 불가 상태 |
| SO_REUSEADDR | TIME_WAIT 중인 포트를 즉시 재사용 가능 |
| Nagle 알고리즘 | 작은 패킷을 합쳐 전송 효율 향상 (지연 가능성 있음) |
| 방화벽 / ACL | 포트 접근 차단 여부 확인 필수 |
| loopback / LAN IP / public IP | IP의 범위와 통신 가능 범위 구분 |
[서버] [클라이언트]
socket() socket()
bind() connect()
listen() (3-way handshake)
accept() ⇄ connect() → 연결 성립
recv() ⇄ send() ← 양방향 데이터 교환
closesocket() closesocket()
이 구조는 OS, 언어와 상관없이 동일하다.
(Windows, Linux, macOS, C, C++, Python, Java 전부 같은 개념)
| 단계 | 학습 내용 | 상태 |
|---|---|---|
| Winsock API 기초 | WSAStartup, socket, bind, listen | ✅ |
| 서버 대기 / 클라이언트 연결 | accept, connect | ✅ |
| 루프백 통신 | 127.0.0.1 실습 | ✅ |
| Winsock 버전 / pragma | 링크 원리 | ✅ |
| inet_addr → InetPton | 신식 API 이해 | ✅ |
| 소켓 구조 및 원리 | TCP / 버퍼 / 엔디언 / 블로킹 | ✅ |
| 서버·클라이언트 역할 지속성 | 개념 완전 정리 | ✅ |
소켓은 운영체제가 제공하는 네트워크용 파일 핸들이며,
TCP는 그 위에서 신뢰성과 순서를 보장하는 프로토콜이다.
서버와 클라이언트는 연결을 맺은 순간부터
대등하게 데이터를 주고받을 수 있지만,
연결을 시작한 쪽과 대기한 쪽의 역할은 세션이 끝날 때까지 유지된다.
이로써 당신은
📘 다음 추천 학습 경로:
send() / recv() 로 실제 문자열 송수신 (에코 통신)/*
* 1단계 - 헤더 포함하기
*/
#include <stdio.h> // 표준 입출력
#include <winsock2.h> // 윈도우에서는 소켓 프로그래밍을 위하여 이 헤더 파일을 포함해야합니다.
#pragma comment(lib, "ws2_32.lib") // 소켓 함수는 winsock2.h 헤더 파일에 선언되어 있지만 실제 구현은 ws2_32.lib 라이브러리에 들어있습니다.
// 비주얼 스튜디오는 링커 설정에서 이 라이브러리를 추가해주어야 하지만, 위와 같이 소스 코드에 직접 명시할 수도 있습니다.
// 링커 설정에서 추가하는 방법은 "프로젝트 속성 -> 링커 -> 입력 -> 추가 종속성"에 "ws2_32.lib"를 추가하면 됩니다.
int main()
{
/*
* 2단계 - Winsock 초기화하기
* 모든 소켓 함수를 사용하기 전에 Windows에서는 반드시 Winsock 라이브러리를 초기화해야합니다.
* 이것은 리눅스에는 없는 Windows 특유의 절차입니다.
*/
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // MAKEWORK(2, 2)는 Winsock 2.2 버전을 사용하겠다는 의미입니다.
if (result != 0)
{
printf("WSAStartup() 오류: %d\n", result);
return 1;
}
// 시스템에 설치된 Winsock 버전이 2.2보다 낮으면 오류가 발생합니다.
/*
* 3단계 - 소켓 생성(TCP 스트림 지정)
*/
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // TCP 소켓을 생성합니다.
if (listen_sock == INVALID_SOCKET)
{
printf("socket() 오류: %d\n", WSAGetLastError());
WSACleanup(); // Winsock 종료
return 1;
}
/*
* 4단계 - (선택사항) 옵션 설정
*/
BOOL yes = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
// 서버가 종료된 후 바로 다시 실행할 때 이전에 사용하던 포트가 TIME_WAIT 상태로 남아있으면
// bind()에서 오류가 발생할 수 있습니다.
// SO_REUSEADDR 옵션을 설정하면 이런 문제를 방지할 수 있습니다(즉시 재사용이 가능).
// 리눅스에서는 기본적으로 이 옵션이 설정되어 있지만, 윈도우에서는 기본값이 FALSE이므로
// 반드시 위와 같이 설정해주어야 합니다.
// 서버를 빠르게 재사용할 수 있습니다.
/*
* 5단계 - 주소 구조체 설정
*/
struct sockaddr_in servaddr;
//ZeroMemory(&servaddr, sizeof(servaddr)); // 구조체 인스턴스를 0으로 초기화(윈도우 전용)
memset(&servaddr, 0, sizeof(servaddr)); // 구조체 인스턴스를 0으로 초기화(C/C++ 표준)
servaddr.sin_family = AF_INET; // 이거는 통신으로 전달되는 데이터가 아니므로 빅엔디안 이런거 필요 없음
servaddr.sin_port = htons(7777); // 빅엔디언 바이트 순서로(u short)
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 빅엔디안 바이트 순서로(u long)
/*
* 6단계 - 소켓과 주소를 바인딩
*/
if (bind(listen_sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) == SOCKET_ERROR)
{
printf("bind() 오류: %d\n", WSAGetLastError());
closesocket(listen_sock);
WSACleanup(); // Winsock 종료
return 1;
}
/*
* 7단계 - 클라이언트 접속 대기
*/
if (listen(listen_sock, SOMAXCONN) == SOCKET_ERROR)
{
printf("listen() 오류: %d\n", WSAGetLastError());
closesocket(listen_sock);
WSACleanup(); // Winsock 종료
return 1;
}
printf("서버가 클라이언트 접속을 대기 중입니다...\n");
/*
* 8단계 - 클라이언트 접속 허용
*/
struct sockaddr_in cliaddr;
int clilen = sizeof(cliaddr);
SOCKET client_sock = accept(listen_sock, (struct sockaddr*)&cliaddr, &clilen);
if (client_sock == INVALID_SOCKET)
{
printf("accept() 오류: %d\n", WSAGetLastError());
closesocket(listen_sock);
WSACleanup(); // Winsock 종료
return 1;
}
/*
* 9단계 - 접속한 클라이언트의 주소/포트 출력
*/
char isptr[16];
DWORD ip = ntohl(cliaddr.sin_addr.s_addr);
snprintf(isptr, sizeof(isptr), "%d.%d.%d.%d",
(ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
printf("클라이언트 접속 허용: %s:%d\n", isptr, ntohs(cliaddr.sin_port));
// 명시적으로 소켓과 Winsock 자원 해제
closesocket(client_sock);
closesocket(listen_sock);
WSACleanup();
return 0;
}
#define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr이 deprecated됨을 안내하는 경고를 무시합니다.
// 이것은 IPv6 도입되고 안정성을 개선하기 위해 InetPton()으로 바뀌었습니다. 어쨌든...
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed; %d\n", WSAGetLastError());
return 1;
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
printf("socket failed: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
struct sockaddr_in srv;
srv.sin_family = AF_INET;
srv.sin_port = htons(7777);
srv.sin_addr.s_addr = inet_addr("127.0.0.1");
printf("Connecting to server...\n");
if (connect(s, (struct sockaddr*)&srv, sizeof(srv)) == SOCKET_ERROR)
{
printf("connect failed: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
printf("Connected!\n");
// 명시적으로 소켓과 Winsock 자원 해제
closesocket(s);
WSACleanup();
return 0;
}