Java

[Java] 98. 코드 리팩토링(1:1 양방향 통신-클라이언트 측)

Song hyun 2024. 5. 23. 12:15
728x90
반응형

[Java] 98. 코드 리팩토링(1:1 양방향 통신-클라이언트 측)


 

*자세한 해석, 설명은 아래 글을 참고하자. 

https://whatsthatsound.tistory.com/193

 

[Java] 97. 코드 리팩토링 (1:1 양방향 통신-서버측)

[Java] 97. 코드 리팩토링 (1:1 양방향 통신)(1) 1단계. 함수로 분리하기(2) 2단계. 상속 활용하기 (부모-추상 클래스 작성)(3) 3단계. 상속 활용하기 (자식 클래스 작성) *먼저 보고 오면 좋은 글https://wha

whatsthatsound.tistory.com


(1) MultiThreadClient

package ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

// 1단계 - 함수로 분리해서 리팩토링 진행
public class MultiThreadClient {
	
	public static void main(String[] args) {
	
		System.out.println("===== 클라이언트 실행 =====");
		
		try (Socket socket = new Socket("localhost",5004)){
			System.out.println("=====Connected to the server=====");
			
			PrintWriter socketWriter = 
					new PrintWriter(socket.getOutputStream());
			BufferedReader socketReader = 
					new BufferedReader(new InputStreamReader(socket.getInputStream()));
			BufferedReader keyboardReader =
					new BufferedReader(new InputStreamReader(System.in));
			
			startWriteThread(socketWriter,keyboardReader);
			startReadThread(socketReader);
			
			System.out.println("main 스레드 작업 완료 ...");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	} // end of main 
	
	// 1. 서버로부터 데이터를 읽어들이는 스레드
	private static void startReadThread(BufferedReader socketReader) {
		Thread readThread = new Thread(()->{
			try {
				String serverMessage;
				while((serverMessage=socketReader.readLine())!=null) {
					System.out.println("서버에서 온 MSG : "+serverMessage);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
		
		readThread.start();
		waitForThreadToEnd(readThread);
	}
	
	// 2. 서버에게 데이터를 보내는 스레드
	private static void startWriteThread(PrintWriter sockerWriter,BufferedReader keyboardReader) {
		Thread writeThread = new Thread(()->{
			try {
				String clientMessage;
				while((clientMessage=keyboardReader.readLine())!=null) {
					sockerWriter.println(clientMessage);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
		
		writeThread.start();
		waitForThreadToEnd(writeThread);
	}
	
	// 3. 워커 스레드가 종료될 때까지 기다리는 메서드
	private static void waitForThreadToEnd(Thread thread) {
		try {
			thread.join();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
} // end of class

 

(2) AbstractClient(추상 클래스)

package ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

// 2단계 - 상속 활용 리팩토링 단계
public abstract class AbstractClient {
	
	private Socket socket;
	private BufferedReader socketReader;
	private PrintWriter socketWriter;
	private BufferedReader keyboardReader;
	
	
	// set 메서드
	// 메서드 의존 주입 (멤버 변수에 참조 변수 할당)
	protected void setSocket(Socket socket) {
		this.socket = socket;
	}
	
	// get 메서드
	protected Socket getSocket() {
		return this.socket;
	}
	
	// 실행의 흐름이 필요하다.
	// final - 상속 받은 자식 클래스에서 수정이 불가
	public final void run() {
		try {
			setupClient();
			setupStream();
			startService();
		} catch (Exception e) {
			
		} finally {
			cleanup();
		}
	}
	
	// 1. 포트 번호 할당
	protected abstract void setupClient() throws IOException;
	
	// 2. 스트림 초기화
	private void setupStream() throws IOException{
		socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		socketWriter = new PrintWriter(socket.getOutputStream(),true);
		keyboardReader = new BufferedReader(new InputStreamReader(System.in));
	}
	
	// 3. 서비스 시작
	private void startService() {
		Thread writeThread = createWriteThread();
		Thread readThread = createReadThread();
		
		writeThread.start();
		readThread.start();
		
		try {
			readThread.join();
			writeThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	// 4. 캡슐화
	private Thread createWriteThread(){
		return new Thread(()->{
			try {
				String msg;
				while((msg=keyboardReader.readLine())!=null) {
					socketWriter.println(msg);
					socketWriter.flush();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}
	
	private Thread createReadThread() {
		return new Thread(()->{
			try {
				String msg;
				while((msg=socketReader.readLine())!=null) {
					System.out.println("client 측 msg : "+msg);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}
	
	// 캡슐화 - 소켓 자원 종료
	private void cleanup() {
		try {
			if(socket!=null) {
				socket.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	

}

 

(3) MyClient

package ch05;

import java.io.IOException;
import java.net.Socket;

// 2 -1 상속을 활용한 구현 클래스 설계 하기
public class MyClient extends AbstractClient{

	@Override
	protected void setupClient() throws IOException {
		// 추상클래스: 자식 클래스는 부모 클래스에게 기능을 상속받아,
		// 그 기능을 확장하거나 사용한다.
		// 클라이언트측 소켓 통신 --- 준비물 : 소켓
		super.setSocket(new Socket("localhost",5000));
		System.out.println(">>> Client was connected on port 5000 <<<");
	}

	public static void main(String[] args) {
		MyClient myClient = new MyClient();
		myClient.run();
	}
	
}
728x90
반응형