서버 - 여러명의 사용자가 한 서버에서 채팅을 할 수 있게 중계자 역할
javafx - 자바 기반 GUI 라이브러리
Client
public class Client {
Socket socket; //네트워크상에서 통신하기 위해
public Client(Socket socket) { //생성자 어떠한 변수의 초기화를 위해
this.socket = socket;
receive(); //반복적으로 클라이언트로 부터 어떠한 메시지를 전달받을수있도록
}
//클라이언트로부터 메시지를 전달받는 메소드
public void receive() {
Runnable thread = new Runnable() {
//Runnable은 run 함수를 반드시 가지고 있어야됨
@Override
public void run() { 하나의 쓰레드가 어떠한 모듈로써 동작을 할껀지 정의하는 함수
try {
while (true) { //반복적으로 클라이언트에게 무엇인가 전달받을수 있도록하기위해
InputStream in = socket.getInputStream(); //어떠한 내용을 전달받을수있도록하기위해
byte[] buffer = new byte[512];
int length = in.read(buffer);
while (length == -1) throw new IOException();
System.out.println("[메시지 수신성공]"
+ socket.getRemoteSocketAddress()
+ ": " + Thread.currentThread().getName()); //클라이언트 주소 + 각 쓰레드별 이름 (고유정보)
String message = new String(buffer, 0, length, "UTF-8");
for (Client client : Main.clients) { //다른 클라이언트에게도 전달받은 메시지를 그대로 보내주기위해
client.send(message);
}
}
} catch (Exception e) {
try {
System.out.println("메시지 수신오류"
+ socket.getRemoteSocketAddress()
+ ": " + Thread.currentThread().getName()); //클라이언트 주소 + 각 쓰레드별 이름 (고유정보)
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
};
Main.threadPool.submit(thread); //threadpool에 등록
}
//클라이언트에게 메시지를 전송하는 메소드
public void send(String message) {
Runnable thread = new Runnable() {
@Ovverride
public void run() {
try {
OutputStream out = socket.getOutputStream(); // outputstream --> 송신을 위한 객체
out.wirte(buffer); //버퍼의 닮긴 내용을 서버에서 클라이언트로 전송
out.flush();
} catch (Exception e) {
try {
System.out.println("[메시지 송신 오류]"
+ socket.getRemoteSocketAddress
+ ": " + Thread.cureentThread().getName());
Main.clients.remove(Client.this); //오류가 발생했다면 메인 함수안의 오류가 난 클라이언트를 지워줌
socket.close(); //오류가 난 클라이언트의 소켓을 닫음
}
}
}
};
Main.threadPool.submit(thread);
}
}
Main (서버)
public class Main extend Application {
public static ExecutorService threadPool; //다양한 클라이언트가 접속했을때 스레드들을 효과적으로 관리하기 위해
public static Vector<Client> clients = new Vector<Client>(); //vector --> 배열
ServerSocket serverSocket;
//서버를 구동시켜서 클라이언트의 연결을 기다리는 메소드
public void startServer() {
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(IP, port);
} catch (Exception e) {
e.printStackTrace();
if (!serverSocket.isClosed()) {
stopServer();
}
return;
}
//클라이언트가 접속할때까지 계속 기다리는 쓰레드
Runnable thread = new Runnable() {
while (true) {
Socket socket = serverSocket.accpet(); //accept가 호출되면 실행 멈추고 client 계속 기다림
clients.add(new Client(socket)); //클라이언트 접속
System.out.println("클라이언트 접속"
+ socket.getRemoteSocketAddress()
+ ": " + Thread.currentThread().getName()); //로그
} catch (Exception e) {
if (!serverSocket.isClosed()) {
stopServer(); //서버 소켓에서 문제가 일어남
}
break;
}
};
threadPool = Executors.newCachedThreadPool(); //쓰레드풀 초기화
threadPool.submit(thread); //쓰레드풀에 현재 클라이언트를 기다리는 쓰레드 담을수있도록 처리
}
public void stopServer () {
try {
//현재 작동중인 모든 소켓 담기
Iterator<Client> iterator = clients.iterator();
while(Iterator.hasNext()) {
Client client = iterator.next();
client.socket.close();
iterator.remove();
}
//서버소켓이 null이 아니고 열려있다면 --> 닫으셈
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
// threadPool도 열려있다면 닫아라
if (threadPool != null && !threadpool.isShutdown()) {
threadPool.shutdown();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//ui를 생성하고 실질적으로 프로그램을 동작시키는 메소드
@Override
public void start(Stage primaryState) {
// 레이아웃
BoarderPane root = new BorderPane();
root.setPadding(new Insets(5));
TextArea textArea = new TextArea();
textArea.setEditable(false);
textArea.setFont(new Font("나눔고딕", 15));
root.setCenter(textArea);
Button toggleButton = new Button("시작하기");
toggleButton.setMaxWidth(Double.MAX_VALUE);
BorderPand.setMargin(toggleButton, new Insets(1, 0, 0, 0));
root.setBottom(toggleButton);
String IP = "127.0.0.1";
int port = 9876;
toggleButton.setOnAction(event -> {
if (toggleButton.getText().equals("시작하기")) {
startServer(IP, port);
Platform.runLater(() -> {
String message = String.format("서버시작", IP, port);
textArea.appendText(message);
toggleButton.setText("종료하기");
});
} else {
stopServer();
Platform.runLater(() -> {
String message = String.format("서버종료", IP, port);
textArea.appendText(message);
toggleButton.setText("시작하기");
});
}
});
Scence scene = new Scene(root, 400, 400);
primaryStage.setTitle("채팅서버");
primaryStage.setOnCloseRequest(event -> stopServer());
primaryStage.setScene(scene);
primaryStage.show();
}
//프로그램 진입점
public static void main(String[] args) {
Launch(args);
}
}
Client Project --> 아예 다른 컴퓨터에서 접속하는 것이므로 다른 프로젝트로 생성
- main
public class Main extends Application {
Socket socket;
TextArea textArea;
//클라이언트 프로그램 동작 메소드
public void startClient(String IP, int port) { //동시다발적으로 스레드 생성 안해도 됨 --> runnable대신 thread
Thread thread = new Thread() {
public void run() {
try {
socket = new Socket(IP, port);
} catch (Exception e) {
if (!socket.isClosed()) {
stopClient();
System.out.println("서버 접속 실패");
Platform.exit(); // 프로그램 자체 종료
}
}
}
};
thread.start();
}
//클라이언트 프로그램 종료 메소드
public void stopClient() {
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//서버로부터 메시지를 전달받는 메소드
public void receive () {
while (true) {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[512];
int length = in.read(buffer);
if (length == -1) throw new IOException();
String message = new String(buffer, 0, length, "UTF-8");
Platform.runlater(() -> {
textArea.appendText(message);
});
} catch (Exception e) {
}
}
}
//서버로 메시지를 전송하는 메소드
public void send(String message) {
Thread thread = new Thread() {
public void run() {
try {
OutputStream out = socket.getOutputStream();
byte[] buffer = message.getBytes("UTF-8");
out.write(buffer);
out.flush();
} catch (Exception e) {
stopClient();
}
}
};
thread.start();
}
//실제로 프로그램을 동작시키는 메소드
@Override
public void start(Stage primaryStage) {
BoarderPane root = new BoarderPane();
root.setPadding(new Insets(5));
HBOX hbox = new HBOX();
hbox.setSpacing(5);
TextField userName = new TextField();
userName.setPrefWidth(150);
userName.setPromptText("닉네임을 입력하세요");
}
// 프로그램 진입점
public static void main(String[] args) {
launch(args);
}
}