네트워크를 공부하다 보면 소켓이라는 단어를 자주 만나게 됩니다.
처음에는 소켓을 서버와 클라이언트를 연결할 때 사용하는 코드 정도로만 생각했습니다.
그런데 웹 서버, 채팅 서버, 게임 서버처럼 네트워크를 사용하는 구조를 계속 보다 보니
소켓은 단순한 코드 객체가 아니라 통신의 중심에 가까운 개념이라는 생각이 들었습니다.
그래서 소켓이 정확히 무엇이고, TCP 통신에서 어떤 역할을 하는지 직접 확인해보고 싶었습니다.
이번에는 Java로 간단한 TCP 채팅 서버와 클라이언트를 만들어 보면서 소켓의 흐름을 따라가 보았습니다.
중요하게 보고 싶었던 부분은 채팅 기능 자체가 아니었습니다.
소켓이 무엇인지, 소켓을 통해 데이터가 어떻게 이동하는지, 그리고 연결이 어떤 흐름으로 시작되고 종료되는지를
확인하는 것이 목적이었습니다.
소켓이란 무엇인가
소켓은 네트워크 통신에서 애플리케이션이 데이터를 주고받기 위해 사용하는 통신의 끝점입니다.
여기서 끝점이라는 말은 데이터가 들어오고 나가는 입구라고 이해할 수 있습니다.
네트워크 통신은 한쪽 프로그램만으로 이루어지지 않습니다.
데이터를 보내는 쪽과 받는 쪽이 있어야 하고, 양쪽 프로그램에는 각각 데이터를 주고받기 위한 소켓이 필요합니다.
두 소켓이 연결되면 프로그램 사이에 데이터를 주고받을 수 있는 통신 경로가 만들어집니다.
조금 더 정확히 말하면 소켓은 애플리케이션이 운영체제의 네트워크 기능을 사용할 수 있게 해주는 인터페이스입니다.
프로그램이 직접 TCP의 내부 동작을 모두 처리하는 것이 아니라,
운영체제가 제공하는 소켓이라는 창구를 통해 데이터를 보내고 받는 구조입니다.
소켓은 애플리케이션과 운영체제의 네트워크 계층 사이에 있는 통신 창구라고 볼 수 있습니다.
이 점에서 소켓은 단순히 연결을 만드는 코드가 아니라,
네트워크 통신이 애플리케이션으로 들어오고 나가는 경계처럼 느껴졌습니다.
TCP 소켓이 의미하는 것
TCP 소켓은 TCP 연결 위에서 데이터를 주고받기 위한 소켓입니다.
TCP는 데이터를 보내기 전에 먼저 연결을 만들고, 연결이 만들어진 뒤에는 그 연결을 통해 데이터를 주고받는 방식입니다.
애플리케이션은 이 TCP 연결을 직접 세세하게 다루지 않습니다.
프로그램 입장에서는 소켓에 데이터를 쓰면 상대방에게 데이터가 전달되고,
소켓에서 데이터를 읽으면 상대방이 보낸 데이터를 받을 수 있습니다.
그 사이에서 운영체제의 TCP 스택은 연결 관리, 순서 관리, 재전송 같은 일을 처리합니다.
애플리케이션은 이 복잡한 과정을 직접 구현하지 않고, 소켓을 통해 보내기와 받기를 수행합니다.
그래서 TCP 소켓은 애플리케이션이 TCP 연결을 사용할 수 있게 해주는 통신 창구라고 볼 수 있었습니다.
ServerSocket과 Socket의 차이
TCP 서버를 만들 때는 ServerSocket과 Socket이 함께 등장합니다.
처음에는 두 개가 비슷한 개념처럼 보였지만, 실제 역할은 분명히 달랐습니다.
ServerSocket은 클라이언트의 연결 요청을 기다리는 역할을 합니다.
반면 Socket은 연결이 만들어진 뒤 실제 데이터를 주고받는 역할을 합니다.
ServerSocket은 대기용이고 Socket은 통신용입니다.
비유하면 ServerSocket은 가게 입구와 같습니다.
가게 입구는 손님이 들어오기를 기다리는 역할을 하지만, 손님이 들어온 뒤 실제 대화는 입구와 하지 않습니다.
직원과 손님 사이에서 대화가 이루어집니다.
이때 직원과 손님 사이의 대화 통로가 Socket이라고 볼 수 있습니다.
중요한 점은 ServerSocket이 직접 채팅 메시지를 주고받는 것이 아니라는 점입니다.
ServerSocket은 연결 요청을 기다리고 수락하는 역할을 하며,
실제 데이터 통신은 연결이 수락된 뒤 만들어진 Socket이 담당합니다.
이 차이를 이해하고 나니 서버 쪽 소켓 구조가 훨씬 명확해졌습니다.
서버가 먼저 기다리는 이유
TCP 통신에서는 서버가 먼저 기다리는 상태가 되어야 합니다.
서버가 대기하고 있어야 클라이언트가 연결을 요청할 수 있기 때문입니다.
이번 실습에서도 서버를 먼저 실행했습니다.
서버는 ServerSocket을 통해 클라이언트의 연결 요청을 기다렸고,
이 상태는 아직 데이터를 주고받는 상태가 아니라 누군가 접속하기를 기다리는 상태였습니다.
네트워크 관점에서는 이 단계를 Listening 상태라고 볼 수 있습니다.
Listening 상태는 TCP Socket Lifecycle의 시작점입니다.


