// 채팅 서버 프로그램 - 다중 스레드 프로그램
// > 클라이언트에서 보내온 메세지를 전달받아 모든 클라이언트에게 전달하는 기능
// > 클라이언트와 연결된 소켓은 새로운 스레드를 생성하여 독립적으로 입출력되도록 설정
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatServerApp {
// 현재 접속중인 모든 클라이언트의 소켓정보가 저장된 List 객체를 저장하기 위한 필드
private List<SocketThread> clientList;
public ChatServerApp() {
ServerSocket chatServer = null;
try {
// 포트를 활성화하여 클라이언트가 접속할 수 있는 환경 제공
chatServer = new ServerSocket(5000);
System.out.println("[메세지]채팅 서버 동작중...");
// ArrayList 객체를 생성하여 필드에 저장
clientList = new ArrayList<>();
while(true) {
try {
// 클라이언트가 접속되면 클라이언트와 연결된 Socket 객체를 반환받아 저장
Socket socket = chatServer.accept();
System.out.println("[접속로그]" + socket.getInetAddress().getHostAddress()
+ "의 컴퓨터에서 서버에 접속 하였습니다.");
// 클라이언트와 연결된 Socket 객체가 저장된 SocketThread 객체 생성
// > Thread 클래스를 상속받은 스레드 클래스로 객체 생성
SocketThread socketThread = new SocketThread(socket);
// List 객체의 요소로 SocketThread 객체 추가
clientList.add(socketThread);
// 스레드 객체를 이용하여 새로운 스레드 생성
// > 새로운 스레드는 run() 메소드를 호출하여 명령을 독립적으로 실행
socketThread.start();
} catch (IOException e) {
System.out.println("[에러로그]클라이언트의 접속 관련 문제가 발생 하였습니다.");
}
}
} catch (IOException e) {
System.out.println("[에러로그]서버가 정상적으로 동작되지 않습니다.");
}
}
public static void main(String[] args) {
new ChatServerApp();
}
// 현재 접속중인 모든 클라이언트에게 메세지를 전달하는 메소드
public void sendMessage(String message) {
// List 객체에 저장된 요소(SocketThread 객체)를 차례대로 제공받아 반복 처리
for(SocketThread socketThread : clientList) {
// SocketThread 객체의 out 필드에 저장된 출력스트림을 사용하여 메세지 전달
// > 외부클래스는 내부클래스로 객체를 생성하여 필드와 메소드를 접근제한자와 상관없이 접근 가능
socketThread.out.println(message);
}
}
// 클라이언트와 연결된 소켓을 이용하여 입출력 기능을 제공하기 위한 클래스
// > 독립적인 입력 또는 출력 기능을 제공하기 위해 새로운 스레드를 생성하여 명령을
// 실행할 수 있도록 Thread 클래스를 상속받아 run() 메소드를 오버라이드 선언
public class SocketThread extends Thread {
// 클라이언트와 연결된 Socket 객체를 저장하기 위한 필드
private Socket client;
// 클라이언트에서 보내온 메세지를 읽기 위한 입력스트림을 저장하기 위한 필드
private BufferedReader in;
// 클라이언트에게 메세지를 보내기 위한 출력스트림을 저장하기 위한 필드
private PrintWriter out;
public SocketThread(Socket client) {
this.client = client;
}
// 새로운 스레드가 실행할 명령을 작성하기 위한 메소드
// > 클라이언트에 보내온 메세지를 전달받아 모든 클라이언트에게 전달하는 명령 작성
@Override
public void run() {
// 클라이언트의 대화명을 저장하기 위한 변수 선언
String aliasName = "";
try {
// 소켓의 입력스트림을 제공받아 대량의 문자데이타를 읽을 수 있는 입력스트림으로 확장하여 필드에 저장
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 소켓의 출력스트림을 제공받아 문자열을 전달할 수 있는 출력스트림으로 확장하여 필드에 저장
// > PrintWriter 클래스의 PrintWriter(OutputStream out, boolean autuFlush)
// 생성자를 사용하여 PrintWriter 객체 생성
// > autuFlush 매개변수에 [true]를 전달하면 버퍼를 사용하지 않고 무조건 출력스트림으로 데이타 전달
out = new PrintWriter(client.getOutputStream(), true);
// 클라이언트에서 보내온 대화명을 반환받아 저장
// > 클라이언트가 대화명을 보내오기 전까지 스레드 일시 중지
aliasName = in.readLine();
// 현재 접속중인 모든 클라이언트에게 입장 메세지 전달
// > 내부클래스에서는 외부클래스의 필드 또는 메소드를 접근제한자에 상관없이 접근 가능
sendMessage("[" + aliasName + "]님이 입장 하였습니다.");
// 클라이언트에서 보내온 메세지를 전달받아 현재 접속중인 모든 클라이언트에게 전달
// > 클라이언트가 접속을 종료하기 전까지 반복 처리
// > 클라이언트가 접속을 종료한 경우 IOException 발생
while(true) {
sendMessage("[" + aliasName + "]" + in.readLine());
}
} catch (IOException e) { // IOException 면
// 클라이언트가 접속을 종료한 경우 실행될 명령 작성
// List 객체에 저장된 요소 중 접속중인 클라이언트의 정보(SocketThread 객체) 삭제
clientList.remove(this);
// 현재 접속중인 모든 클라이언트에게 퇴장 메세지 전달
sendMessage("[" + aliasName + "]님이 퇴장 하였습니다.");
System.out.println("[해제로그]" + client.getInetAddress().getHostAddress()
+ "의 컴퓨터에서 서버 접속을 종료 하였습니다.");
}
}
}
}
ChatClientApp
package xyz.itwill.net;
// 채팅 클라이언트 프로그램 - GUI
// > 서버에서 보내온 메세지를 전달받아 JTextArea 컴퍼넌트에 출력 - 무한루프
// > JTextField 컴퍼넌트에서 입력된 메세지를 서버에 전달 - 이벤트 처리 메소드
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.regex.Pattern;
public class ChatClientApp extends JFrame implements ActionListener { // implements ActionListener : JTextFielddptj 이벤트처리하는 메소드
private static final long serialVersionUID = 1L;
private JTextArea jTextArea; // 출력 컴퍼넌트
private JTextField jTextField; // 입력 컴퍼넌트
// 서버와 연결된 Socket 객체를 저장하기 위한 필드
private Socket socket;
// 서버에서 보내온 메세지를 전달받기 위한 입력스트림을 저장하기 위한 필드
private BufferedReader in;
// 서버에서 메세지를 보내기 위한 출력스트림을 저장하기 위한 필드
private PrintWriter out;
// 대화명을 저장하기 위한 필드
private String aliasName;
public ChatClientApp (String title) {
// 프레임에 컴퍼지던트 담아줌
super(title);
jTextArea = new JTextArea();
jTextField = new JTextField();
jTextArea.setFont(new Font("굴림체", Font.BOLD,20));
jTextField.setFont(new Font("굴림체", Font.BOLD, 20));
jTextArea.setFocusable(false);
JScrollPane jScrollPane = new JScrollPane(jTextArea);
getContentPane().add(jScrollPane, BorderLayout.CENTER);
getContentPane().add(jTextField, BorderLayout.SOUTH);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(700, 200, 400, 500);
setVisible(true);
// JTextField 컴퍼넌트에서 발생된 ActionEvent를 처리하기 위한 ActionListener 객체를 추가하여 이벤트 처리
jTextField.addActionListener(this);
try {
// Socket 객체를 생성하여 필드에 저장 - 서버 접속
socket = new Socket("192.000.00.00" , 5000);
// 192.000.00.00 내컴퓨터
// 소켓의 입력스트림을 제공받아 대량의 문자데이터를 읽을 수 있는 입력스트림으로 확장하여 필드에 저장
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 소켓의 출력스트림을 제공받아 문자열을 전달할 수 있는 출력스트림으로 확장하여 필드에 저장
out = new PrintWriter(socket.getOutputStream(), true);
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "서버에 접속할 수 없습니다."
, "접속오류", JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
// 정상적인 대화명을 입력받기 위한 반복문
while (true) {
aliasName = JOptionPane.showInputDialog(this, "대화명을 입력해 주세요."
, "대화명 입력", JOptionPane.QUESTION_MESSAGE);
String regEx = "^[가-힣]{2,6}$"; // 정규표현식 저장 (한글로 2글자 ~ 6글자)
// 정규표현식과 입력값(대화명)의 패턴이 같은 경우 반복문 종료
if (Pattern.matches(regEx, aliasName)) // 만약 정규입력값이 같다면 멈춤
break;
JOptionPane.showMessageDialog(this, "정상적인 대화명을 입력해 주세요."
, "입력오류", JOptionPane.ERROR_MESSAGE);
}
// 입력된 대화명을 서버에게 전달
out.println(aliasName);
// 서버에 보내온 메세지를 반환받아 JTextArea 컴퍼넌트에 추가하여 출력
// > 프로그램이 종료되기 전까지 무한 반복
while (true) {
try {
jTextArea.append(in.readLine() + "\n");
// JTextArea 컴퍼너트의 스크롤을 맨 끝으로 이동되도록 처리
jTextArea.setCaretPosition(jTextArea.getText().length());
} catch (IOException e) { // 서버 프로그램이 종료된 경우 발생
JOptionPane.showMessageDialog(this, "서버와 연결이 끊어졌습니다."
,"접속오류" , JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}
}
public static void main(String[] args) {
new ChatClientApp("자바채팅");
}
// JTextField 컴퍼넌트에서 입력된 문자열(메세지)을 반환받아 서버에 전달하는 명령
public void actionPerformed (ActionEvent e) {
String message = jTextField.getText();
if (!message.equals("")) { // 입력된 메세지가 존재할 경우
out.println(message); // 서버에 메세지 전달
jTextField.setText(""); // JTextField 컴퍼넌트 초기화
}
}
}