Java/Java

JAVA 채팅 프로그램

code2772 2022. 10. 20. 16:36
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