Java_ 소켓

Java_ 채팅, 스레드

치개 2022. 10. 20. 15:32
  • 스레드
    • 프로세스 내에서 실제로 작업을 수행하는 주체
    • 모든 프로세스는 한 개 이상의 스레드를 갖고 있음 -> 여러 개의 스레드를 가지면 멀티스레드
    • 프로그램을 한 줄 씩 실행
  • 멀티 스레드
    • 여러 개의 스레드를 가짐
    • 프로세스처럼 작업을 동시에 처리 가능
    • 프로세스보다 오버헤드가 적음
    • 적용 조건 -> 병행성, 동기화, 통신

 

  • 자바 채팅 프로그램 (Swing)

 

  • ChatServer.java
//서버와 포트번호만을 준비하는 클래스
//클라이언트가 접속하게되면 그 클라이언트의 정보를 ChatHandler 클래스에 전달
public class ChatServer {
    private ServerSocket serverSocket; //서버 소켓 생성
    private List<ChatHandler> list;
    public ChatServer() {
        try {
            serverSocket = new ServerSocket(1234);
            System.out.println("서버 실행");
            list = new ArrayList<ChatHandler>();
            while (true) {
                Socket socket = serverSocket.accept();
                ChatHandler handler = new ChatHandler(socket, list);
                //스레드 시작
                handler.start();
                // 핸들러를 담음 (클라이언트의 갯수)
                list.add(handler);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new ChatServer();
    }
}

 

  • InfoDTO.java
//상수의 집합
//JOIN, EXIT, SEND 3가지의 명령어에 따라 다른 역할 수행
enum Info {
    JOIN, EXIT, SEND
}

public class InfoDTO implements Serializable {
    private String nickName;
    private String message;
    //enum Info{}
    private Info command;

    //롬복 Getter 대체 가능
    public String getNickName() {
        return nickName;
    }

    public String getMessage() {
        return message;
    }

    public Info getCommand() {
        return command;
    }

    //롬복 Setter 대체 가능
    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setCommand(Info command) {
        this.command = command;
    }
}

 

  • ChatHandler.java
//소켓 처리단
//서버의 중요 기능을 담당
//클라이언트로부터 정보를 전달받아 사용자의 서버에 메세지 내용을 뿌려주는 서버
//스레드의 역할
public class ChatHandler extends Thread{
    private ObjectInputStream reader;
    private ObjectOutputStream writer;
    private Socket socket;
    private List<ChatHandler> list; //멀티 스레드

    public ChatHandler(Socket socket, List<ChatHandler> list) throws IOException {
        this.socket = socket;
        this.list = list;
        writer = new ObjectOutputStream(socket.getOutputStream());
        reader = new ObjectInputStream(socket.getInputStream());
    }

    public void run() {
        InfoDTO dto = null;
        String nickName;
        try {
            while (true) {
                dto = (InfoDTO)reader.readObject();
                nickName = dto.getNickName();

                //사용자가 접속을 종료했을 때 퇴장하였다는 메세지
                if (dto.getCommand()==Info.EXIT) {
                    InfoDTO sendDto = new InfoDTO();
                    sendDto.setCommand(Info.EXIT);
                    writer.writeObject(sendDto);
                    writer.flush();

                    reader.close();
                    writer.close();
                    socket.close();

                    list.remove(this);  //퇴장한 유저를 삭제함

                    sendDto.setCommand(Info.SEND);  //퇴장 메세지 전달
                    sendDto.setMessage(nickName+"님 퇴장하였습니다");
                    broadcast(sendDto);
                    break;
                } else if (dto.getCommand()==Info.JOIN) {
                    //사용자가 접속하면 입장 메세지 전달
                    InfoDTO sendDto = new InfoDTO();
                    sendDto.setCommand(Info.SEND);
                    sendDto.setMessage(nickName+"님 입장하였습니다");
                    broadcast(sendDto);
                } else if (dto.getCommand()==Info.SEND) {
                    //사용자가 메세지를 입력하면 전달하고 출력
                    InfoDTO sendDto = new InfoDTO();
                    sendDto.setCommand(Info.SEND);
                    sendDto.setMessage("["+nickName+"]"+dto.getMessage());
                    broadcast(sendDto);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }catch (ClassNotFoundException e2) {
            e2.printStackTrace();
        }
    }

    //다른 클라이언트에게 전체 메세지 전달
    public void broadcast(InfoDTO sendDto) throws IOException {
        for (ChatHandler handler : list) {
            //핸들러의 writer 값 보냄
            handler.writer.writeObject(sendDto);
            //핸들러 안의 writer 값 비워줌 -> 모두 보냄
            handler.writer.flush();
        }
    }
}

 

  • ChatClient.java
//JFrame 을 사용하여 윈도우 창을 띄워준다. -> JFrame 은 스윙 클래스의 일, 구현되는 하나의 창
//끊임없는 데이터 교환을 위한 Runnable 을 implements
//자바 Swing -> 자바의 GUI 패키지 ----> AWT 보다 가볍고 컴포넌트가 자바로 작성되어 있어 어떤 플랫폼에서도 일관된 화면 작성 가능
public class ChatClient extends JFrame implements ActionListener, Runnable {
//Runnable -> 스레드의 인터페이스화 된 형태
//JAVA 에서는 다중상속이 불가능하여 스레드를 상속받지 못하는 경우 Implments로 Runnable을 받아서 구현
    private JTextArea output;
    private JTextField input;
    private JButton sendBtn;
    private Socket socket; //소켓
    private ObjectInputStream reader = null;    //객체를 직렬화하여 저장
    private ObjectOutputStream writer = null;   //직렬화한 데이터를 역직렬화

    private String nickName;

    public ChatClient() {
        //센터에 TextArea 만들기
        output = new JTextArea();
        output.setFont(new Font("맑은 고딕", Font.BOLD, 15));
        output.setEditable(false);
        JScrollPane scroll = new JScrollPane(output);
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        //하단에 버튼과 TextArea 넣기
        JPanel bottom = new JPanel();
        bottom.setLayout(new BorderLayout());
        input = new JTextField();

        sendBtn = new JButton("보내기");
        //가운데 정렬
        bottom.add("Center", input);
        //오른쪽 정렬
        bottom.add("East", sendBtn);

        Container c = this.getContentPane();
        //가운데 정렬
        c.add("Center", scroll);
        //아래쪽 정렬
        c.add("South", bottom);
        //윈도우 창 설정
        setBounds(300, 300, 300, 300);
        setVisible(true);

        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                try {
                    InfoDTO dto = new InfoDTO();
                    dto.setNickName(nickName);
                    dto.setCommand(Info.EXIT);
                    writer.writeObject(dto);
                    writer.flush();
                }catch (IOException e2){
                    e2.printStackTrace();
                }
            }
        });
    }

    public static void main(String[] args) {
        new ChatClient().service();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            //JTextField 의 값을 서버로 보냄
            String msg = input.getText();
            InfoDTO dto = new InfoDTO();

            if (msg.equals("exit")) {
                dto.setCommand(Info.EXIT);
            } else {
                dto.setCommand(Info.SEND);
                dto.setMessage(msg);
                dto.setNickName(nickName);
            }
            writer.writeObject(dto);
            writer.flush();
            input.setText("");
        }catch (IOException e2) {
            e2.printStackTrace();
        }
    }

