네트워크 프로그래밍은 웹 개발과 다릅니다. 웹 사이트를 만드는 것이 아니라, 서로 다른 위치에 있는 컴퓨터들이 데이터를 주고받을 수 있도록 연결하는 기술입니다. 통신 규약(프로토콜)을 통해 데이터 전송 규칙을 정하고, 하드웨어와 소프트웨어 인터페이스를 구현하여 노드 간 정보 교환을 가능하게 합니다.
OSI 계층 모델과 프로토콜 비교
| 특성 | TCP | UDP |
|---|---|---|
| 연결 방식 | 연결 지향 | 비연결 |
| 신뢰성 | 높음 (순서 보장, 재전송) | 낮음 (순서 미보장) |
| 속도 | 상대적으로 느림 | 빠름 |
| 용도 | 파일 전송, 이메일 | 실시간 스트리밍, 게임 |
TCP 소켓 통신 구현
다음은 기본적인 에코 서버-클라이언트 예제입니다. 서버는 클라이언트의 접속을 대기하며, 연결 후 메시지를 수신합니다.
import java.io.*;
import java.net.*;
public class TcpServerBasic {
public static void main(String[] args) {
try (ServerSocket listener = new ServerSocket(7777)) {
System.out.println("서버 대기 중...");
Socket client = listener.accept();
System.out.println("클라이언트 연결됨: " + client.getInetAddress());
BufferedReader reader = new BufferedReader(
new InputStreamReader(client.getInputStream())
);
String received = reader.readLine();
System.out.println("수신: " + received);
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.*;
import java.net.*;
public class TcpClientBasic {
public static void main(String[] args) {
try (Socket channel = new Socket("localhost", 7777)) {
PrintWriter sender = new PrintWriter(
channel.getOutputStream(), true
);
sender.println("안녕하세요, 서버!");
} catch (IOException e) {
e.printStackTrace();
}
}
}양방향 TCP 통신 예제
실무에서는 요청-응답 패턴이 일반적입니다. 서버가 클라이언트에게 응답을 보내는 방식으로 구현합니다.
import java.io.*;
import java.net.*;
public class EchoService {
public static void main(String[] args) {
try (ServerSocket gateway = new ServerSocket(6666)) {
while (true) {
Socket peer = gateway.accept();
handleConnection(peer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleConnection(Socket peer) {
try (peer;
BufferedReader in = new BufferedReader(
new InputStreamReader(peer.getInputStream()));
PrintWriter out = new PrintWriter(
peer.getOutputStream(), true)) {
String payload = in.readLine();
System.out.println("클라이언트 메시지: " + payload);
out.println("ECHO: " + payload);
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.*;
import java.net.*;
public class EchoRequester {
public static void main(String[] args) {
try (Socket pipe = new Socket("127.0.0.1", 6666);
BufferedReader responseReader = new BufferedReader(
new InputStreamReader(pipe.getInputStream()));
PrintWriter requestWriter = new PrintWriter(
pipe.getOutputStream(), true)) {
requestWriter.println("반갑습니다");
String reply = responseReader.readLine();
System.out.println("서버 응답: " + reply);
} catch (IOException e) {
e.printStackTrace();
}
}
}UDP 데이터그램 통신
UDP는 연결 설정 없이 즉시 데이터를 전송합니다. DatagramPacket과 DatagramSocket을 사용하며, 각 패킷은 독립적으로 목적지로 전달됩니다.
import java.net.*;
public class UdpReceiver {
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket(5555)) {
byte[] bucket = new byte[2048];
DatagramPacket packet = new DatagramPacket(bucket, bucket.length);
while (true) {
socket.receive(packet);
String text = new String(packet.getData(), 0,
packet.getLength(), "UTF-8");
System.out.println("[" + packet.getAddress() + "] " + text);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}import java.net.*;
public class UdpSender {
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket()) {
String message = "UDP 메시지 전송";
byte[] payload = message.getBytes("UTF-8");
DatagramPacket packet = new DatagramPacket(
payload,
payload.length,
InetAddress.getByName("127.0.0.1"),
5555
);
socket.send(packet);
System.out.println("전송 완료");
} catch (Exception e) {
e.printStackTrace();
}
}
}UDP 바이너리 데이터 전송
원시 타입이나 객체를 전송할 때는 ByteArray 스트림을 활용하여 바이트 배열로 변환합니다.
import java.io.*;
import java.net.*;
public class BinaryUdpServer {
public static void main(String[] args) {
try (DatagramSocket endpoint = new DatagramSocket(4444)) {
byte[] cache = new byte[1024];
DatagramPacket envelope = new DatagramPacket(cache, cache.length);
endpoint.receive(envelope);
ByteArrayInputStream raw = new ByteArrayInputStream(
envelope.getData(), 0, envelope.getLength());
DataInputStream decoder = new DataInputStream(raw);
double value = decoder.readDouble();
boolean flag = decoder.readBoolean();
System.out.printf("받은 데이터: %.2f, %b%n", value, flag);
} catch (Exception e) {
e.printStackTrace();
}
}
}import java.io.*;
import java.net.*;
public class BinaryUdpClient {
public static void main(String[] args) {
try (DatagramSocket endpoint = new DatagramSocket()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream encoder = new DataOutputStream(buffer);
encoder.writeDouble(3.14159);
encoder.writeBoolean(true);
encoder.flush();
byte[] payload = buffer.toByteArray();
DatagramEnvelope envelope = new DatagramPacket(
payload,
payload.length,
InetAddress.getByName("127.0.0.1"),
4444
);
endpoint.send(envelope);
} catch (Exception e) {
e.printStackTrace();
}
}
}포트 번호 선택 가이드
- 0 ~ 1023: 알려진 포트(HTTP 80, HTTPS 443 등), 사용 금지
- 1024 ~ 49151: 등록된 포트, 충돌 주의
- 49152 ~ 65535: 동적/사설 포트, 사용자 정의에 적합
- TCP와 UDP는 독립적인 포트 공간을 가짐(같은 번호 사용 가능)
핵심 클래스 정리
| TCP | UDP |
|---|---|
| ServerSocket | DatagramSocket |
| Socket | DatagramPacket |
| InputStream/OutputStream | ByteArray 스트림(직접 구성) |