클라이언트의 접속으로 생성된 소켓을 사용해 클라이언트가 보내온 메세지를 전달받아 모든 클라이언트에게 전달하는 기능 구현
클라이언트의 접속으로 생성된 소켓은 새로운 스레드를 생성하여 독립적으로 입력 또는 출력 처리되도록 다중 스레드 프로그램으로 작성
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.List;
import java.util.Vector;
public class ChattingServerApp {
//현재 서버에 접속중인 클라이언트와 연결된 소켓이 요소에 저장된 List 객체를 저장할 필드
private List<SocketThread> clientList;
public ChattingServerApp() {
ServerSocket chattingServer=null;
//Vector 객체(List 객체)를 생성하여 필드에 저장
clientList=new Vector<SocketThread>();
try {
chattingServer = new ServerSocket(5000);
System.out.println("[메세지]채팅 서버 동작중...");
while(true) {
Socket socket = chattingServer.accept();
//생성자 매개변수에 클라이언트와 연결된 정보가 저장된 Socket 전달하여 SocketThread 객체 생성
SocketThread socketThread = new SocketThread(socket);
//Vector 객체(List 객체)에 소켓이 저장된 SocketThread 객체를 요소에 저장하여 추가
clientList.add(socketThread);
//SocketThread 객체(Thread 객체)를 사용하여 새로운 스레드를 생성하여 run() 메소드의 명령 실행
socketThread.start();
System.out.println("[접속로그]"+socket.getInetAddress().getHostAddress()
+"의 컴퓨터에서 채팅서버에 접속 하였습니다.");
}
} catch (IOException e) {
System.out.println("[에러로그]서버가 정상적으로 동작되지 않습니다.");
}
}
public static void main(String[] args) {
new ChattingServerApp();
}
//매개변수로 전달받은 문자열(메세지)를 서버에 접속중인 모든 클라이언트에게 전달하는 메소드
public void sendMessage(String message) {
//Vector 객체(List 객체)에 저장된 요소값(SocketThread 객체)를 차례대로 제공받아 처리하는 반복문
for(SocketThread socketThread : clientList) {
//SocketThread 객체의 out 필드에 저장된 출력스트림을 사용하여 문자열(메세지) 전달
// => 외부클래스의 메소드에서는 내부클래스로 생성된 객체를 사용해 접근제한자에
//상관없이 필드 또는 메소드 사용 가능
socketThread.out.println(message);
}
}
// 채팅 스레드
public class SocketThread extends Thread {
private Socket socket;
//클라이언트에서 보내온 메세지를 전달받기 위한 입력스트림을 저장하기 위한 필드
private BufferedReader in;
//클라이언트에게 메세지를 전달하기 위한 출력스트림을 저장하기 위한 필드
private PrintWriter out;
public SocketThread(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
//클라이언트의 대화명을 저장하기 위한 변수
String aliasName="";
try {
//소켓의 입력스트림을 사용해 대량의 문자데이타(문자열)를 전달받을 수 있는 입력스트림으로 확장
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//소켓의 출력스트림을 사용해 모든 자료형의 값을 문자열로 전달할 수 있는 출력스트림으로 확장
// => PrintWriter(OutputStream out, boolean autoFlush) 생성자로 PrintWriter 객체 생성
// => autoFlush 매개변수에 [true]를 전달할 경우 출력버퍼를 사용하지 않고
//출력스트림으로 데이타를 직접 전달할 수 있도록 처리
out=new PrintWriter(socket.getOutputStream(), true);
//클라이언트에서 보내온 대화명(처음 들어온 텍스트가 닉네임)을 입력스트림으로 반환받아 저장
aliasName=in.readLine();
//현재 접속중인 모든 클라이언트에게 입장 메세지를 출력스트림으로 전달
// => 내부클래스의 메소드에서는 외부클래스의 필드 또는 메소드를 접근제한자에
//상관없이 사용 가능
sendMessage("["+aliasName+"]님이 입장 하였습니다.");
//클라이언트에서 보내온 메세지를 전달받아 현재 서버에 접속중인 모든 클라이언트에게 전달
// => 클라이언트가 서버 접속을 종료하기 전까지 반복 처리
// => 클라이언트가 서버 접속을 종료하면 IOException 발생 - 반복문 종료
while(true) {
sendMessage("["+aliasName+"]"+in.readLine());
}
} catch (IOException e) {//클라이언트가 서버 접속을 종료한 경우 실행될 명령 작성
//서버에 접속된 클라이언트의 소켓이 요소로 저장된 List 객체에서 접속을 종료한
//클라이언트의 소켓을 삭제 처리
clientList.remove(this);//this >> SocketThread 객체
//현재 서버에 접속중인 모든 클라이언트에게 퇴장 메세지 전달
sendMessage("["+aliasName+"]님이 퇴장 하였습니다.");
System.out.println("[종료로그]"+socket.getInetAddress().getHostAddress()
+"의 컴퓨터에서 채팅서버의 접속을 종료 하였습니다.");
}
}
}
}
JTextField 컴퍼넌트로 입력한 메세지를 서버에 전달 - 이벤트 처리 객체(EventQueue 스레드)
서버에서 보내온 메세지를 전달받아 JTextArea 컴퍼넌트에 출력 처리 - 무한루프(main 스레드)
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;
//채팅 클라이언트 프로그램 작성
// => JTextField 컴퍼넌트로 입력한 메세지를 서버에 전달 - 이벤트 처리 객체(EventQueue 스레드)
// => 서버에서 보내온 메세지를 전달받아 JTextArea 컴퍼넌트에 출력 처리 - 무한루프(main 스레드)
public class ChattingClientApp extends JFrame {
private static final long serialVersionUID = 1L;
//이벤트 처리 메소드에서 사용할 컴퍼넌트를 저장하기 위한 필드
private JTextArea textArea;//출력 컴퍼넌트
private JTextField textField;//입력 컴퍼넌트
//접속 서버의 정보가 저장된 Socket 객체를 저장하기 위한 필드
private Socket socket;
//서버에서 보내온 메세지를 전달받기 위한 입력스트림을 저장하기 위한 필드
private BufferedReader in;
//서버에게 메세지를 전달하기 위한 출력스트림을 저장하기 위한 필드
private PrintWriter out;
//대화명을 저장하기 위한 필드
private String aliasName;
public ChattingClientApp(String title) {
super(title);
textArea=new JTextArea();
textField=new JTextField();
textArea.setFont(new Font("굴림체", Font.BOLD, 20));
textField.setFont(new Font("굴림체", Font.BOLD, 20));
textArea.setFocusable(false);
JScrollPane scrollPane=new JScrollPane(textArea);
getContentPane().add(scrollPane, BorderLayout.CENTER);
getContentPane().add(textField, BorderLayout.SOUTH);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(700, 200, 400, 500);
setVisible(true);
//JTextField 컴퍼넌트에서 ActionEvent가 발생될 경우 이벤트 처리하기 위한 ActionListener 객체 추가
// => ActionListener 인터페이스를 상속받은 익명의 클래스를 사용하여 ActionListener 객체 생성
textField.addActionListener(new ActionListener() {
//JTextField 컴퍼넌트에 입력된 문자열(메세지)을 반환받아 서버에 전달하는 명령 작성
@Override
public void actionPerformed(ActionEvent e) {
//JTextField 컴퍼넌트에 입력된 문자열(메세지)을 반환빋이 저장
String message=textField.getText();
if(!message.equals("")) {//반환받은 문자열(메세지)이 있는 경우
//서버와 연결된 출력스트림을 사용하여 문자열(메세지) 전달
out.println(message);
//JTextField 컴퍼넌트 초기화 처리 - 기존에 입력된 문자열(메세지) 제거
textField.setText("");
}
}
});
try {
//Socket 객체를 생성하여 필드에 저장 - 서버 접속
socket=new Socket("xxx.xxx.xxx.xxx", 5000);
//소켓의 입력스트림을 사용해 대량의 문자데이타(문자열)를 전달받을 수 있는 입력스트림으로 확장
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);
//대화명 : 2~6 범위의 한글만 입력받아 사용되도록 설정 - 정규표현식
String regEx="^[가-힣]{2,6}$";
//정상적인 대화명인 경우 반복문 종료
if(aliasName !=null && Pattern.matches(regEx, aliasName)) break;
//메세지 다이얼로그를 사용해 에러메세지 출력
JOptionPane.showMessageDialog(this, "2~6 범위의 한글로 작성된 대화명을 입력해 주세요."
, "입력오류", JOptionPane.ERROR_MESSAGE);
}
//입력받은 대화명을 출력스트림을 사용해 서버에 전달
out.println(aliasName);
//서버에서 보내온 메세지를 입력스트림을 사용해 전달받아 JTextArea 검퍼넌트에 출력하여 출력
// => 클라이언트 프로그램이 종료되기 전까지 반복 처리
while(true) {
try {
//입력스트림을 사용해 서버에서 보내온 메세지를 반환받아 JTextArea 컴퍼넌트에 추가하여 출력 처리
textArea.append(in.readLine()+"\n");
//JTextArea 컴퍼넌트의 스크롤을 마지막 위치로 이동되도록 처리
textArea.setCaretPosition(textArea.getText().length());
} catch (IOException e) {//네크워크 문제로 서버 접속이 종료된 경우
JOptionPane.showMessageDialog(this, "서버와의 연결이 끊어졌습니다."
, "접속오류", JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}
}
public static void main(String[] args) {
new ChattingClientApp("자바채팅");
}
}