IT/Java

java - 쓰레드를 적용한 채팅 소스(Thread Chatting)

노마드오브 2018. 11. 23. 20:36

MyChatServer 프로젝트


ChatServer.java


import java.io.BufferedWriter;

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.HashMap;

import java.util.Map;


public class ChatServer {

    // 스레드간의 정보를 공유할 HashMap

    // key는 채팅별명(아이디)이고 value는 출력스트림

    Map<String, BufferedWriter> map;


    public ChatServer() {

        map = new HashMap<>();


        ServerSocket serverSocket = null; // 서버소켓

        Socket socket = null; // 클라이언트와 통신하기 위한 소켓

        final int PORT = 6000; // 포트번호


        try {

            serverSocket = new ServerSocket(PORT);


            System.out.println("*** 채팅 서버 ***");

            System.out.println("서버는 클라이언트 소켓의 접속요청을 기다리고 있음");


            while (true) {

                socket = serverSocket.accept();


                ChatServerRunnable chatServerRunnable = new ChatServerRunnable(socket, map);

                Thread thread = new Thread(chatServerRunnable);

                thread.start(); // 스레드 시작

            }

        } catch (IOException e) {

            e.printStackTrace();

            System.exit(0);

        } finally {

            if (serverSocket != null) {

                try {

                    serverSocket.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }

    } // ChatServer()


    public static void main(String[] args) {

        new ChatServer();

    } // main()


}




ChatServerRunnable.java


import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.net.InetAddress;

import java.net.Socket;

import java.util.Collection;

import java.util.Iterator;

import java.util.Map;


public class ChatServerRunnable implements Runnable {

    Socket socket; // 매번 새로운 클라이언트 정보의 소켓

    Map<String, BufferedWriter> map; // 공유데이터

    

    BufferedReader reader = null; // 클라이언트로부터 데이터를 수신받기 위한 스트림

    BufferedWriter writer = null; // 클라이언트에게 데이터를 송신하기 위한 스트림

    

    String userId; // 접속자의 아이디(채팅이름)

    


    public ChatServerRunnable(Socket socket, Map<String, BufferedWriter> map) {

        this.socket = socket;

        this.map = map;

        

        InetAddress inetAddress = socket.getInetAddress();

        System.out.println(inetAddress + "로부터 연결요청 받음");

        

        try {

            reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));

            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));

            

            // 새로운 클라이언트가 접속해서 처음보낸 문자열은 아이디

            userId = reader.readLine();

            broadcast(userId + "님이 접속하셨습니다.");

            System.out.println("접속한 클라이언트의 아이디는 " + userId + "입니다.");

            

            // 여러 스레드가 공유하는 맵을 동기화함

            synchronized (map) {

                // 맵은 모든 클라이언트에 의해 공유되어

                // 메시지 브로드캐스팅을 위해 출력스트림을 맵에 저장

                map.put(userId, writer);

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    } // ChatRunnable



    @Override

    public void run() {

        String line = "";

        

        try {

            while (true) {

                line = (String) reader.readLine();

                

                if (line.equals("/quit")) { // "/quit" 종료

                    break;

                } else { // 받은 메시지를 모든 클라이언트에게 브로드캐스팅함

                    broadcast(userId + " : " + line);

                }

            } // while

        } catch (Exception e) {

            e.printStackTrace();

        } finally { // "/quit" 종료 명령어 메시지가 오면

            synchronized (map) {

                map.remove(userId); // 종료명령어를 보낸 클라이언트의 정보를 맵에서 삭제

            }

            broadcast(userId + "님이 나가셨습니다."); // 나머지 클라이언트들에게 접속종료 알림

            System.out.println(userId + "님이 나가셨습니다."); // 서버에도 출력

            

            try {

                if (socket != null) {

                    socket.close(); // 나간 클라이언트 소켓객체를 닫기

                } 

            } catch (Exception e2) {

                e2.printStackTrace();

            }

        }

    } // run()

    

    // 받은 메시지를 모든 클라이언트에게 브로드캐스팅하기 위한 메소드

    public void broadcast(String message) {

        synchronized (map) {

            Collection<BufferedWriter> collection = map.values();

            

            try {

                Iterator<BufferedWriter> iter = collection.iterator();

                while (iter.hasNext()) {

                    BufferedWriter writer = iter.next();

                    writer.write(message + "\n");

                    writer.flush();

                }

                

//                for (BufferedWriter writer : collection) {

//                    writer.write(message + "\n");

//                    writer.flush();

//                } 

            } catch (Exception e) {

                e.printStackTrace();

            }

        } // synchronized

    } // broadcast()


} // ChatRunnable class






MyChatClient 프로젝트


GUIChatClient.java


import java.awt.BorderLayout;

import java.awt.CardLayout;

import java.awt.Container;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.net.Socket;


import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JLabel;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTextArea;

import javax.swing.JTextField;


public class GUIChatClient extends JFrame implements ActionListener {

    

    static final int SERVER_PORT = 6000; // 포트번호

    Socket socket;

    BufferedReader reader;  // 서버로부터 데이터를 전송받기 위한 스트림

    BufferedWriter writer; // 서버에게 데이터를 전송하기 위한 스트림

    String userId;  // 접속자의 아이디(채팅이름)을 저장할 변수 선언

    

