[Java] 20. 소켓 통신

김주희's avatar
Feb 26, 2025
[Java] 20. 소켓 통신
수정필요

1. 통신의 기본

notion image

2. Socket 통신

    notion image

    3. 단방향 통신 (Simplex)

    1. 단방향 통신

    1. 서버 측은 수신만 가능하고 클라이언트 측은 송신만 가능하다.

    2. 서버 측 코드

    package ex20.ch01; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; // 서버는 소켓 두 개 필요 public class MyServer01 { public static void main(String[] args) { try { // 20000번이라는 포트번호로 대기하고 있는 서버 소켓 생성 ServerSocket ss = new ServerSocket(20000); System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요."); // 소켓 연결되면 소켓 추가로 생성됨(포트번호는 랜덤) // 기본의 서버 소켓(다른 사람의 요청을 받아야 되기 때문)과의 연결은 끊기고 새로 생성된 소켓과 연결됨 Socket socket = ss.accept(); // 프로세스 대기 System.out.println("소켓이 연결되었습니다."); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); // 소켓만 읽으면 됨 알아서 상대방 컴퓨터에 연결되어있으니까 String body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음) System.out.println("서버 측: " + body); /* String body = ""; // 데이터 받을 곳 while (true) { // 한 번에 처리 못하는 큰 데이터일 수 있으니까 while 돌리기 body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음) // 최초에는 대기 // 여기서 대기 상태인데 다 찰 때까지 기다림. \n까지 소비 // 안녕\n 안녕\n 안녕 -> 세 번째 안녕은 null 처리됨 if (body == null) break; // (읽어봤을 때 값 없으면) 버퍼에 값 없으면 while } */ } catch (IOException e) { throw new RuntimeException(e); } } }

    3. 클라이언트 측 코드

    package ex20.ch01; import java.io.*; import java.net.Socket; // 단방향 통신 (Simplex) public class MyClient01 { public static void main(String[] args) { // loop back 주소 = localhost = 내 주소를 의미 try { Socket socket = new Socket("localhost", 20000); // 소켓 연결 // 키보드로부터 입력 받은 것을 쓸 거임 => 1. 키보드와 연결된 소켓/ 2. 서버와 연결된 소켓 BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); System.out.println("키보드 입력 대기 중....."); String msg = keyboard.readLine(); // 여기서 멈춰있음 BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()) ); //output - 앱 입장에서 쓰는거, input - 앱 입장에서 받는거 bw.write(msg); bw.write("\n"); // 중요~~~ 외우세용 bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } } }
     

    4. 코드 실행 과정

    Current File로 두고 Server 파일과 Client 파일을 각각 실행하기
    notion image
    1. MyServer01 파일을 실행한다.
      1. ServerSocket 이 만들어지고 연결 전 대기
      2. Socket socket = ss.accept(); // 여기서 대기
        notion image
    1. MyClient01 파일을 실행한다.
      1. Socket이 생성되고 서버 측 소켓과 연결
      2. BufferedReader를 통해 키보드로부터 입력받기 위해 대기
      3. String msg = keyboard.readLine(); // 여기서 멈춰 있음
        notion image
    1. 서버 - 클라이언트 소켓이 연결됨을 확인할 수 있다.
      1. 서버 소켓으로 클라이언트 측의 소켓이 생성되어 보낸 연결 요청을 받는다. (accept())
      2. 연결을 위한 소켓 생성
      3. 클라이언트 소켓과 연결
      4. BufferedReader를 통해 클라이언트 측에서 보낸 문자열 읽어들이기 위해 대기
      5. Socket socket = ss.accept(); // 정확히는 서버 파일 실행 시 여기서 대기 중임 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); String body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음)
        notion image
    1. 클라이언트 측에서 문자열 입력
      1. 키보드로 입력 받은 문자열을 BufferedWriter를 통해 서버 측으로 보낸다.
      2. 문자열을 읽어들일 수 있도록 반드시 ‘\n’이 필요하다.
      3. 버퍼가 다 차지 않더라도 보낼 수 있도록 flush()해준다.
      4. bw.write(msg); bw.write("\n"); bw.flush();
        notion image
    1. 서버 측에서 클라이언트가 보낸 문자열 확인
      1. notion image
     
     

    4. 반이중 통신 (Half Duplex)

    1. 반이중 통신

    1. 클라이언트가 보낸 데이터를 서버에서 받아서 확인만 하고 끝나는 게 아니라 받은 데이터에 대하여 응답을 돌려준다. (정보의 전송이 양방향임)
    1. 양 쪽에서 동시에 송수신이 가능한 전이중 통신(예 - 채팅)과는 달리 한 쪽이 송신하는 동안은 다른 쪽이 송신하지 못한다.
    1. 예 - 웹

    2. 서버 측 코드

    package ex20.ch02; import java.io.*; import java.net.ServerSocket; import java.net.Socket; // 반이중이니까 서버가 받아서 응답 -> BW public class MyServer02 { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(20000); System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요."); Socket socket = ss.accept(); System.out.println("소켓이 연결되었습니다."); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); // request String reqBody = br.readLine(); // response 응답할 body 내용 // 받은 내용 parsing해서 프로토콜 만들어서 응답 String respBody = ""; // 프로토콜 // 내가 만들었으니까 client에게 알려주어야 됨. if (reqBody.equals("name")) { respBody = "metacoding"; } else if (reqBody.equals("age")) { respBody = "39"; } else { respBody = "error"; } // 읽는 버퍼 & 쓰는 버퍼 -> 메모리에 두 개 떠 있음 BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()) ); bw.write(respBody); bw.write("\n"); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } } }

    3. 클라이언트 측 코드

    package ex20.ch02; import java.io.*; import java.net.Socket; // 반이중 Half Duplex (예 - 반이중) (전이중 - 채팅) public class MyClient02 { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 20000); BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); System.out.println("키보드 입력 대기 중....."); // client 입장에서 요청하는 것 - request String reqBody = keyboard.readLine(); // name, age BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()) ); bw.write(reqBody); bw.write("\n"); bw.flush(); // 응답 받음 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); String respBody = br.readLine(); System.out.println(respBody); } catch (IOException e) { throw new RuntimeException(e); } } }
     

    4. 코드 실행 과정

    1. 단방향 통신과 1,2,3번 과정 동일
      1. notion image
        notion image
        notion image
    1. 클라이언트 측에서 키보드로부터 데이터 입력받음
      1. 이때 키보드로부터 클라이언트가 요청하므로 reqBody라고 지정
      2. notion image
    1. 서버 측에서 클라이언트로부터 받은 데이터를 프로토콜 기준을 확인
      1. name을 보냈으므로 metacoding을 응답을 보내기 위한 respBody에 담는다
      2. BufferedWriter를 통해 클라이언트 측으로 respBody를 보낸다
      3.  
    1. 클라이언트 측에서 서버가 보낸 응답을 확인
      1. BufferedReader를 통해 서버가 보낸 응답 받아서 확인 가능하다.
      2. notion image
    1. 연결 종료
      1. notion image
     

    5. 전이중

     
     
    package ex20.ch03; import java.io.*; import java.net.ServerSocket; import java.net.Socket; // Full Duplex (전이중) // 논리적인 선 2개 / 비동기적 public class MyServer03 { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(20000); System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요."); Socket socket = ss.accept(); System.out.println("소켓이 연결되었습니다."); // 1. 쓰기 분신 BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); new Thread(() -> { while (true) { try { String reqBody = keyboard.readLine(); // 쓰기 스레드 대기 -> 굳이 sleep 안줘도 됨! bw.write(reqBody); bw.write("\n"); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } // 계속 true니까 BufferedReader 쪽에 에러남 -> 스레드에 이 부분을 맡기자! } }).start(); // 2. 읽기 분신 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 새 스레드에 안 맡기면 메인 스레드가 담당 new Thread(() -> { while (true) { try { String respBody = br.readLine(); System.out.println("받은 메세지 : " + respBody); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } } // Q. main 스레드가 종료되면 모든게 종료? // A. X. 모든 스레드가 종료되어야
     
     
    package ex20.ch03; import java.io.*; import java.net.Socket; public class MyClient03 { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 20000); // 키보드로부터 받아서 쓰는 것까지는 동기적임 // 1. 쓰기 분신 BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); new Thread(() -> { while (true) { try { String reqBody = keyboard.readLine(); bw.write(reqBody); bw.write("\n"); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); // 2. 읽기 분신 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); new Thread(() -> { while (true) { try { String respBody = br.readLine(); System.out.println("서버로부터 받은 메세지 : " + respBody); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
     
     

    6. Multiflex

     
    package ex20.ch04; import java.io.*; import java.util.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; // Multiflex (채팅) public class MyServer04 { private List<ManagerThread> threads = new ArrayList<>(); // 내부 클래스 - 외부에서 접근이 안됨 내부에서만 쓰기 위한 것. (상태에 접근 가능) class ManagerThread extends Thread { private String username; // s1, s2, s3 private Socket socket; private BufferedReader br; private BufferedWriter bw; public ManagerThread(Socket socket) { this.socket = socket; try { this.br = new BufferedReader(new InputStreamReader(socket.getInputStream())); this.bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); } catch (Exception e) { throw new RuntimeException(e); } } public String getUsername() { return username; } public void setUsername() { try { // 최초로 들어오는 메세지를 id로 설정 username = br.readLine(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public void run() { send("당신의 아이디를 전달해주세요."); // send로 한번 호출하고 시작 setUsername(); send("당신의 아이디는 " + getUsername() + "입니다."); while (true) { try { String msg = br.readLine(); // 분신 대기 // 1. + 2. = 서비스? // 1. 파싱 String[] sps = msg.split(":"); // ALL:msg, ssar:msg String protocol = sps[0]; String body = sps[1]; // 2. 프로토콜 분석 if (protocol.equals("ALL")) { for (ManagerThread mt : threads) { mt.send(body); // 전체 메세지 } } else { for (ManagerThread mt : threads) { if (protocol.equals(mt.getUsername())) { mt.send(body); // 귓속말 } } } } catch (IOException e) { throw new RuntimeException(e); } } } public void send(String msg) { try { bw.write(msg); bw.write("\n"); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } } } public MyServer04() { try { ServerSocket serverSocket = new ServerSocket(20000); while (true) { Socket socket = serverSocket.accept(); // 박스 만들기 ManagerThread mt = new ManagerThread(socket); // 박스에 담기 threads.add(mt); // 분신 만들기 mt.start(); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) { new MyServer04(); } } // Q. main 스레드가 종료되면 모든게 종료? // A. X. 모든 스레드가 종료되어야
     
     
    package ex20.ch04; import java.io.*; import java.net.Socket; public class MyClient04 { public static void main(String[] args) { try { Socket socket = new Socket("192.168.0.99", 20000); // 키보드로부터 받아서 쓰는 것까지는 동기적임 // 1. 쓰기 분신 BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); new Thread(() -> { while (true) { try { String reqBody = keyboard.readLine(); bw.write(reqBody); bw.write("\n"); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); // 2. 읽기 분신 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); new Thread(() -> { while (true) { try { String respBody = br.readLine(); System.out.println("서버로부터 받은 메세지 : " + respBody); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
    Share article

    jay0628