Sockets for Clients

TaekJun Jung·2025년 12월 17일

Network

목록 보기
5/8

네트워크 프로그래밍

"Java Network Programming" 책을 기반으로 Sockets for Clients을 간략히 요약하여 정리했습니다.


데이터그램(Datagram)

인터넷은 데이터를 한 번에 통째로 보내지 못합니다.
'데이터그램' 또는 '패킷'이라고 불리는 작은 조각으로 쪼개서 보냅니다.

구조

  • 헤더(Header): 보내는 곳 포트, 받는 곳 포트, 체크섬 등

  • 페이로드(Payload): 실제 보내려는 데이터 내용물

문제점

데이터를 조각내서 보내다 보니 온갖 문제가 발생합니다.

  • 분할과 재조립: 큰 파일은 수천 개의 조각으로 잘라야 하고,
    도착하면 다시 순서대로 붙여야 함

  • 분실(Loss): 가는 도중에 조각이 사라질 수 있음 (재전송 필요)

  • 순서 뒤바뀜(Out of order): 1번보다 2번 조각이 먼저 도착할 수 있음
    (순서 재배치 필요)

  • 손상(Corruption): 데이터가 깨져서 올 수 있음


소켓(Sockets)

위의 모든 복잡한 작업을 대신 해주는 추상화 계층(Abstraction Layer)입니다.

복잡한 네트워크 연결을 단순한 스트림(Stream)처럼 다루게 해줍니다.

그저 소켓에서 InputStream을 얻어 읽고(read), OutputStream을 얻어 쓰면(write) 됩니다.


Using Sockets

소켓의 7가지 기본 동작

공통 (클라이언트 & 서버)

  • 1. 원격 머신에 연결 (Connect): 상대방에게 전화를 검
  • 2. 데이터 전송 (Send): 말을 함
  • 3. 데이터 수신 (Receive): 상대방 말을 들음
  • 4. 연결 종료 (Close): 전화 끊음

위는 java.net.Socket으로, 주로 클라이언트가 사용합니다.

서버 전용(Server Only)

  • 5. 포트에 바인딩 (Bind): "나 80번 포트에서 영업할게"라고 등록
  • 6. 데이터 수신 대기 (Listen): 손님 올 때까지 기다림
  • 7. 연결 수락 (Accept): 연결 받아들임

위는 java.net.ServerSocket으로, 서버가 사용합니다.


소켓 프로그래밍은 연결하고(Connect), 스트림을 얻어서(Stream),

읽고 쓰고(Read/Write), 끊는(Close) 과정의 반복입니다.


Reading from Servers with Sockets

Socket socket = new Socket("time.nist.gov", 13); // 소켓 연결 및 생성

단순히 객체만 만드는 것이 아니라, 이 순간 실제로 네트워크를 타고
서버(time.nist.gov)의 13번 포트로 TCP 연결을 시도합니다.

서버가 없거나 포트가 닫혀 있으면 IOException이 발생하므로
반드시 try-catch로 감싸야 합니다.


안전 장치: 타임아웃 설정(중요)

socket.setSoTimeout(15000); // 15초

서버가 연결은 받아줬는데, 데이터를 안 보내고 가만히 있는 경우(무응답)를 대비하는 것입니다.


자원 해제 (Resource Management): 소켓은 시스템의 포트와 리소스를 점유하므로, 사용이 끝나면 반드시 닫아야 합니다. (Close)

//Java 7 이상: try-with-resources 구문 사용
try (Socket socket = new Socket("time.nist.gov", 13)) {
	// ... 사용 ...
} // 블록 나가면 자동으로 close() 호출됨

데이터 읽기: 서버로부터 데이터를 읽어오는 과정은 파일 읽기와 매우 유사합니다.

  • 1. 스트림 획득: socket.getInputStream()으로 들어오는 통로를 염
  • 2. 변환: Daytime 프로토콜은 ASCII 텍스트를 보낸다고 명시되어 있음
    바이트를 문자로 바꾸기 위해 InputStreamReader를 사용하며, 인코딩을 지정
  • 3. 읽기: -1(스트림의 끝)이 나올 때까지 문자를 읽어 StringBuilder에 담음

1. 기준 시간(Epoch) 맞추기: 1900년 vs 1970년

가장 먼저 해야 할 일은 시간의 기준점을 맞추는 것입니다.

  • Time Protocol (RFC 868): 1900년 1월 1일 자정을 0으로 침
  • Java (Unix Time): 1970년 1월 1일 자정을 0으로 침

서버는 1900년 기준으로 알려주는데, 자바는 1970년부터 센 값을 원합니다.
그래서 그 차이(70년)만큼 빼줘야 합니다.

2. 비트 연산으로 데이터 조립하기 (가장 중요)

서버는 32비트 숫자(4바이트)를 1바이트씩 쪼개서 총 4번 보냅니다.
이 4개의 조각을 받아서 하나의 큰 숫자(long)로 합쳐야 합니다.

long secondsSince1900 = 0;