    // 화면구성

    CardLayout cardLayout;

    JTextField tfServerIp; // 접속할 서버IP를 입력받을 텍스트필드

    JTextField tfName; // 접속 대화명을 입력받을 텍스트필드

    JButton btnConnect; // 서버 접속 버튼

    JTextArea textArea; // 채팅내용 출력 용도

    JScrollPane scrollPane;

    JTextField tfInput; // 채팅내용 입력할 텍스트필드

    JButton btnSend; // 채팅내용(문자열) 전송 버튼

    JButton btnExit; // 프로그램 종료 버튼

    Container contentPane;

    

    public GUIChatClient() {

        setTitle("채팅 클라이언트");

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        

        contentPane = getContentPane();

        

        cardLayout = new CardLayout();

        contentPane.setLayout(cardLayout);

        

        // 접속화면

        JPanel connectPanel = new JPanel();

        connectPanel.setLayout(new BorderLayout());

        

        connectPanel.add(new JLabel("다중 채팅 접속화면", JLabel.CENTER), BorderLayout.NORTH);

        

        // 접속창 하위패널

        JPanel subConnectPanel = new JPanel();

        

        subConnectPanel.add(new JLabel("서버 아이피: "));

        tfServerIp = new JTextField("127.0.0.1", 15);

        subConnectPanel.add(tfServerIp);

        

        subConnectPanel.add(new JLabel("   대화명: "));

        tfName = new JTextField("아이언맨", 15);

        subConnectPanel.add(tfName);

        

        connectPanel.add(subConnectPanel, BorderLayout.CENTER);

        

        btnConnect = new JButton("서버 접속");

        connectPanel.add(btnConnect, BorderLayout.SOUTH);

        

        btnConnect.addActionListener(this);

        

        

        // 채팅화면

        JPanel chatPanel = new JPanel();

        chatPanel.setLayout(new BorderLayout());

        

        chatPanel.add(new JLabel("채팅 프로그램", JLabel.CENTER), BorderLayout.NORTH);

        textArea = new JTextArea(10, 35); // 채팅내용 출력창 용도

        scrollPane = new JScrollPane(textArea);

        chatPanel.add(scrollPane, BorderLayout.CENTER);

        

        JPanel subChatPanel = new JPanel(); // 채팅창 하위패널

        tfInput = new JTextField("", 13);

        btnSend = new JButton("전송");

        btnExit = new JButton("종료");

        

        tfInput.addActionListener(this);

        btnSend.addActionListener(this);

        btnExit.addActionListener(this);

        

        subChatPanel.add(tfInput);

        subChatPanel.add(btnSend);

        subChatPanel.add(btnExit);

        

        chatPanel.add(subChatPanel, BorderLayout.SOUTH);

        

        

        contentPane.add("접속창", connectPanel);

        contentPane.add("채팅창", chatPanel);

        

        cardLayout.show(contentPane, "접속창");

        

        setLocationByPlatform(true);

        setSize(300, 300);

        setVisible(true);

    } // GUIChatClient()

    

    // 

    public void init() throws IOException {

        String ipAddr = tfServerIp.getText();

        socket = new Socket(ipAddr, SERVER_PORT);

        System.out.println("ipAddr : " + ipAddr);

        if (socket != null) {

            System.out.println("socket not null");

        }

        

        reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));

        writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));

        

        if (reader != null) {

            System.out.println("reader not null");

        }

        if (writer != null) {

            System.out.println("writer not null");

        }

        

        userId = tfName.getText(); // 대화명 가져와서

        writer.write(userId + "\n");  // 서버에게 전송

        writer.flush();

        System.out.println("userId : " + userId);

        

        // 서버가 보낸 데이터를 수신받기 위한 스레드 준비 후 시작

        Thread thread = new Thread(new ReadRunnable());

        thread.start();

        

        cardLayout.show(contentPane, "채팅창"); // 채팅창으로 화면전환

        tfInput.requestFocus(); // 포커스 주기

    } // init()

    

    // 서버가 보낸 데이터를 수신받기 위한 내부클래스

    class ReadRunnable implements Runnable {


        @Override

        public void run() {

            String line = ""; // 서버로부터 받은 데이터를 저장하기 위한 변수

            try {

                while (true) {

                    line = reader.readLine(); // 서버 데이터 수신

                    textArea.append(line + "\n");

                    textArea.setCaretPosition(textArea.getText().length());

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

        } // run()

    } // class ReadRunnable

    


    @Override

    public void actionPerformed(ActionEvent e) {

        Object obj = e.getSource();

        

        try {

            if (obj == btnConnect) { // 채팅서버 접속버튼

                init();

            } else if (obj == tfInput || obj == btnSend) { // 텍스트필드에서 엔터키를 쳤거나 전송버튼 눌렸을때

                String sendMsg = tfInput.getText();

                writer.write(sendMsg + "\n");

                writer.flush();

                

                tfInput.setText("");

                tfInput.requestFocus();

            } else if (obj == btnExit) { // 종료버튼

                System.exit(0); // 프로그램 강제 종료

            } 

        } catch (Exception e2) {

            e2.printStackTrace();

            textArea.append(e2.getMessage() + "\n");

        }

    } // actionPerformed()


    public static void main(String[] args) {

        new GUIChatClient();

    }


}