채팅 서버 프로그램

  • 클라이언트의 접속으로 생성된 소켓을 사용해 클라이언트가 보내온 메세지를 전달받아 모든 클라이언트에게 전달하는 기능 구현

  • 클라이언트의 접속으로 생성된 소켓은 새로운 스레드를 생성하여 독립적으로 입력 또는 출력 처리되도록 다중 스레드 프로그램으로 작성


ChattingServerApp

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("자바채팅");
    }
}
profile
최선을 다해 꾸준히 노력하는 개발자 망고입니당 :D

0개의 댓글