- [Java] 97. 코드 리팩토링 (1:1 양방향 통신-서버측)2024년 05월 23일
- Song hyun
- 작성자
- 2024.05.23.:53
728x90반응형[Java] 97. 코드 리팩토링 (1:1 양방향 통신)
(1) 1단계. 함수로 분리하기
(2) 2단계. 상속 활용하기 (부모-추상 클래스 작성)
(3) 3단계. 상속 활용하기 (자식 클래스 작성)
*먼저 보고 오면 좋은 글
https://whatsthatsound.tistory.com/192
(1) 1단계. 함수로 분리하기
(1) 클라이언트로부터 데이터를 읽는 Thread 분리
-원래는 main Thread 내부에 readThread/writeThread가 존재했다. 이번에는 리팩토링을 위해, 각각 read/writeThread를 호출하는 startReadThread()/startWriteThread() 메서드를 main 바깥에 생성해보자.
-해당 메서드 내부에는 Thread의 실행(.start())과 제어(waitForThreadToEnd())가 이루어진다.
// 클라이언트로부터 데이터를 읽는 스레드 분리 // 소켓 <--- 스트림을 얻어야 한다. /// 데이터를 읽는 객체는 뭐지??? <--- 문자. private static void startReadThread(BufferedReader bufferedReader) { Thread readThread = new Thread(() -> { try { String clientMessage; while ((clientMessage = bufferedReader.readLine()) != null) { // 서버측 콘솔에 클라이언트가 보낸 문자 데이터 출력 System.out.println(" 클라이언트에서 온 MSG : " + clientMessage); } } catch (Exception e) { } }); readThread.start(); // 스레드 실행 -> run() 메서드 실행/ waitForThreadToEnd(readThread); // 메인 스레드 대기 처리 --> join() --> 고민 --> 2번의 반복 될 듯 }
(2) 클라이언트에게 데이터를 보내는 Thread 분리
// 서버 측에서 --> 클라이언트로 데이터를 보내는 기능 private static void startWriteThread(PrintWriter printWriter, BufferedReader keyboardReader) { Thread writeThread = new Thread(() -> { try { String serverMessage; while ((serverMessage = keyboardReader.readLine()) != null) { printWriter.println(serverMessage); printWriter.flush(); } } catch (Exception e) { e.printStackTrace(); } }); writeThread.start(); waitForThreadToEnd(writeThread); // 메인 스레드 대기 }
(3) 워커 Thread가 종료될 때까지 기다리는 메서드(join())
-앞서 이야기했듯이, 한 스레드가 실행될 동안 다른 스레드가 먼저 종료된다면, socket 역시 close되어 통신이 불가능해진다. 이를 위해 .join()을 호출한다.
// 워커 스레드가 종료 될 때까지 기다리는 메서드 private static void waitForThreadToEnd(Thread thread) { try { thread.join(); } catch (Exception e) { // TODO: handle exception } }
(4) main 함수 작성
-main 쓰레드 내부에 있던 Thread 들을 main 클래스 바깥에 메서드 형태로 만들게 되었다. (method-thread)
그래서 main 함수가 더욱 간단해졌다.
-ServerSocket/socket(serverSocket.accept()) 생성
-클라이언트와의 통신/키보드 입력을 위한 readerStream/writerStream/keyboardReader 생성
-위에서 생성한 스트림을 활용해 startReadThread()/startWriteThread() 실행
public class MultiThreadServer { // 메인 함수 public static void main(String[] args) { System.out.println("===== 서버 실행 ====="); // 서버측 소켓을 만들기 위한 준비물 // 서버 소켓, 포트 번호 try (ServerSocket serverSocket = new ServerSocket(5000)) { Socket socket = serverSocket.accept(); // 클라이언트 대기 --> 연결 요청이 오면 --> 소켓 객체 생성 (클라와 연결) System.out.println("----- client connected -----"); // 클라이언트와 통신을 위한 스트림을 설정 (대상 소켓을 얻었다) BufferedReader readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 클라이언트에게 보낼 스트림 PrintWriter writerStream = new PrintWriter(socket.getOutputStream(), true); // 키보드 스트림 준비 BufferedReader keyboardReader = new BufferedReader(new InputStreamReader(System.in)); // 스레드를 시작합니다. startReadThread(readerStream); startWriteThread(writerStream, keyboardReader); System.out.println("main 스레드 작업 완료 ..."); } catch (Exception e) { e.printStackTrace(); } } // end of main
(2) 2단계. 상속 활용하기 (부모-추상 클래스 작성) : AbstractServer 클래스
-이번에는 추상 클래스의 상속을 통해, 더욱 간단하게 리팩토링해보자.
우선 AbstractServer 클래스에 핵심적인 멤버변수/ 메서드를 만들어보자.
(1) 멤버변수로 serverSocket / socket / readerStream(BufferedReader) / writerStream(PrintWriter) / keyboardReader(BufferedReader)를 선언한다.(2) 메서드 의존 주입을 통해 serverSocket과 socket을 초기화한다. (=setter)
(3) serverSocket을 가져올 수 있도록 getter 메서드를 만든다. (=getServerSocket)
(4) run() 메서드
-setupServer() // 포트번호 할당
-connection() // 클라이언트 연결 대기
-setupStream() // 스트림 초기화
-startService() // 서비스 시작
메서드들을 호출한다.
*try-catch-finally문을 사용해 cleanup() 메서드를 호출한다.
(5) setupServer() 메서드: 추상 메서드로, 자식 클래스에서 포트 번호를 할당하게 한다.
(6) connection() 메서드: 추상 메서드로, 클라이언트 연결을 기다리게 한다. (.accpet())
(7) setupStream() 메서드: 앞서 선언된 입출력 스트림들을 초기화시킨다.
(8) startService() 메서드: readThread/writeThread들을 start/join시킨다.
(9) createRead/WriteThread 쓰레드: 입/출력한 데이터를 출/입력한다.
(10) cleanup() 메서드: 자원들을 종료시킨다.
package ch05; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; // 상속의 활용 public abstract class AbstractServer { private ServerSocket serverSocket; private Socket socket; private BufferedReader readerStream; private PrintWriter writerStream; private BufferedReader keyboardReader; // set 메서드 // 메서드 의존 주입 (멤버 변수에 참조 변수 할당) protected void setServerSocket(ServerSocket serverSocket) { this.serverSocket = serverSocket; } protected void setSocket(Socket socket) { this.socket=socket; } // get 메서드 protected ServerSocket getServerSocket() { return this.serverSocket; } // 실행의 흐름이 필요하다. // final - 상속받은 자식 클래스에서 수정 불가 public final void run() { // 1. 서버 세팅 - 포트 번호 할당 try { setupServer(); connection(); setupStream(); startService(); } catch (Exception e) { } finally { cleanup(); } } // 1. 포트 번호 할당 (구현 클래스에서 직접 설계) protected abstract void setupServer() throws IOException; // 구현부 x = 추상 메서드 // 2. 클라이언트 연결 대기 실행 (구현 클래스) protected abstract void connection() throws IOException; // 3. 스트림 초기화 (연결된 소켓에서 스트림을 뽑아야 한다.) - 여기서 함 private void setupStream() throws IOException{ readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream())); writerStream = new PrintWriter(socket.getOutputStream(),true); keyboardReader = new BufferedReader(new InputStreamReader(System.in)); } // 4. 서비스 시작 private void startService() { // while <--- Thread readThread = createReadThread(); // while ---> Thread writeThread = createWriteThread(); readThread.start(); writeThread.start(); try { readThread.join(); writeThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } // 캡슐화 private Thread createReadThread() { return new Thread(() -> { try { String msg; // scanner.nextLine(); <-- 무한 대기(사용자가 콘솔에 값 입력까지 대기 while((msg=readerStream.readLine())!=null) { // 서버 측 콘솔에 출력 System.out.println("client 측 msg : "+msg); } } catch (Exception e) { e.printStackTrace(); } }); } private Thread createWriteThread() { return new Thread(()->{ try { String msg; // 서버 측 키보드에서 데이터를 한 줄 라인으로 읽음 while((msg=keyboardReader.readLine())!=null) { // 클라이언트와 연결된 소켓에 데이터를 보낸다. writerStream.println(msg); writerStream.flush(); } } catch (Exception e) { e.printStackTrace(); } }); } // 캡슐화 - 소켓 자원 종료 private void cleanup() { try { if(socket != null) { socket.close(); } if(serverSocket!=null) { serverSocket.close(); } } catch (Exception e) { e.printStackTrace(); } } }
(3) 3단계. 상속 활용하기 (자식 클래스 작성)
: MyThreadServer 클래스
(1) 앞서 작성한 추상 클래스 - AbstractServer 클래스를 상속(extends) 한다.
(2) setupServer() 메서드: 부모 클래스로부터 serverSocket을 받아와, 메세지를 출력한다.
(3) connection() 메서드: setupServer()로부터 받아온 serverSocket에 .accept()를 걸어 socket을 초기화한다.
(4) main 쓰레드: MyThreadServer 객체를 생성한 뒤 .run()한다.
package ch05; import java.io.IOException; import java.net.ServerSocket; public class MyThreadServer extends AbstractServer{ @Override protected void setupServer() throws IOException { // 추상 클래스 --> 부모 --> 자식 (부모 기능을 확장, 또는 사용) // 서버측 소켓 통신 -- 준비물 : 서버 소켓 super.setServerSocket(new ServerSocket(5000)); System.out.println(">>> Server started on port 5000 <<<"); } @Override protected void connection() throws IOException { // 서버 소켓.accpet(); 호출이다!!! super.setSocket(super.getServerSocket().accept()); } public static void main(String[] args) { MyThreadServer myThreadServer = new MyThreadServer(); myThreadServer.run(); } }
728x90반응형'Java > 네트워크 통신' 카테고리의 다른 글
[Java] 99. 1:N 양방향 통신 (0) 2024.05.24 [Java] 98. 코드 리팩토링(1:1 양방향 통신-클라이언트 측) (0) 2024.05.23 [Java] 96. 1:1 양방향 통신(채팅 기본 기능 구현) (0) 2024.05.22 [Java] 95. 1:1 양방향 통신 (서버 측) (0) 2024.05.22 [Java] 94. 1:1 단방향 통신 (Client) (0) 2024.05.22 다음글이전글이전 글이 없습니다.댓글