🔶 이것이 자바다 : TCP 네트워킹(2)
✔ TCP 네트워킹(2)
💡 채팅 프로그램
- 서버 코드
package sec07.exam03_chatting; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Iterator; import java.util.List; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; // 클래스 선언 // Application 을 상속한다 => java fx실행클래스를 만들기 위해 public class ServerExample extends Application { // 필드 선언 ExecutorService executorService; // 스레드풀 생성위해 선언 ServerSocket serverSocket; // 클라이언트의 연결요청 수락하기 위해 선언 // List<Client> => 아래 class Client를 객체 생성 해 List에 저장 // Vector : 스레드에 안전하다 List<Client> connections = new Vector<Client>(); // void startServer() { // 서버 시작 코드 // start 버튼 눌렀을 때 실행 // 1) ExecutorService 객체 만들기 // executorService 라고 하는 스레드 풀 걕체 생성 executorService = Executors.newFixedThreadPool( // 스레드 최대갯수 입력 // 현재 PC의 CPU가 지원하는 코어의 수를 얻을 수 있다 =>코어의 수 만큼 쓰레드 생성 Runtime.getRuntime().availableProcessors() ); try { // 2) ServerSocket 객체 만들어 포트에 binding 한다 serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress("localhost",5001)); }catch(Exception e) { if(!serverSocket.isClosed()) { stopServer(); } return; // start서버 종료 } // 3) 연결 수락 작업을 스레드에서 처리할 수 있도록 Runnable객체를 만들어 // executorService 에 submit 메소드를 제공하는 코드 작성 // Runnable 객체 생성 => 연결 수락 작업 객체 Runnable runnable = new Runnable() { // ctrl + space 해서 run메소드 오버라이드 @Override public void run() { // 연결 수락을 위한 코드 작성 // 첫 화면에 띄울 문구 작성 Platform.runLater(()->{ // UI변경 코드 displayText("[서버시작]"); btnStartStop.setText("stop"); // start 버튼을 누루면 해당 버튼이 stop으로 바뀌는 코드 }); // 무한루프 작성 => 클라이언트에 연결 수락 작업을 계속적으로 할 수 있는 코드 작성 while(true){ try { // 클라이언트가 연결요청하면 accept 가 연결 수락을 하고 socket 을 리턴한다 Socket socket = serverSocket.accept(); // 로그 출력 -> 클라이언트 IP와 , 현재 담당 스레드 풀의 이름을 출력한다 String message = "[연결 수락 : " + socket.getRemoteSocketAddress()+ ":" + Thread.currentThread().getName() + "]" ; Platform.runLater(()->{ displayText(message); }); // 클라이언트 연결 하나당 클라이언트 객체를 생성해서 List<Client>에 저장한다 Client client = new Client(socket); // connections 에 클라이언트 저장 connections.add(client); // 로그 출력 => 몊개의 클라이언트가 연결 되었는지 출력 Platform.runLater(()->{ displayText("[연결 개수 : " + connections.size()+ "]" ); }); } catch (Exception e) { if(!serverSocket.isClosed()) { // 만약에 서버소켓이 닫혀있지 않다면 stopServer(); // 서버를 중지한다 } break; } } } }; // startServer의 맨마지막 코드 // 스레드 풀의 스레드 처리할 수 있게 해준다 executorService.submit(runnable); } void stopServer() { // 서버 종료 코드 // stop 버튼 눌렀을 때 실행 try { // 연결된 모든 클라이언트의 연결을 끊기 위해 소켓,익스큐터 서비스 클로즈,리스크 컬렉션에서 클라이언트 제거 Iterator<Client> iterator = connections.iterator(); // iterator에서 가지고 올 클라이언트가 있다면 while(iterator.hasNext()) { // 클라이언트 가져오기 Client client = iterator.next(); // 가지고온 클라이언트의 소켓 클로즈 => 예외처리 해야한다 client.socket.close(); // 클라이언트 제거 iterator.remove(); } // Socket 클로즈 if(serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } // executor 클로즈 if(executorService != null && !executorService.isShutdown()) { executorService.shutdown(); } // 로그 쿨력 Platform.runLater(()->{ displayText("[서버 멈춤]"); btnStartStop.setText("start"); // 버튼 start로 변경 }); } catch (Exception e) {} // 예외처리 내용 없어서 내용작성 X } class Client { // 데이터 통신코드 // 필드 선언 Socket socket; // 생성자 생성 => socket 매개값으로 받아 socket에 대입 Client(Socket socket){ this.socket = socket; receive(); // 클라이언트 객체를 생성할 때 항상 메소드 호출 } // 클라이언트가 보낸 데이터 받는 메소드 void receive() { // 스레드 풀(executorService) 안에 있는 스레드가 receive 메소드를 실행 할 수 있도록 Runnable runnable = new Runnable() { @Override public void run() { try { // 클라이언트가 보낸 데이터를 받을 코드 while(true) { byte[] byteArr = new byte[100]; InputStream inputStream = socket.getInputStream(); // inputStream 을 통해 클라이언트가 보낸 데이터 받기 int readByteCount = inputStream.read(byteArr); if(readByteCount == -1) { // 클라이언트가 정상적으로 종료를 했다면 throw new IOException(); // 강제적으로 IO예외를 발생 } // 로그 출력내용 String message = "[요청 처리 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]"; // 로그 출력 Platform.runLater(()-> displayText(message)); // 읽은 바이트 문자열로 변환 String data = new String(byteArr,0,readByteCount,"UTF-8"); // 받은 데이터를 모든 클라이언트에게 전달 // for : 모든 클라이언트를 리스트에서 얻어서 send메소드를 호출해서 데이터를 클라이언트로 보낸다 for(Client client : connections) { client.send(data); } } }catch(Exception e) { try { // 해당 클라이언트를 리스트에서 삭제 connections.remove(Client.this); // 출력 로그 생성 String message = "[클라이언트 통신 안됨 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]"; // 로그 출력 Platform.runLater(()-> displayText(message)); socket.close(); } catch (IOException e1) { } } } }; //executorService 내부의 스레드가 run() 이라고 하는 작업을 실행하게 한다 executorService.submit(runnable); } // 클라이언트로 데이터를 보내는 메소드 => 매개값으로 문자열을 받아 클라이언트로 전송한다 void send(String data) { Runnable runnable = new Runnable() { @Override public void run() { // 클라이언트한테 데이터 보내는 메소드 작성 // 바이트배열 생성 => 보내고자 하는 매개값 data 에서 바이트 얻어낸다 try { byte[] byteArr = data.getBytes("UTF-8"); // 클라이언트로 보내기 OutputStream outputStream = socket.getOutputStream(); // outputStream의 write메소드를 호출해 byteArr 에 저장된 내용을 클라이언트로 보낸다 outputStream.write(byteArr); outputStream.flush(); // write메소드 호출후 꼭 사용해야한다 } catch (Exception e) { try { // write 에서 예외 처리 발생시 출력로그 작성 String message = "[클라이언트 통신 안됨 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]"; // 해당 클라이언트를 리스트에서 삭제 connections.remove(Client.this); socket.close(); } catch (IOException e1) { } } } }; //executorService 내부의 스레드가 run() 이라고 하는 작업을 실행하게 한다 executorService.submit(runnable); } } ////////////////////////////////////////////////////// // 자바로 UI 만드것 TextArea txtDisplay; Button btnStartStop; @Override public void start(Stage primaryStage) throws Exception { BorderPane root = new BorderPane(); root.setPrefSize(500, 300); txtDisplay = new TextArea(); txtDisplay.setEditable(false); BorderPane.setMargin(txtDisplay, new Insets(0,0,2,0)); root.setCenter(txtDisplay); btnStartStop = new Button("start"); btnStartStop.setPrefHeight(30); btnStartStop.setMaxWidth(Double.MAX_VALUE); btnStartStop.setOnAction(e->{ if(btnStartStop.getText().equals("start")) { startServer(); } else if(btnStartStop.getText().equals("stop")){ stopServer(); } }); root.setBottom(btnStartStop); Scene scene = new Scene(root); scene.getStylesheets().add(getClass().getResource("app.css").toString()); primaryStage.setScene(scene); primaryStage.setTitle("Server"); primaryStage.setOnCloseRequest(event->stopServer()); primaryStage.show(); } void displayText(String text) { txtDisplay.appendText(text + "\n"); } public static void main(String[] args) { launch(args); } }
-
클라이언트 코드
package sec07.exam03_chatting; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.Socket; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class ClientExample extends Application { // 통신과 관련된 필드 Socket socket; void startClient() { // 연결 시작 코드 // 서버에 연결되기 전까지 대기상태로 있기위해 별도의 Thread생성 => 익명객체로 생성 Thread thread = new Thread() { @Override public void run() { try { // socket 객체 생성 socket = new Socket(); // 연결 socket.connect(new InetSocketAddress("localhost", 5001)); // 로그 출력 Platform.runLater(()->{ displayText("[연결 완료: " + socket.getRemoteSocketAddress() + "]"); btnConn.setText("stop"); // start버튼 stop로 변경 btnSend.setDisable(false); // send버튼 활성화 }); } catch (IOException e) { Platform.runLater(()->displayText("[서버 통신 안됨]")); if(!socket.isClosed()) { stopClient(); }return; } // 연결 성공시 => receive 메소드를 받아 항상 서버가 보내는 데이터를 받는다 receive(); } }; thread.start(); // thread 시작 } void stopClient() { // 연결 끊기 코드 try { Platform.runLater(()->{displayText("[연결 끊음]"); btnConn.setText("start"); // stop버튼 start로 변경 btnSend.setDisable(true); // send버튼 비활성화 }); if(socket !=null && !socket.isClosed()) { socket.close(); } }catch(Exception e) {} } void receive() { // 데이터 받기 코드 while(true) { try { byte[] byteArr = new byte[100]; InputStream inputStream = socket.getInputStream(); int readByteCount = inputStream.read(byteArr); if(readByteCount == -1) { // 클라이언트가 정상적으로 종료를 했다면 throw new IOException(); // 강제적으로 IO예외를 발생 } // 읽은 바이트 문자열로 변환 String data = new String(byteArr,0,readByteCount,"UTF-8"); // 로그 출력 Platform.runLater(()-> displayText("[받기 완료]" + data)); } catch(Exception e) { Platform.runLater(()-> displayText("[서버 통신 안됨]")); stopClient(); break; // while문 빠져나가기 } } } void send(String data) { // 데이터 전송 코드 // 별도의 스레드 만들어 데이터 보내기 Thread thread = new Thread() { @Override public void run() { try { byte[] byteArr = data.getBytes("UTF-8"); OutputStream outputStream = socket.getOutputStream(); outputStream.write(byteArr); outputStream.flush(); // write메소드 호출후 꼭 사용해야한다 Platform.runLater(()-> displayText("[보내기 완료]")); } catch (Exception e) { Platform.runLater(()-> displayText("[서버 통신 안됨]")); stopClient(); } } }; thread.start(); // thread 시작 } ////////////////////////////////////////////////////// // 자바 UI TextArea txtDisplay; TextField txtInput; Button btnConn, btnSend; @Override public void start(Stage primaryStage) throws Exception { BorderPane root = new BorderPane(); root.setPrefSize(500, 300); txtDisplay = new TextArea(); txtDisplay.setEditable(false); BorderPane.setMargin(txtDisplay, new Insets(0,0,2,0)); root.setCenter(txtDisplay); BorderPane bottom = new BorderPane(); txtInput = new TextField(); txtInput.setPrefSize(60, 30); BorderPane.setMargin(txtInput, new Insets(0,1,1,1)); btnConn = new Button("start"); btnConn.setPrefSize(60, 30); btnConn.setOnAction(e->{ if(btnConn.getText().equals("start")) { startClient(); } else if(btnConn.getText().equals("stop")){ stopClient(); } }); btnSend = new Button("send"); btnSend.setPrefSize(60, 30); btnSend.setDisable(true); btnSend.setOnAction(e->send(txtInput.getText())); bottom.setCenter(txtInput); bottom.setLeft(btnConn); bottom.setRight(btnSend); root.setBottom(bottom); Scene scene = new Scene(root); scene.getStylesheets().add(getClass().getResource("app.css").toString()); primaryStage.setScene(scene); primaryStage.setTitle("Client"); primaryStage.setOnCloseRequest(event->stopClient()); primaryStage.show(); } void displayText(String text) { txtDisplay.appendText(text + "\n"); } public static void main(String[] args) { launch(args); } }
▶ 서버 프로그램 실행 출력
▶ 서버실행 출력
▶ 클라이언트 실행 출력
▶ 클라이언트 연결 출력
▶ 클라이언트2 연결 출력
▶ 클라이언트 대화 출력
▶ 클라이언트 중지 출력
▶ 서버 중지 출력