본문 바로가기
Java/Java

JAVA 채팅 프로그램

by code2772 2022. 10. 20.

[ 목차 ]

    728x90
    반응형

    ✔ JAVA 채팅 프로그램

    쓰레드 병렬처리
    ServerSocket의 accept() 실행하면 해당 작업이 완료되기전까지 블로킹(blocking)이 일어남
    쓰레드를 사용하면 블로킹이 일어나는 현상을 해결할 수 있음
    
    DataInputStream
    - 기본 데이터타입 단위로 데이터를 읽을 수 있음
    - byte 단위로 데이터를 읽는 것이 아님
    - readUTF() : UTF-8(모든 언어가 사용가능한) 형식으로 코딩된 문자열을 읽을 수 있음
    
    DataOutputStream
    - 기본 데이터타입 단위로 데이터를 쓸 수 있음
    - byte 단위로 데이터를 쓴는 것이 아님
    - writeUTF() : UTF-8(모든 언어가 사용가능한) 형식으로 코딩된 문자열을 출력할 수 있음
    

    서버

     

    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.Iterator;
    
    public class ChatServer {
    
        private HashMap<String, OutputStream> clients;
        // <유저네임, 데이터>
        // 키는 아이디를 저장하고 내보네는 스트림을 아웃풋으로 하는 해시맵을 클라이언트라고한다.
        // 키와 벨류로 있는것이 헤시멥
        // 필드만 만들어 놓움
    
        public static void main(String[] args) {
            new ChatServer().start();
            // 메인이 실행되자 마자 생성자 호출하고 쳇서버, 스타트 실행
        }
    
        public ChatServer() {
            clients = new HashMap<>();
        }
    
        public void start() {
            ServerSocket serverSocket = null;
            //서버 프로그램을 개발할 때 쓰이는 클래스
            Socket socket = null;
            //client에서 서버로 접속하거나 Server에서 accept 하는데 필요한 클래스
    
            try {
                serverSocket = new ServerSocket(3579);
                //클라이언트가 아이피 주소와 포트번호까지 써줘야 이 문장으로 들어올 수 있다.
                System.out.println("서버가 시작되었습니다");
    
                while (true) {
                    socket = serverSocket.accept();
                    // 서버가 사용자가 들어올 때까지 대기하고 있는 장소로
                    // 들어오면 일반 소켓을 만들어지게 된다. 사용자의 소켓, 포트 등 정보를 알 수 있다.
                    System.out.println("IP : " + socket.getInetAddress() +
                            ", PORT : " + socket.getPort() + "에서 접속했습니다.");
                    ServerReceiver thread = new ServerReceiver(socket);
                    thread.start();
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 모든 접속한 사람들에게 동일한 내용을 보네기 위한 메소드를 만들어보자
    
        public void sendToAll(String msg) {
            // 클라이언트가 해시맵, 키셋은 유저네임
            // 키셋()은 메소드를 사용하여 set객체를 반환받은 후 이터레이터 사용
            Iterator<String> it = clients.keySet().iterator();//탐색 / 이터레이터 해시멥을 탐색하는
            // 전체 출력을 하는 경우 반복문을 사용하지 않고 Iterator을 사용하여 출력한다.
            while (it.hasNext()) { //자료가 있다면(접속된 사용자 만큼 반복)
                try {
                    DataOutputStream out = (DataOutputStream) clients.get(it.next());
                    // it.next() 는 유저네임
                    //clients.get(it.next()는 아웃풋 스트림 -> 데이터 아웃풋 스트림으로
                    // 들어가게 하기 위해 (DataOutputStream)사용하여 변환하게 한다.
                    out.writeUTF(msg); // 받은걸 출력
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        class ServerReceiver extends Thread {
            private Socket socket; // 참조변수
            private DataInputStream in;
            private DataOutputStream out;
            public ServerReceiver(Socket socket) {// 소켓을 메개변수로 넣고 서로 연관시키기
                this.socket = socket;
                try {
                    in = new DataInputStream(socket.getInputStream());
                    // 소켓에 데한 인풋 스트림을 만들어 입력가능하게
                    out = new DataOutputStream(socket.getOutputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override // 스레드를 만들었기 때문에 만들어야 한다.
            public void run() {
                String userName = "";
                try {
                    userName = in.readUTF(); //이름 읽어오기
                    // 상대가 입력하지 않으면 계속 대기한다(스캐너 역할)
                    sendToAll("😁" + userName + "님이 들어오셨습니다"); // 모든 클라이언트에 전송
                    clients.put(userName, out);
                    // 서버가 있고 클라이언트가 있으면 접속을 하게되면 유저네임을 집어넣고 접속한 사람에게 아웃풋 스트림을 전송하면
                    // 메시지를 보넬 통로를 만든것이다. 이는 해시맵을 만든것으로 통로가 서로 달라 각 쌍으로 키와 벨류로 저장한 것이다.
                    //아이디 별로 아웃풋 스트림을 해시멥에 담아준것이다.
                    System.out.println("현재 접속자 : " + clients.size()); //수는 해시맵의 사이즈로 확인 가능하다.
    
                    while (in != null) {
                        String msg = in.readUTF();
                        if (msg.contains("quit")) break; // 종료
                        sendToAll(msg);//아니면 모든 사용자에게 메시지를 전송한다.
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    sendToAll("😢" + userName + " 님이 나가셨습니다");
                    clients.remove(userName);
                    System.out.println("IP : " + socket.getInetAddress() +
                            ", PORT : " + socket.getPort() + "에서 접속종료.");
                    System.out.println("현재 접속자 : " + clients.size());
                }
            }
        }
    }
    

    클라이언트

    import java.io.*;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.util.Scanner;
    
    public class ChatClient {
        public static void main(String[] args) {
            System.out.println("대화명을 입력하세요");
            Scanner sc = new Scanner(System.in);
            String userName = sc.next();
            try {
                Socket socket = new Socket("192.168.6.13", 3579);
                ////client에서 서버로 접속하거나 Server에서 accept 하는데 필요한 클래스
                System.out.println("서버와 연결되었습니다");
    
                Thread sender = new Thread(new ClientSender(socket, userName));
                Thread receiver = new Thread(new ClientReceiver(socket));
    
                sender.start();
                receiver.start();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        static class ClientSender extends Thread { // 단일로 돌아감
            private Socket socket;
            private DataOutputStream out;
            private String userName;
    
            public ClientSender(Socket socket, String userName) {
                this.socket = socket;
                try {
                    out = new DataOutputStream(socket.getOutputStream());
                    this.userName = userName;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void run() {
                BufferedReader br = null;
                try {
                    br = new BufferedReader(new InputStreamReader(System.in));
                    // 터미널에서 입력한 데이터를 가저오는 것이다. 인풋스트림 리더를 통해 버퍼에 저장하고 버퍼에서 저장된 라이트
                    if (out != null) out.writeUTF(userName);
    
                    while (out != null) {
                        out.writeUTF("[" + userName + "]" + br.readLine());
                    }// 메세지가 돌며 버퍼에서 있는것을 읽어 서버쪽으로 전송하는 구문
                    // [apple] 안녕하세요를 라인별로 읽어 서버쪽으로 계속 전달
    
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (br != null) try {
                        br.close();
                    } catch (IOException e) {
                    }
                    // 입출력 예외처리
                    if (out != null) try {
                        out.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
    
        static class ClientReceiver extends Thread {// 서버의 글을 받악가지고 처리하는 부분
            private Socket socket;
            private DataInputStream in;
    
            public ClientReceiver(Socket socket) {
                this.socket = socket;
                try {
                    in = new DataInputStream(socket.getInputStream());
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void run() {
                while (in != null) {
                    try {//읽어온것을 그저 찍어낸다
                        System.out.println(in.readUTF());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    반응형

    'Java > Java' 카테고리의 다른 글

    Java 음성 소켓 통신(서버, 클라이언트) 기초  (0) 2023.04.21
    JAVA (TCP/IP, 서버 클라이언트)  (0) 2022.10.20
    JAVA 네트워크  (0) 2022.10.20
    자바 제네릭(Generic)  (0) 2022.10.12
    자바 스레드  (0) 2022.10.07