반응형
참고사이트
https://m.blog.naver.com/PostView.naver?blogId=irene-fts&logNo=221713585210&navType=by
01. 채팅 서버 프로그램
package xyz.itwill.net;
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 {
1) 필드
//접속된 모든 클라이언트의 소켓 정보를 저장하기 위한 콜렉션 필드
private List<SocketThread> clientList = null;
2) 생성자
//생성자를 호출하면 실행될 명령 작성
public ChatServerApp() {
ServerSocket chatServer = null;
try {
//ServerSocket객체 생성
//5000포트 열렸다! 여기로 들어와라~
chatServer = new ServerSocket(5000);
System.out.println("[메세지]채팅 서버 동작 중...");
//SocketThread의 ArrayList객체 생성
clientList = new ArrayList<SocketThread>();
//1. 클라이언트가 접속되길 기다림
//2. 클라이언트가 접속되면 '클라이언트와 연결된 소켓을 가진 Thread객체'를 만듦
//3. 리스트객체의 요소로 추가해줌 + 새로운 스레드 동작시킴
//4. 무한반복...
while(true) {
try {
//1.
//클라이언트가 접속되면 클라이언트와 연결된 Socket 객체를 반환받아 저장
Socket client = chatServer.accept();
//접속된 클라이언트가 누구인지 기록
System.out.println("[접속로그]"+client.getInetAddress().getHostAddress()+"의 컴퓨터에서 접속 하였습니다.");
//2.
//Thread클래스를 상속받은 자식클래스인 SocketThread로 객체 생성 - Thread 객체 생성
SocketThread socketThread = new SocketThread(client); //클라이언트와 연결된 소켓 정보
//3.
//콜렉션 필드에 저장된 List 객체에 요소(SocketThread 객체)를 추가하여 저장
clientList.add(socketThread); // 접속된 모든 클라이언트의 소켓정보
//4.🎩
//Thread 객체로 새로운 스레드를 생성하여 run() 메소드의 명령 실행
socketThread.start(); //socketThread객체의 run()메소드에 의해 입출력이 알아서 동작됨
}catch (IOException e) {
System.out.println("[에러로그]클라이언트의 접속 관련 문제가 발생 되었습니다."); //예외발생희미
}
}
} catch (IOException e) {
System.out.println("[에러로그]서버가 정상적으로 동작되지 않습니다."); //예외발생희미
}
}
3) 메인메소드
//메인메소드에서 생성자 호출
public static void main(String[] args) {
new ChatServerApp();
}
4) 메소드
//현재 서버에 접속된 모든 클라이언트에게 메세지를 전달하는 메소드
public void sendMessage(String message) {
//List 객체에 저장된 요소(SocketThread 객체)를 하나씩 제공받아 반복 처리
for(SocketThread client:clientList) {
//SocketThread 객체의 출력스트림을 이용하여 클라이언트에게 메세지 전달
client.out.println(message); //out=printWriter
}
}
5) (중첩 내부 Thread클래스)
//5. (내부클래스)
//🎩클라이언트와 연결된 소켓을 이용해 입출력 기능을 제공하기 위한 클래스
// => run( )메소드가 호출되면 새로운 스레드는 자동으로 입출력스트림을 만들어 메세지를 주고받게 해줄수 있도록 작성할 것이다
// => 독립적인 입력 또는 출력 기능을 제공하기 위해 새로운 스레드를 생성하여 실행되도록 설정
public class SocketThread extends Thread{
private Socket socket; //클라이언트의 소켓과 연결된 Socket 객체를 저장하기 위한 필드
private BufferedReader in; //클라이언트에서 보내온 메세지를 읽기 위한 입력스트림을 저장하기 위한
private PrintWriter out; //클라이언트로 메세지를 보내기 위한 출력스트림을 저장하기 위한 필드 - BufferedWriter보다 메소드 더 많아서 이용함
//소켓 생성자 필수, 매개변수가 없는 생성자는 존재할 수 없음ㅠㅠ, 무조건 소켓가져와야함
public SocketThread(Socket socket) {
this.socket = socket;
}
//새로운 스레드가 실행하기 위한 명령 작성 - 클라이언트의 메세지를 전달받아 모든 접속 클라이언트에게 전달하는 명령이 실행되도록 작성
@Override
public void run() {
String aliasName = ""; //클라이언트의 대화명을 저장하기 위한 변수 선언 (사용자의 이름)
try {
// => 클라이언트와 연결된 소켓의 입력스트림을 반환받아 대량의 문자데이타를 읽을 수 있는 입력스트림으로 확장
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// => 클라이언트와 연결된 소켓의 출력스트림을 반환받아 모든 형식의 값을 문자열로 전달할 수 있는 출력스트림으로 확장
//out = new PrintWriter(socket.getOutputStream()); //출력버퍼 사용 -> write() 후 flush() 사용해야함
// => PrintWriter(OutputStream out, boolean autoFlush) : autoFlush 매개변수에 [true]를 전달할 경우 출력버퍼를 사용하지 않고 출력스트림에 값을 직접 전달
out = new PrintWriter(socket.getOutputStream(), true); //flush() 사용안해도 됨
//클라이언트에서 보내온 대화명을 얻어와 변수에 저장 - 클라이언트에서 대화명을 입력받아 전달할 때까지 스레드 일시 중지
aliasName = in.readLine();
//현재 접속된 모든 클라이언트에게 입장 메세지를 전달
sendMessage("["+aliasName+"]님이 입장 하였습니다.");
//클라이언트에서 보내온 메세지를 전달받아 현재 접속된 모든 클라이언트에게 입장 메세지를 전달
// => 클라이언트가 접속을 종료하기 전까지 반복처리
// => 클라이언트가 접속을 종료하면 (클라이언트의 소켓이 사라지면)클라이언트와 연결된 입력스트림과 출력스트림이 제거되어 IOException 발생
while(true) {
sendMessage("["+aliasName+"]"+ in.readLine()); //보내온 메세지가 없다면 스레드는 일시중지되어있음
}
}catch (IOException e) {
//클라이언트가 접속을 종료한 경우 실행될 명령 작성
// => 콜렉션필드에 저장된 List 객체에서 접속 종료된 클라이언트의 소켓정보(SocketThread객체)를 삭제
// => 현재 접속 중인 모든 클라이언트에게 퇴장메세지 전달
clientList.remove(this); //종료된 클라이언트의 정보 삭제, this 키워드로 현재 SocketThread 객체 표현
sendMessage("["+aliasName+"]님이 퇴장 하였습니다."); //퇴장메세지 전달
System.out.println("[해제로그]"+socket.getInetAddress().getHostAddress()+"의 컴퓨터에서 접속을 종료 하였습니다.");
}
}
02. 채팅 클라이언트 프로그램
package xyz.itwill.net;
import java.awt.BorderLayout;
import java.awt.Font;
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 javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
//채팅 클라이언트 프로그램 - Swing
// => 서버에서 보내온 메세지를 전달받아 JTextArea 컴퍼넌트에 출력 - 무한루프
// => JTextField 컴퍼넌트에서 입력한 메세지를 서버에 전달 - 이벤트 처리 메소드
// => 이벤트처리프로그램은 이벤트큐가 있기 때문에 원래부터 다중 스레드 프로그램임!! - javax.Swing패키지 이용
//채팅 클라이언트 프로그램의 역할
// => 서버에서 보내 온 메세지를 전달받아 JTextArea 컴포넌트에 출력할 것이다. - **무한루프 (main스레드 이용)
// =>** JTextField 컴포넌트에서 입력한 메세지를 서버에 전달할 것이다. - 이벤트 처리 메소드를 통해 서버에서 가져온 데이타를 area에 출력할 것임!
public class ChatClientApp extends JFrame implements ActionListener{
1) 필드
// => 디자인클래스를 만들기 위한 + 이벤트처리에서 사용하기 위한 필드
private static final long serialVersionUID = 1L;
private JTextArea area; //출력 컴포넌트
private JTextField field; //입력 컴포넌트 , 이벤트에서 사용하기 위해 필드로 만듦..
// => 서버에서 필요한 값을 서로 입출력하기 위한 필드
private Socket socket; //서버의 소켓과 연결된 소켓정보를 저장하기 위한 필드
private BufferedReader in; //서버에서 보내온 메세지를 읽기 위한 입력스트림을 저장하기 위한 필드
private PrintWriter out; //서버에서 메세지를 보내기 위한 출력스트림을 저장하기 위한 필드
private String aliasName; //대화명을 저장하기 위한 필드
2) 생성자
//생성자를 호출하면 실행될 명령 작성
// => swing패키지를 이용한 디자인 작성
// => Socket 객체 + 입출력스트림
public ChatClientApp(String title) {
super(title);
area = new JTextArea();
field = new JTextField();
JScrollPane pane = new JScrollPane(area);
getContentPane().add(pane, BorderLayout.CENTER);
getContentPane().add(field, BorderLayout.SOUTH);
area.setFont(new Font("굴림체",Font.BOLD, 20));
field.setFont(new Font("굴림체",Font.BOLD, 20));
area.setFocusable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(700,200,400,500);
setVisible(true);
//JTextField 컴포넌트에서 이벤트가 발생될 경우 실행될 이벤트 처리 객체 등록
//=> 클라이언트가 메세지를 입력한 경우 실행될 명령을 이벤트 처리 메소드에 작성
field.addActionListener(this);
//디자인 완성 후..
try {
//Socket 객체 생성 - 채팅 서버에 접속
socket = new Socket("192.168.13.10",5000);
//서버와 연결된 소켓의 입력스트림을 반환받아 대량의 문자데이타를 읽을 수 있는 입력스트림으로 확장
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//서버와 연결된 소켓의 출력스트림을 반환받아 모든 형식의 값을 문자열로 전달할 수 있는 출력스트림으로 확장
out = new PrintWriter(socket.getOutputStream(), true); //flush() 사용안해도 됨
} catch (IOException e) {
//보여줄메세지 / 메세지제목/ 메세지타입(에러메세지)
JOptionPane.showMessageDialog(this, "서버에 접속할 수 없습니다.","접속오류",JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
//대화명을 입력받아 필드에 저장 - 정상적인 대화명이 입력되도록 무한루프 이용
while(true) {
//JOptionPane.showInputDialog(Component parent, String message, String title, int messageType) : 입력 다이얼로그를 보여주는 메소드 - 입력값을 문자열로 반환
aliasName = JOptionPane.showInputDialog(this,"대화명을 입력해 주세요.","대화명 입력",JOptionPane.QUESTION_MESSAGE);
//정상적인 대화명을 입력한 경우 반복문 종료
if(aliasName !=null && aliasName.equals("")) break;
JOptionPane.showMessageDialog(this, "대화명을 반드시 입력해주세요.","입력오류",JOptionPane.ERROR_MESSAGE);
}
//서버에 대화명을 전달
out.println(aliasName);
//서버에서 보내온 메세지를 전달받아 JTextArea 컴포넌트에 추가하여 출력 - 무한루프
while(true) {
try {
area.append(in.readLine() + "\\n");
//JTextArea 컴포넌트의 스크롤을 맨 아래로 이동 처리
area.setCaretPosition(area.getText().length());
}catch (IOException e) {
JOptionPane.showMessageDialog(this, "서버와 연결이 끊어졌습니다.","접속오류",JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}
}
3) 메인메소드
//메인메소드에서 생성자 호출
public static void main(String[] args) {
new ChatClientApp("자바채팅");
}
4) 오버라이드선언된 메소드
//이벤트 처리 메소드에서는 JTextField 컴포넌트의 입력값(메세지)을 반환받아 서버에 전달
@Override
public void actionPerformed(ActionEvent e) {
//JTextField 컴포넌트의 입력값을 반환받아 저장
String message = field.getText();
if(!message.equals("")) { //입력메세지가 존재하는 경우
out.println(message); //서버에 메세지 전달
field.setText(""); //JTextField 컴포넌트 초기화
}
}
}
반응형
'java > java.net' 카테고리의 다른 글
[java.net] 4. UDP 프로토콜 프로그램 (0) | 2024.07.03 |
---|---|
[java.net] 3. TCP 프로토콜 프로그램 (0) | 2024.07.03 |
[java.net] 2. IPAddress 클래스 | ServerSocket 클래스 (0) | 2024.07.02 |
[java.net] 1. 프로토콜의 구조 (0) | 2024.07.02 |