자바

week10: 자바 네트워크

aerimi-code 2025. 11. 9. 23:18

네트워크 기초 개념: 

ip와 port를 이용한 통신

 

 

IP주소 : 장치를 식별하는 고유한 숫자 주소

포트: 하나의 IP주소 내에서 여러 프로그램을 구분하는 번호.

 

=> 네트워크 통신은 IP주소 + 포트 번호를 조합하여 어떤 컴퓨터의 어떤 프로그램으로 정보를 보낼지 지정한다.

예: 127.0.0.1:8080

 

URL클래스:

프로토콜: 통신 규칙과 데이터 전송 방식 (TCP, UDP, HTTP 등), 통신언어

 

TCP/UDP:

연결방식 연결 지향 (1:1 연결 필수) 비연결 지향(그냥 데이터 보내)
신뢰성 높음 (순서 보장, 누락 없음) 낮음 (순서/누락 가능)
속도 상대적으로 느림 빠름
오류처리 수신 확인, 재전송 있음 수신 확인 없음
사용 예 채팅, 파일 전송, 웹 온라인 게임, 스트리밍
Java 클래스 Socket, ServerSocket DatagramSocket

 


소켓 프로그래밍의 구조: 서버와 클라이언트

 

 

서버와 클라이언트의 역할:

서버 (ServerSocket)

  • 클라이언트의 연결 요청을 기다림
  • 고정된 IP와 포트 번호 사용
ServerSocket listener = new ServerSocket(50023);
Socket socket = listener.accept(); // 연결 대기

 

클라이언트 (Socket)

  • 서버에 연결을 요청
Socket socket = new Socket("192.168.0.1", 50023);

 

 

socket:

프로그램이 네트워크를 통해 데이터를 주고받을 수 있도록 OS가 제공하는 창구 (시작점!).

IP주소와 포트 번호가 합쳐진 형태로 특정 프로그램을 식별한다.

 

네트워크 통신: 소켓과 소켓과의 데이터 교환

 

자바는 소켓 통신이 가능한 라이브러리를 지원한다. 

 

자바에서의 통신프로그램 구조

 

소켓을 만들어 소켓끼리 연결하고 정보를 주고 받는다. 

 

서버는 두개의 소켓을 가지고 있다. 

이유: ServerSocket은 listener socket으로 연결 요청을 듣고 오직 기다리는 역할만 한다. (문지기, 전화 교환원)

Socket은 ServerSocket이 요청을 수락(accept)할때마다 생성한다. 

 

주요 메소드

ServerSocket
Socket

 

통신과정:

 

  • 서버: 서버 소켓 생성 → 연결 대기(listen)
SocketSocket serverSocket = new ServerSocket(50023);

 

  • 클라이언트: 서버에 연결 요청
Socket s = new Socket("192.164.45.61", 50023);
  • 서버: 연결 수락(accept) → 새 소켓 생성
Socket socket = serverSocket.accept();

 

  • 양쪽: 입출력 스트림 생성하여 데이터 송수신
Scanner input = new Scanner(socket.getInputStream());
PrintWriter output = new PrintWriter(socket.getOutputStream(), true);

 

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

 


🌊 스트림: 데이터가 흐르는 통로 

 

네트워크에서는 통로(Socket)를 통해 받은 데이터를 우리가 보기 편한 형태(문자열, 숫자)로 읽고 쓰는 과정을 거쳐야한다.

이 과정에 스트림이 쓰인다. 

 

네트워크 통신에서는 InputStream과 OutputStream은 소켓을 통해 연결된 두 지점 사이의 데이터 흐름(통로)을 나타낸다.

 

클래스 역할 비유
socket.getInputStream() 데이터가 들어오는 통로 소켓 창구로 들어오는 물을 담는 파이프
socket.getOutputStream() 데이터가 나가는 통로 소켓 창구에서 나가는 물을 흘려보내는 파이프

주의: 이 기본 스트림들은 데이터를 바이트(byte) 단위로 처리한다. 

 

 

하지만 InputStream과 OutputStream은 바이트단위로 처리하기 때문에, 사람이 읽을 수 있는 언어로 변환해주는 보조 클래스가 필요하다. (Scanner, PrintWriter, BufferedReader, BufferedWriter)

 

이 클래스들을 데코레이터 패턴 처럼 기본 스트림에 덧붙여 기능을 확장하는 것이다.

 

 