    public void service() {
        //서버 IP 입력받기
        String serverIp = JOptionPane.showInputDialog(this, "서버 IP를 입력하세요", "127.0.0.1");
        //값이 입력되지않으면 서버 꺼짐
        if (serverIp==null || serverIp.length()==0) {
            System.out.println("서버 IP가 입력되지 않았습니다.");
            System.exit(0);
        }

        nickName = JOptionPane.showInputDialog(this, "닉네임을 입력하세요", "닉네임", JOptionPane.INFORMATION_MESSAGE);
        if (nickName==null || nickName.length()==0) {
            nickName="guest";
        }

        try {
            socket = new Socket(serverIp, 1234);
            reader = new ObjectInputStream(socket.getInputStream());
            writer = new ObjectOutputStream(socket.getOutputStream());
            System.out.println("전송 준비 완료");
        } catch (UnknownHostException e) {
            System.out.println("서버를 찾을 수 없습니다.");
            e.printStackTrace();
            System.exit(0);
        } catch (IOException e2) {
            System.out.println("서버와 연결되지 않았습니다.");
            e2.printStackTrace();
            System.exit(0);
        }

        try {
            InfoDTO dto = new InfoDTO();
            dto.setCommand(Info.JOIN);
            dto.setNickName(nickName);
            writer.writeObject(dto);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Thread thread = new Thread(this);
        thread.start();
        input.addActionListener(this);
        sendBtn.addActionListener(this);
    }

    @Override
    public void run() {
        InfoDTO dto = null;
        while (true) {
            try {
                dto = (InfoDTO) reader.readObject();
                if (dto.getCommand()==Info.EXIT) {
                    reader.close();
                    writer.close();
                    socket.close();
                    System.exit(0);
                } else if (dto.getCommand()==Info.SEND) {
                    output.append(dto.getMessage() + "\n");

                    int pos = output.getText().length();
                    output.setCaretPosition(pos);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e2) {
                e2.printStackTrace();
            }
        }
    }
}

 

 

IP 입력 화면
닉네임 입력 화면
양방향 채팅 화면
채팅에서 나간 화면

 

 

 

GitHub - Pearlmoon997/Socket: 자바 소켓 프로그래밍

자바 소켓 프로그래밍. Contribute to Pearlmoon997/Socket development by creating an account on GitHub.

github.com