연결은 언제 만들어지는가
서버가 기다리는 상태에서 클라이언트가 연결을 요청하면 TCP 연결이 만들어집니다.
이때 내부적으로는 TCP 3way handshake 과정이 일어납니다.
쉽게 말하면 클라이언트와 서버가 서로 통신할 준비가 되었는지 확인하는 과정입니다.
이 과정이 끝나면 양쪽 프로그램 사이에 TCP 연결이 성립됩니다.
Java 코드에서는 이 과정을 직접 하나씩 구현하지 않았습니다.
운영체제와 Java의 소켓 API가 내부 과정을 처리해주었기 때문에,
프로그램 입장에서는 연결이 성공하면 Socket을 통해 데이터를 주고받을 수 있는 상태가 된 것처럼 보였습니다.
여기서 중요한 점은 연결이 만들어질 때마다 통신용 Socket이 생긴다는 점입니다.
서버는 하나의 ServerSocket으로 여러 클라이언트의 연결 요청을 받을 수 있지만,
실제 통신은 클라이언트마다 만들어진 각각의 Socket으로 이루어집니다.
클라이언트가 두 명이면 서버는 두 클라이언트와 각각 다른 Socket 연결을 가지게 됩니다.
모든 클라이언트가 하나의 통신 통로를 공유하는 것이 아니라,
각 클라이언트마다 독립적인 TCP 연결이 만들어지는 구조였습니다.
소켓으로 데이터를 보낸다는 것
소켓으로 데이터를 보낸다는 것은 애플리케이션이 운영체제에게 데이터를 넘긴다는 의미입니다.
애플리케이션이 소켓의 출력 방향으로 데이터를 쓰면,
운영체제는 그 데이터를 TCP 연결을 통해 상대방에게 보냅니다.
상대방 운영체제는 받은 데이터를 상대 프로그램의 소켓으로 전달하고,
상대 프로그램은 소켓의 입력 방향에서 그 데이터를 읽습니다.
흐름을 단순하게 보면 다음과 같습니다.
내 프로그램
-> 내 소켓
-> 운영체제의 TCP 처리
-> 네트워크
-> 상대 운영체제의 TCP 처리
-> 상대 소켓
-> 상대 프로그램
이 흐름에서 애플리케이션이 직접 네트워크 패킷을 조립하지 않는다는 점이 중요했습니다
.
프로그램은 소켓에 데이터를 쓰고, 소켓에서 데이터를 읽습니다.
그 사이의 TCP 처리와 전송 과정은 운영체제가 담당합니다.
그래서 소켓은 애플리케이션과 네트워크 사이를 이어주는 실질적인 입출구였습니다.
데이터는 문자열이 아니라 바이트로 이동한다
채팅 프로그램에서는 사용자가 문자열을 입력하지만,
TCP가 실제로 다루는 것은 문자열이 아닙니다.
TCP가 다루는 것은 바이트 흐름입니다.
예를 들어 사용자가 "안녕하세요"라고 입력하면 이 문자열은 먼저 바이트로 변환됩니다.
이번 실습에서는 UTF-8 인코딩을 사용했습니다.
문자열이 UTF-8 바이트로 바뀐 뒤 TCP 소켓을 통해 상대방에게 전달되고,
상대방은 받은 바이트를 다시 문자열로 해석합니다.
흐름은 다음과 같았습니다.
문자열 입력
-> UTF-8 바이트로 변환
-> TCP Socket을 통해 전송
-> 상대방 Socket에 도착
-> 다시 문자열로 해석
이 과정을 통해 네트워크에서 이동하는 데이터의 실제 형태는 문자열이 아니라 바이트라는 점을 확인할 수 있었습니다.
소켓은 문자열을 이해하는 것이 아니라 바이트를 주고받는 통로입니다.
우리가 채팅 메시지처럼 볼 수 있었던 것은 프로그램이 바이트를 문자열로 변환해서 보여주었기 때문입니다.
TCP는 메시지 단위를 모른다
처음에는 TCP가 메시지를 하나씩 알아서 구분해준다고 생각하기 쉬웠습니다.
하지만 TCP는 메시지 단위가 아니라 바이트 스트림 방식으로 동작합니다.
바이트 스트림이라는 것은 데이터가 길게 이어진 흐름처럼 전달된다는 뜻입니다.
TCP 입장에서는 어디까지가 첫 번째 메시지이고, 어디부터가 두 번째 메시지인지 알지 못합니다.
그 구분은 애플리케이션이 정해야 합니다.
이번 실습에서는 한 줄을 메시지 하나로 정했습니다. 즉, 줄바꿈을 기준으로 메시지를 구분했습니다.
보내는 쪽은 한 줄을 보냈고, 받는 쪽은 한 줄을 읽었습니다.
이 규칙이 있었기 때문에 채팅 메시지를 하나씩 구분할 수 있었습니다.
이 부분을 통해 소켓 통신에서는 데이터 전송뿐 아니라 메시지를 나누는 규칙도 중요하다는 것을 알 수 있었습니다.
소켓과 스트림
Java에서 TCP 소켓을 사용하면 입력 스트림과 출력 스트림을 얻을 수 있습니다.
여기서 스트림은 소켓 안에 따로 들어 있는 독립적인 프로세스가 아니라,
연결된 소켓을 읽고 쓰기 위한 방향이라고 이해하는 것이 자연스러웠습니다.
입력 스트림은 상대방이 보낸 데이터를 읽는 방향입니다.
출력 스트림은 내 프로그램에서 상대방으로 데이터를 보내는 방향입니다.
즉, Socket은 TCP 연결의 끝점이고,
InputStream과 OutputStream은 그 연결 위에서 데이터를 읽고 쓰는 통로처럼 사용할 수 있었습니다.
이 구조 덕분에 프로그램은 같은 Socket을 통해 데이터를 받을 수도 있고 보낼 수도 있었습니다.
서버는 왜 메시지를 중계하는가
이번 채팅 구조에서 클라이언트끼리 직접 연결된 것은 아니었습니다.
클라이언트가 보낸 메시지는 먼저 서버로 이동했고,
서버는 그 메시지를 읽은 뒤 다른 클라이언트에게 다시 전달했습니다.
데이터 흐름은 다음과 같았습니다.
hooni 클라이언트
-> 서버
-> friend 클라이언트
이 구조에서 서버는 중계자 역할을 했습니다.
서버가 메시지를 받아 다시 전달했기 때문에 여러 클라이언트가 하나의 채팅방에 있는 것처럼 동작할 수 있었습니다.
클라이언트가 많아져도 각 클라이언트는 서버와만 연결하면 됩니다.
그리고 서버가 각 클라이언트의 Socket을 통해 메시지를 전달하면 됩니다.
이 구조를 통해 실제 채팅 서비스가 왜 서버 중심으로 동작하는지 이해할 수 있었습니다.