데이터 입력(읽기) 계열: InputStream 처리

클래스 역할 인자 (socket.getInputStream()) 사용하는 이유
Scanner 문자열/숫자를 쉽게 읽음 InputStream **줄 단위(nextLine())**나 공백 단위로 데이터를 읽기 가장 편리합니다.
BufferedReader 문자를 효율적으로 읽음 InputStreamReader 대용량의 데이터를 **줄 단위(readLine())**로 읽을 때 성능이 좋고 효율적입니다.
InputStreamReader 바이트 → 문자 변환 InputStream 바이트 스트림을 문자 스트림으로 변환하는 중간 역할을 합니다.

 

코드 해석:

Scanner input = new Scanner(socket.getInputStream());
//or
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

 

1.

  • socket.getInputStream(): 소켓에서 바이트 단위로 들어오는 통로를 얻는다.
  • new Scanner(...): 그 바이트 통로를 Scanner에 넣어, 문자열 형태로 데이터를 쉽게 읽을 수 있게 만든다.

2.

  • socket.getInputStream(): 바이트 통로를 얻습니다.
  • new InputStreamReader(...): 바이트를 문자로 변환합니다.
  • new BufferedReader(...): 변환된 문자를 버퍼(Buffer)에 모아뒀다가 효율적으로 한 줄씩 읽습니다.

 

 

 

 

데이터 출력(쓰기) 계열: OutputStream 처리

클래스 역할 인자 (socket.getOutputStream()) 사용하는 이유
PrintWriter 데이터를 쉽게 출력 OutputStream 문자열을 출력하기 가장 편리하며, println()으로 자동 줄바꿈 및 자동 플러시 설정이 가능합니다.
BufferedWriter 문자를 효율적으로 출력 OutputStreamWriter 대용량의 데이터를 출력할 때 성능이 좋고 효율적입니다.
OutputStreamWriter 문자 → 바이트 변환 OutputStream 문자 스트림을 네트워크가 사용하는 바이트 스트림으로 변환하는 중간 역할을 합니다.

 

코드해석: 

PrintWriter output = new PrintWriter(socket.getOutputStream(), true);
//or
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

 

1.

  • socket.getOutputStream(): 소켓으로 바이트 단위로 나가는 통로를 얻는다.
  • new PrintWriter(...): 그 바스트 통로에 문자열을 쉽게 쓸 수 있게 함.
  • true: Auto Flush(자동 플러시) 설정. 데이터를 쓸 때마다 버퍼를 비워(flush) 바로 네트워크로 전송하라는 의미 (네트워크 통신에서는 필수적)

2.

  • socket.getOutputStream(): 바이트 통로를 얻습니다.
  • new OutputStreamWriter(...): 문자를 바이트로 변환
  • new BufferedWriter(...): 변환할 문자를 버퍼에 모아뒀다가 한 번에 효율적으로 출력
  1.  

 

📦 버퍼란?

데이터가 임시로 저장되는 작은 임시 메모리 공간. 

 

❓ 왜 버퍼에 모으나?  

컴퓨터가 데이터를 하나하나 처리하는 것 -> 매우 비효율적 '

예시: 네트워크나 하드디스크에 데이터를 보낼 때는 데이터를 보낼 때 마다 시스템에 요청을 해야한다. -> 시간 소요

(문자 1개당 요청 필요)

 

=> 버퍼에 다 모으고 전송! -> 성능 빨라짐

 

스트림 생성 예제

// 입출력 스트림 생성
Scanner input = new Scanner(socket.getInputStream());
PrintWriter output = new PrintWriter(socket.getOutputStream(), true);

// 데이터 송수신
output.println("Hello Server!"); // 전송
String message = input.nextLine(); // 수신

 

양방향 통신

 

문제점: 

단순 반복문으로 읽고 쓰기를 하면 blocking 발생합니다. 한쪽이 입력을 기다리는 동안 다른 쪽은 대기 상태가 되어 실시간 통신이 불가능합니다.

 

해결: 멀티스레드

  • 읽기 스레드: 상대방 메시지 수신 담당
  • 쓰기 스레드: 내 메시지 전송 담당
  • 두 작업을 독립적으로 동시에 처리
// 여러 클라이언트 처리
while(true) {
    Socket socket = serverSocket.accept();
    new ServerThread(socket, listWriters).start(); // 각 클라이언트마다 스레드 생성
}