안녕하세요 알통몬입니다. 공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!! 포스팅 내용이 찾아주신 분들께 도움이 되길 바라며 더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^
|
이전 포스팅들에서 공부해본 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); } |
이상입니다.
서버를 만들었으니 이제 클라이언트도 만들어봐야겠죠??
다음 포스팅에서는 클라이언트를 만들어보겠습니다~
'자바' 카테고리의 다른 글
자바 UDP 네트워킹 - 발신자와 수신자 (0) | 2017.04.13 |
---|---|
자바 Socket 채팅 클라이언트 만들기 (0) | 2017.04.13 |
자바 Socket 데이터 통신과 스레드 병렬 처리 (0) | 2017.04.11 |
자바 TCP 네트워킹, ServerSocket과 Socket / ServerSocet의 생성 및 연결 수락 (0) | 2017.04.11 |
자바 네트워크 - 서버와 클라이언트, IP 주소와 포트, InetAddress (0) | 2017.04.11 |