for (int i = 0; i < 4; i++) {
	secondsSince1900 = (secondsSince1900 << 8) | raw.read();
}
// 1. 기존에 있던 값 왼쪽으로 8칸(8비트) 밈
// 2. 새로 읽은 바이트(raw.read())를 빈 자리에 채워 넣음

3. 단위 변환 (초 → 밀리초)

자바의 Date 클래스 생성자는 밀리초(ms) 단위를 받습니다.
프로토콜은 초(s) 단위로 줍니다.

따라서 1000을 곱해줘야 합니다.

long msSince1970 = secondsdSince1970 * 1000;
Date time = new Date(msSince1970);

Writing to Servers with Sockets

핵심 개념: 쓰기(Writing)와 프로토콜

소켓 통신은 기본적으로 양방향(Full-duplex)입니다.
InputStream으로 듣고, OutputStream으로 말합니다.

DICT 프로토콜(Port 2628)

  • 명령어: DEFINE <사전 이름> <단어> (예: DEFINE eng-lat gold)
  • 종료 신호: 정의가 끝나면 마침표 . 하나만 있는 줄을 보냄
  • 상태 코드: 150(정의 시작), 250(명령 완료/성공), 552(단어 없음/실패)
  • 연결 종료: quit 명령어를 보내면 서버가 221 응답 후 연결을 끊음

자바 구현의 핵심 단계

서버에 데이터를 보낼 때 가장 주의해야 할 점은 버퍼(Buffer)플러시(Flush), 그리고 줄 바꿈 문자입니다.

A. 스트림 준비 (Writing)

OutputStream out = socket.getOutputStream();
Writer writer = new OutputStreamWriter(out, "UTF-8");
// 문자열 편하게 보내기 위해 Writer로 감싸고, 인코딩(UTF-8) 지정

Writer = new BufferedWriter(writer); // 성능

B. 명령어 전송 (The Critical Part)

writer.write("DEFINE eng-lat gold\r\n"); // 명령어 끝에 반드시 \r\n (CRLF) 포함
writer.flush(); // 버퍼 비우기 (매우 중요!)

\r\n: 대부분의 인터넷 프로토콜은 줄바꿈 문자로 명령의 끝을 인식합니다.
이 문자를 보내지 않으면 서버는 무한히 기다립니다.

flush(): BufferedWriter는 데이터가 꽉 찰때까지 전송을 미룹니다.
명령어가 짧으면 아예 전송되지 않을 수 있어 flush()를 호출해 강제로 밀어야합니다.


Constructing and Connecting Sockets

1. 소켓 생성자: 생성 즉시 연결 (Connect on Create)

Socket socket = new Socket("www.oreilly.com", 80);

생성자가 반환(return)되었다는 것은 연결에 성공했다는 뜻입니다.

2. 포트 스캐너 만들기

""연결에 성공하면 예외가 안 난다"를 역이용하면,
특정 서버의 어떤 Port가 열려있는지 확인하는 탐지기를 만들 수 있습니다.

작동원리

  1. 1번부터 1024번까지 포트 번호를 하나씩 바꿔가며 new Socket(host, i)를 시도

  2. 연결 성공(예외 없음): "아, 이 포트에는 서버가 있구나!"라고 판단 후 출력
    그리고 바로 끊음 (close)

  3. 연결 실패(IOException): 조용히 넘어감


new Socket(host, port)는 생성자이자 연결 시도 명령입니다.

연결이 되면 객체 생성, 안 되면 에러를 발생합니다.


Getting Information About a Socket

소켓은 연결이 성립되는 순간 4가지 고유한 정보를 갖게 됩니다.

1. 소켓의 4가지 핵심 정보 (Getter Methods)

소켓은 양방향 통신이므로, 상대방(Remote)의 정보와 나(Local)의 정보가 쌍으로 존재합니다.

상대방(Remote)

  • getInetAddress() → 상대방 IP 주소: 내가 접속하려고 한 서버의 주소

  • getPort() → 상대방 포트 번호: 보통 정해진 번호 (예: 웹:80, 텔넷:23)


나(Local)

  • getLocalAddress() → 나의 IP 주소: 내 컴퓨터의 네트워크 인터페이스 주소

  • getLocalPort() → 나의 포트 번호: 시스템이 랜덤하게 배정 (중요)

connect 되는 순간 확정되고, 중간에 변경이 불가능합니다. (Setter 메서드 없음)


2. Local Port 매번 바뀌는 이유

  • 서버 포트(Remote): www.oreilly.com의 80번 포트처럼, 모두가 아는 고정된 문

  • 클라이언트 포트(Local): 내 컴퓨터가 서버로 나갈 때 사용하는 문
    운영체제는 남는 포트 중 하나를 랜덤하게 골라서 할당

이유: 만약 내 포트도 80번으로 고정되어 있다면, 브라우저 창을 2개 띄웠을 때,
충돌이 발생합니다.

다른 포트를 쓰면 서버가 응답을 보낼 때 누가 요청했는지 구분이 가능합니다.

profile
e4 best by test

0개의 댓글