자바

자바 TCP 채팅 서버 만들기 - 서버 클래스의 구조 및 startServer(), stopServer(),

알통몬_ 2017. 4. 12. 11:35
반응형


안녕하세요 알통몬입니다.

공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!!

포스팅 내용이 찾아주신 분들께 도움이 되길 바라며

더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^

 


이전 포스팅들에서 공부해본 ServerSocket 과 Socket 그리고 스래드풀 ( ExecutorService )가 어떻게

사용되는지 채팅 서버 구현을 통해서 알아보겠습니다.

그 전에 저는 UI를 JavaFX로 구현을 했는데요.

때문에 JavaFX를 공부하고 오시면 좀 더 이해하시는데 도움이 되실 것 같습니다 ㅎㅎ

http://blog.naver.com/rain483/220605517395

제가 운영하는 네이버 블로그인데요.

약 40개 정도의 JavaFX 포스팅이 있으니 참고해주세요.



1. 서버 클래스의 구조 :

public class ExampleServer extends Application {

  ExecutorService executorService; // 스레드풀

  ServerSocket serverSocket; // 클라이언트의 연결 수락

  List<Client> connections = new Vector<Client>(); // 연결된 클라이언트를 저장하는 List<Client>타입의 connections 필드를 스레드에 안전한 Vector로 초기화


 void startServer(){ ... }

 void stopServer(){ ... }


 class Client { ... }


 UI 생성 코드 ....

}


2. startServer() :

void startServer() {


  executorService = Executors.newFixedThreadPool(

   Runtime.getRuntime().availableProcessors()

     ); // ExecutorService 객체를 얻기위해 Executors.newFixedThreadPool()호출합니다..

     Runtime.getRuntime().availableProcessors() // CPU 코어 수만큼 스레드 생성합니다..

  try { // ServerSocket 생성 및 포트 바인딩.

   serverSocket = new ServerSocket();  

   serverSocket.bind(new InetSocketAddress("localhost", 5001));

  } catch(Exception e) { 예외 발생 시 ServerSocket이 열려 있으면 stopServer() 호출합니다

   if(!serverSocket.isClosed()) { stopServer(); }

   return;

  }

  

  Runnable runnable = new Runnable() { // 연결 수락 코드.

   @Override

   public void run() {

    Platform.runLater(()->{

     displayText("[[서버 시작]]");

     btnStartStop.setText("stop");

    });  

    while(true) {

     try {

      Socket socket = serverSocket.accept();

      String message = "[[연결 수락: " + socket.getRemoteSocketAddress()  + ": " + Thread.currentThread().getName() + "]]";

      Platform.runLater(()->displayText(message));

  

      Client client = new Client(socket);

      connections.add(client);

      Platform.runLater(()->displayText("[[연결 개수 => " + connections.size() + "]]"));

     } catch (Exception e) {

      if(!serverSocket.isClosed()) { stopServer(); }

      break;

     }

    }

   }

  };

  executorService.submit(runnable);

 }


3. stopServer() :

 void stopServer() {

  try {

   Iterator<Client> iterator = connections.iterator();

   while(iterator.hasNext()) {

    Client client = iterator.next();

    client.socket.close();

    iterator.remove(); // 클라이언트 제거.

   }

   if(serverSocket!=null && !serverSocket.isClosed()) { 

    serverSocket.close(); 

   }

   if(executorService!=null && !executorService.isShutdown()) { 

    executorService.shutdown(); 

   }

   Platform.runLater(()->{

    displayText("[서버 멈춤]");

    btnStartStop.setText("start");

   });

  } catch (Exception e) { }

 } 


4.  Client 클래스

class Client { // ExampleServer의 내부 클래스로 선언합니다.

 Socket socket;

 Client(Socket socket) {

   this.socket = socket;

   receive();

  }

  

  void receive() { // 다른 클라이언트의 데이터를 받는 메소드

   Runnable runnable = new Runnable() {

    @Override

    public void run() {

     try {

      while(true) {

       byte[] byteArr = new byte[100];

       InputStream inputStream = socket.getInputStream();

       

       //클라이언트가 비저상 종료를 했을 경우 IOException 발생

       int readByteCount = inputStream.read(byteArr);

       

      //클라이언트가 정상적으로 Socket의 close()를 호출했을 경우

       if(readByteCount == -1) {  throw new IOException(); }

       

       String message = "[[요청 처리: " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]]";

       Platform.runLater(()->displayText(message));

       

      String data = new String(byteArr, 0, readByteCount, "UTF-8");

       

       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 e2) {}

     }

    }

   };

   executorService.submit(runnable);

  }

 

  void send(String data) { //다른 클라이언트에게 데이터를 보내는 메소드

   Runnable runnable = new Runnable() {

    @Override

    public void run() {

     try {

      byte[] byteArr = data.getBytes("UTF-8");

      OutputStream outputStream = socket.getOutputStream();

      outputStream.write(byteArr);

      outputStream.flush();

     } catch(Exception e) {

      try {

       String message = "[[클라이언트 통신 안됨: " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]]";

       Platform.runLater(()->displayText(message));

       connections.remove(Client.this);

       socket.close();

      } catch (IOException e2) { // 예외처리 }

     }

    }

   };

   executorService.submit(runnable);

  }

 }



5. 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("서버 시작")) {

    startServer();

   } else if(btnStartStop.getText().equals("서버 종료")){

    stopServer();

   }

  });

  root.setBottom(btnStartStop);

  

  Scene scene = new Scene(root);

  scene.getStylesheets().add(getClass().getResource("app.css").toString());

             //외부 CSS 적용.

  primaryStage.setScene(scene);

  primaryStage.setTitle("서버");

  primaryStage.setOnCloseRequest(event->stopServer());

  primaryStage.show();

 }

 

 void displayText(String text) {

  txtDisplay.appendText(text + "\n");

 } 

 

 public static void main(String[] args) {

  launch(args);

 }


이상입니다.

서버를 만들었으니 이제 클라이언트도 만들어봐야겠죠??

다음 포스팅에서는 클라이언트를 만들어보겠습니다~


반응형