안녕하세요 알통몬입니다. 공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!! 포스팅 내용이 찾아주신 분들께 도움이 되길 바라며 더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^
|
ExecutorService(스레드풀) 과 ServerSocketChannel, SocketChannel 이 어떻게 사용되는지를
채팅 서버 구현을 통해 알아보겠습니다.
이 포스팅에서는 JAVAFX에 대한 내용이 들어있습니다.
아래 URL로 가셔서 JAVAFX에 대해 공부하시는 것을 추천드립니다.
http://blog.naver.com/rain483/220605517395
1. 서버 클래스의 구조
public class ServerExample extends Application { ExecutorService executorService; // 스레드풀 필드 선언 ServerSocketChannel serverSocketChannel; // 서버 소켓 채널 필드 선언 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() ); // 스레드풀 생성
try { serverSocketChannel = ServerSocketChannel.open(); // 서버 소켓 채널 생성 serverSocketChannel.configureBlocking(true); // 블로킹임을 명시적으로 선언 serverSocketChannel.bind(new InetSocketAddress(5001)); // 포트 바인딩 } catch(Exception e) { // 예외 발생 시 서버 소켓 채널 열려있으면 서버 멈춤. if(serverSocketChannel.isOpen()) { stopServer(); } return; }
Runnable runnable = new Runnable() { // 연력 수락 작업을 Runnable로 정의 @Override public void run() { Platform.runLater(()->{ displayText("[[서버 시작]]"); btnStartStop.setText("stop"); }); while(true) { try { SocketChannel socketChannel = serverSocketChannel.accept(); // 클라이언트의 요청을 기다림. String message = "[[연결 수락: " + socketChannel.getRemoteAddress() + ": " + Thread.currentThread().getName() + "]]"; Platform.runLater(()->displayText(message));
Client client = new Client(socketChannel); connections.add(client); // 클래이언트 객체 생성 후 connections 컬렉션에 추가
Platform.runLater(()->displayText("[[연결 개수: " + connections.size() + "]]")); // 연결된 클라이언트 수 } catch (Exception e) { if(serverSocketChannel.isOpen()) { stopServer(); } break; } } } }; executorService.submit(runnable); } |
3. stopServer()
void stopServer() { try { Iterator<Client> iterator = connections.iterator(); while(iterator.hasNext()) { Client client = iterator.next(); client.socketChannel.close(); iterator.remove(); } // 모든 소켓 채널 닫기 if(serverSocketChannel!=null && serverSocketChannel.isOpen()) { serverSocketChannel.close(); } // 서버 소켓 채널 닫기 if(executorService!=null && !executorService.isShutdown()) { executorService.shutdown(); } // 스레드풀 닫기 Platform.runLater(()->{ // 작업 스레드는 UI 변경 불가하므로 -> Platform.runLater()사용 displayText("[[서버 멈춤]]"); btnStartStop.setText("start"); }); } catch (Exception e) {}
} |
4. Client 클래스
class Client { SocketChannel socketChannel; // 통신을 위한 SocketChannel을 필드로 선언
Client(SocketChannel socketChannel) { //생성자 선언(매개값으로 소켓 채널 필드 초기화) receive() 호출 this.socketChannel = socketChannel; receive(); }
void receive() { // 클라이언트의 데이터를 받기 위해 receive() 선언 Runnable runnable = new Runnable() { // 스레드풀의 작업 스레드가 처리하도록 Runnable로 작업 정의 @Override public void run() { while(true) { // 클라이언트가 보낸 데이터를 반복적으로 받기 위한 무한 루프 try { ByteBuffer byteBuffer = ByteBuffer.allocate(100);
//클라이언트가 비정상 종료를 했을 경우 IOException 발생 int readByteCount = socketChannel.read(byteBuffer);
//클라이언트가 정상적으로 SocketChannel의 close()를 호출했을 경우 if(readByteCount == -1) { throw new IOException(); }
String message = "[[요청 처리: " + socketChannel.getRemoteAddress() + ": " + Thread.currentThread().getName() + "]]"; Platform.runLater(()->displayText(message));
byteBuffer.flip(); Charset charset = Charset.forName("UTF-8"); String data = charset.decode(byteBuffer).toString();
for(Client client : connections) { client.send(data); } } catch(Exception e) { try { connections.remove(Client.this); String message = "[[클라이언트 통신 안됨: " + socketChannel.getRemoteAddress() + ": " + Thread.currentThread().getName() + "]]"; Platform.runLater(()->displayText(message)); socketChannel.close(); } catch (IOException e2) {} break; } } } }; executorService.submit(runnable); // 스레드 풀에서 처리 }
void send(String data) { Runnable runnable = new Runnable() { @Override public void run() { try { Charset charset = Charset.forName("UTF-8"); ByteBuffer byteBuffer = charset.encode(data); socketChannel.write(byteBuffer); } catch(Exception e) { try { String message = "[[클라이언트 통신 안됨: " + socketChannel.getRemoteAddress() + ": " + Thread.currentThread().getName() + "]]"; Platform.runLater(()->displayText(message)); connections.remove(Client.this); socketChannel.close(); } catch (IOException e2) {} } } }; executorService.submit(runnable); // 스레드풀에서 처리 } } |
5. UI 생성 코드 = JavaFX
TextArea txtDisplay; Button btnStartStop;
@Override public void start(Stage primaryStage) throws Exception { BorderPane root = new BorderPane(); root.setPrefSize(500, 300); // 윈도우 사이즈 500 X 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); } } |
다음 포스팅에서는 TCP 블로킹 채널로 클라이언트 구현하기에 대해 공부하겠습니다.
이상입니다.
'자바' 카테고리의 다른 글
자바 NIO TCP 블로킹과 인터럽트 (0) | 2017.04.20 |
---|---|
자바 NIO TCP 블로킹 채널 클라이언트 구현하기 (0) | 2017.04.20 |
자바 NIO TCP 블로킹 채널 (0) | 2017.04.18 |
자바 NIO 파일 비동기 채널 - AsynchronousFileChannel 생성과 닫기, 파일 읽기 , 파일 쓰기 (0) | 2017.04.17 |
자바 NIO 파일 채널 - FileChannel의 생성과 닫기, 파일 쓰기와 읽기, 파일 복사 (1) | 2017.04.16 |