여러 소켓 연결을 동시에 다루는 문제
서버는 보통 하나의 클라이언트만 상대하지 않습니다.
여러 클라이언트가 동시에 접속할 수 있습니다.
이때 서버가 한 클라이언트의 입력만 계속 기다리면 다른 클라이언트의 메시지를 처리할 수 없습니다.
TCP 연결은 클라이언트마다 따로 존재하기 때문에 서버도 각 Socket 연결을 따로 관리해야 합니다.
이번 실습에서는 클라이언트마다 별도의 실행 흐름을 두었습니다.
이것은 단순히 코드 구조를 나누기 위한 것이 아니라
여러 TCP 연결을 동시에 처리하기 위한 네트워크 서버의 기본 구조였습니다.
각 클라이언트의 Socket은 독립적으로 데이터를 받을 수 있습니다.
서버는 이 여러 Socket을 동시에 다룰 수 있어야 했습니다.
이 과정을 통해 소켓 통신은 연결 하나만 이해해서 끝나는 것이 아니라,
여러 연결을 어떻게 함께 처리할지도 중요하다는 것을 알 수 있었습니다.
연결 종료도 lifecycle의 일부다
네트워크 통신은 연결을 만드는 것만큼 종료도 중요합니다.
클라이언트가 더 이상 통신하지 않는다면 Socket을 닫아야 합니다.
Socket을 닫는다는 것은 해당 TCP 연결을 더 이상 사용하지 않겠다는 뜻입니다.
이번 실습에서는 클라이언트가 /quit을 입력하면 연결이 종료되도록 했습니다.
클라이언트가 나가면 서버는 해당 클라이언트를 목록에서 제거했고,
다른 클라이언트에게는 퇴장 메시지가 전달되었습니다.
이 과정은 TCP Socket Lifecycle에서 Close 단계에 해당했습니다.
프로그램에서는 단순히 Socket을 닫는 것처럼 보이지만,
네트워크 내부에서는 연결을 정리하는 과정이 일어납니다.
사용하지 않는 소켓을 닫지 않으면 운영체제의 네트워크 자원이 남을 수 있습니다.
그래서 소켓을 닫는 것은 단순한 프로그램 종료가 아니라 네트워크 자원을 정리하는 과정이었습니다.
TCP Socket Lifecycle 정리
이번에 확인한 TCP Socket Lifecycle은 다음과 같았습니다.
Listening
-> Connection Established
-> Data Transfer
-> Close
Listening 단계에서는 서버가 연결 요청을 기다립니다.
Connection Established 단계에서는 클라이언트와 서버 사이에 TCP 연결이 만들어집니다.
Data Transfer 단계에서는 양쪽 Socket을 통해 바이트 데이터가 이동합니다.
Close 단계에서는 더 이상 사용하지 않는 연결을 닫고 네트워크 자원을 정리합니다.
이 흐름을 직접 확인해보니 소켓은 단순히 연결을 시작하는 도구가 아니었습니다.
소켓은 연결이 만들어지고, 데이터가 이동하고, 연결이 종료되는 전체 과정에 계속 관여했습니다.
직접 확인하면서 이해한 점
이번 실습을 통해 소켓은 네트워크 통신의 끝점이라는 말을 조금 더 구체적으로 이해할 수 있었습니다.
소켓은 애플리케이션이 운영체제의 네트워크 기능을 사용하기 위한 창구였습니다.
프로그램은 소켓에 데이터를 쓰고, 소켓에서 데이터를 읽었습니다.
그 사이에서 운영체제는 TCP 연결을 관리하고 데이터를 상대방에게 전달했습니다.
또한 ServerSocket과 Socket의 역할이 다르다는 점도 분명해졌습니다.
ServerSocket은 연결 요청을 기다리는 역할이었고,
Socket은 연결이 만들어진 뒤 실제 데이터를 주고받는 역할이었습니다.
그리고 TCP는 메시지 단위가 아니라 바이트 흐름을 전달한다는 점도 중요했습니다.
채팅 메시지처럼 보이게 하려면 애플리케이션에서 메시지를 나누는 규칙을 정해야 했습니다.
이번 실습에서는 줄바꿈을 기준으로 한 줄을 하나의 메시지로 처리했습니다.
마무리
처음에는 소켓을 서버와 클라이언트를 연결하는 코드 정도로 생각했습니다.
하지만 직접 확인해보니 소켓은 네트워크 통신의 시작부터 종료까지 계속 등장하는 핵심 구조였습니다.
서버는 ServerSocket으로 연결 요청을 기다렸고,
클라이언트가 접속하면 실제 통신용 Socket이 만들어졌습니다.
데이터는 이 Socket을 통해 바이트 흐름으로 이동했습니다.
서버는 여러 클라이언트의 Socket을 관리하면서 메시지를 중계했고,
통신이 끝나면 Socket을 닫아 연결과 자원을 정리했습니다.
결국 소켓은 네트워크를 사용하는 프로그램의 입출구였습니다.
TCP Socket Lifecycle은 그 입출구가 열리고, 연결되고, 데이터를 흘려보내고, 닫히는 전체 과정이었습니다.
이번 과정을 통해 네트워크 통신은 단순히 데이터를 보내는 한 줄의 코드가 아니라는 것을 알 수 있었습니다.
연결을 만들고, 데이터를 이동시키고, 메시지를 구분하고, 마지막에 연결을 정리하는 전체 흐름이 있었습니다.
그 흐름의 중심에 소켓이 있었습니다.
'Network' 카테고리의 다른 글
| TCP 혼잡제어 (0) | 2026.06.25 |
|---|---|
| T-Pot을 안전하게 운영하기 위한 DMZ 네트워크 설계 (0) | 2026.06.18 |
| MTR로 살펴보는 해외 웹사이트 네트워크 지연과 라우팅 경로 분석 (0) | 2026.06.01 |
| 케이블을 하나하나 따라가며 랩실 랙 네트워크 구조 파악하기 (0) | 2026.05.28 |
| BGP Hijacking (0) | 2026.05.21 |