TCP 소켓 프로그래밍
TCP(Transmission Control Protocol) 소켓 프로그래밍은 클라이언트와 서버 간에 신뢰성 있는 1:1 연결 기반 의 통신을 구현하는 방식입니다. 전화기를 통해 상대방과 직접 연결되어 대화를 나누는 것과 매우 유사합니다.
네트워크를 통한 통신은 결국 앞서 15장에서 배운 입출력(I/O) 스트림 을 통해 데이터를 주고받는 과정입니다. 여기서 소켓(Socket) 은 이 입출력 스트림의 끝단(End-point) 역할을 하는 연결 단자입니다.
1. TCP 소켓의 핵심 구조
TCP 통신을 위해서는 서버 측의 역할과 클라이언트 측의 역할이 명확히 나뉩니다.
- 서버 (ServerSocket): 전화기의 모체(기지국) 역할입니다. 특정 포트(Port)를 열어놓고 대기(
accept)하다가, 클라이언트가 접속하면 이 클라이언트와 1:1로 통신할 수 있는 일반Socket하나를 새로 만들어 줍니다. - 클라이언트 (Socket): 상대방 컴퓨터의 IP와 열어둔 포트 번호를 갖고 연결을 요청합니다.
2. 간단한 에코(Echo) 서버 만들기
클라이언트가 보낸 메시지를 받아서 다시 그대로 돌려주는 에코 단방향 서버와 클라이언트의 구조입니다.
서버 측 (Server) 구현
import java.io.*;
import java.net.*;
public class TcpServer {
public static void main(String[] args) {
try {
// 1. 7777번 포트에서 클라이언트의 접속을 대기하는 ServerSocket 생성
ServerSocket serverSocket = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다. 클라이언트를 대기합니다.");
// 2. 클라이언트 접속 시 통신용 전용 Socket을 반환
Socket socket = serverSocket.accept();
System.out.println("클라이언트가 접속했습니다!");
// 3. 소켓을 통해 데이터를 읽을 수 있는 입력 스트림 생성
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 4. 소켓을 통해 데이터를 보낼 수 있는 출력 스트림 생성
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 5. 클라이언트가 넘긴 메시지를 읽고 다시 보냄
String clientMsg = in.readLine();
System.out.println("수신 메세지: " + clientMsg);
out.println("Server Echo: " + clientMsg);
// 사용 후 반드시 안전하게 닫기
in.close();
out.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
클라이언트 측 (Client) 구현
import java.io.*;
import java.net.*;
public class TcpClient {
public static void main(String[] args) {
try {
// 1. 서버의 IP 주소(로컬호스트)와 7777 포트로 연결 요청
Socket socket = new Socket("127.0.0.1", 7777);
System.out.println("서버에 성공적으로 연결되었습니다.");
// 2. 스트림 구성
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 3. 서버로 메시지 전송
out.println("안녕하세요! TCP 클라이언트입니다.");
// 4. 서버가 돌려준 에코 메시지 수신 및 출력
System.out.println("서버로부터 온 답장: " + in.readLine());
// 자원 해제
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
이처럼 실시간으로 데이터를 주고받을 수 있지만, 접속하는 사용자가 많아진다면 accept() 단계에서 블로킹(대기)이 발생하므로 멀티 쓰레드나 비동기 I/O(NIO)를 함께 활용해야만 실제 서비스가 가능해집니다.