본문 바로가기
CS(Computer Science)/20) 자바

자바를 자바 25

by tonyhan18 2020. 12. 30.
728x90

Chat Program with Multiple Users

Multiple User을 지원하는 채팅 프로그램을 제작, 이번에는 UDP를 활용한 서버를 구현

  • MultiChatServer

    • Manages Users
    • client sends a message, server redirects the message to all other clients
  • MultiChatClient

    • Connects to the server
    • Communicates with other clients

public class MultiChatServer {
    HashMap<String,DataOutputStream> clients;

    // 해쉬맵 생성, 여러개의 스레드가 동시에 접근 가능
    //synchronizedMap(원래는 asynchronized)
    MultiChatServer() {
        clients = new HashMap<>();
        Collections.synchronizedMap(clients);
    }

    public void start() {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
       //7777 port 열어놓기
            serverSocket = new ServerSocket(7777);
            System.out.println("server has started.");
            while(true) {
                socket = serverSocket.accept();
                System.out.println("a new connection from [" + socket.getInetAddress() + ":" + socket.getPort() + "]");

               //새로운 Client가 올때마다 스레드를 새로 생성한다.
                ServerReceiver thread = new ServerReceiver(socket);
                thread.start();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

여기에서 생각해 봐야 하는 것은 HashMap을 사용했을때 문제점이 발생할 수 있을지?

그런데 HashMap을 사용했을때 발생할 수 있는 문제는 unique key해야 한다는 점이다. 그런데 name을 사용했기 때문에 동일한 이름을 사용했을 경우 문제가 생길 수 있다.

    //msg를 성분으로 받아와서 메세지를 보낸다.
    void sendToAll(String msg) {
        //key들의 집합을 뽑아서 iterator 제작
        Iterator<String> it = clients.keySet().iterator();
        while(it.hasNext()) {
            try {
            //key들을 하나씩 받아와서 연결 및 메세지 전송
                DataOutputStream out = (DataOutputStream)clients.get(it.next());
                out.writeUTF(msg);
            } catch(IOException e) { }
        }
    }
    public static void main(String args[]) {
        new MultiChatServer().start();
    }

sendToAll을 그냥 사용하게 되면 메세지를 서버로 보낸 클래이언트에게도 메세지가 전송되게 된다. 그렇기에 이러한 문제점을 해결할 수 있는 방법을 찾아내야 한다.

    //클래스 안에 클래스가 정의되어 있음
    class ServerReceiver extends Thread {
        Socket socket;
        DataInputStream in;
        DataOutputStream out;

        //Client 쪽과 이미 만들어진 Socket을 가지고 ServerReceiver 제작
        ServerReceiver(Socket socket) {
            this.socket = socket;

            //InputStream과 OutputStream을 받아와서 사용
            try {
                in = new DataInputStream(socket.getInputStream());
                out = new DataOutputStream(socket.getOutputStream());
            } catch(IOException e) {}
        }

        public void run() {
            String name = "";
            try {
                //DataStream으로 부터 데이터 오기를 기다림
                name = in.readUTF();
                sendToAll("#"+name+" has joined.");

                //클라이언트에사 받아온 이름을 outPutStream으로 데이터 저장
                clients.put(name, out);
                System.out.println("Current number of users: " + clients.size());

                // 읽은 데이터를 모든 유저에게 전달
                while (in != null) {
                    sendToAll(in.readUTF());
                }
            } catch(IOException e) {
                // ignore
            } finally {
                sendToAll("#"+name+" has left.");

                // 클라이언트와 접속이 끝난경우 클라이언트 삭제 및 정보 출력
                clients.remove(name);
                System.out.println("["+socket.getInetAddress()+":"+socket.getPort()+"]"+" has disconnected.");
                System.out.println("Current number of users: " + clients.size());
            }
        }
    }
}

public class MultiChatClient {
    //ClientSender 안에는 소켓으로부터 OutputStream을 받아옴
    static class ClientSender extends Thread {
        Socket socket;
        DataOutputStream out;
        String name;
        ClientSender(Socket socket, String name) {
            this.socket = socket;
            try {
                out = new DataOutputStream(socket.getOutputStream());
                this.name = name;
            } catch(Exception e) {}
        }

        @SuppressWarnings("all")
        public void run() {
            Scanner scanner = new Scanner(System.in);
            try {
                //서버쪽으로 먼저 정보를 보냄
                if (out != null) {
                    out.writeUTF(name);
                }
                while (out != null) {
                    out.writeUTF("["+name+"]"+scanner.nextLine());
                }
            } catch(IOException e) {}
        }
    }
    static class ClientReceiver extends Thread {
        Socket socket;
        DataInputStream in;

        ClientReceiver(Socket socket) {
            this.socket = socket;
            try {
                in = new DataInputStream(socket.getInputStream());
            } catch(IOException e) {}
        }

        public void run() {
            while (in != null) {
                try {
                    System.out.println(in.readUTF());
                } catch(IOException e) {}
            }
        }
    }

    //main에 UserName을 성분으로 할당
    public static void main(String args[]) {
        if(args.length != 1) {
            System.out.println("usage: java MultichatClient username");
            System.exit(0);
        }

        // 서버쪽으로 접속
        try {
            String serverIp = "127.0.0.1";
            Socket socket = new Socket(serverIp, 7777);
            System.out.println("connected to server.");
            Thread sender = new Thread(new ClientSender(socket, args[0]));
            Thread receiver = new Thread(new ClientReceiver(socket));
            sender.start();
            receiver.start();
        } catch(ConnectException ce) {
            ce.printStackTrace();
        } catch(Exception e) {}
    }
}


서버를 우선 작동시킨다음에 Client를 실행시키어 보자


글자를 입력하니 유저가 왔다는 메세지가 들어왔다.


Cmder을 하나더 켜서 접속해 보니 서버에 두번째 접속자가 들어온 것을 확인할 수 있었다.

추가실습

Client에서 자기자신을 제외하고 전체에 보내는 방법에 대해서 한번 생각해 보자.

UDP Socket Programming

UDP는 connection을 만드는 과정이 미 존재

UDP만의 class가 따로 존재하여 DatagramSocket을 사용해서 socket을 만들고 메세지를 전달하게 된다. 이때 사용하는 함수가 send이다.

recevie함수를 호출함으로써 socket의 메세지가 들어올때까지 blocked 되어 있는다.

public class UdpClient {
    @SuppressWarnings("all")
    public void start() throws IOException, UnknownHostException {
    //Socket을 먼저 제작 이때 보면 따로 연결한다는 개념이 없다.
        DatagramSocket datagramSocket = new DatagramSocket();
        InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
        byte[] msg = new byte[100];

        //패킷을 제작해서 메세지를 보내고 받는다.(1byte 짜리)
        DatagramPacket outPacket = new DatagramPacket(msg, 1, serverAddress, 7777);
        DatagramPacket inPacket = new DatagramPacket(msg, msg.length);
        datagramSocket.send(outPacket);
        datagramSocket.receive(inPacket);
        System.out.println("current server time: " + new String(inPacket.getData()));
    }

    public static void main(String args[]) {
        try {
            new UdpClient().start();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

Server

public class UdpServer {
    @SuppressWarnings("all")
    public void start() throws IOException {
        DatagramSocket socket = new DatagramSocket(7777);
        DatagramPacket inPacket, outPacket;
        byte[] inMsg = new byte[10];
        byte[] outMsg;

        //while 문으로 데이터를 계속 받아온다.
        while(true) {
            inPacket = new DatagramPacket(inMsg, inMsg.length);
            socket.receive(inPacket);
            InetAddress address = inPacket.getAddress();
            //데이터를 보내줄때 필요하기 때문에 포트번호도 받아옴
            int port = inPacket.getPort();

            //String으로 데이터를 받아와서 패킷을 만들어서 데이터 전송
            SimpleDateFormat sdf = new SimpleDateFormat("[hh:mm:ss]");
            String time = sdf.format(new Date());
            outMsg = time.getBytes();
            outPacket = new DatagramPacket(outMsg, outMsg.length, address, port);
            socket.send(outPacket);
        }
    }

    public static void main(String args[]) {
        try {
            new UdpServer().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


위와 같이 서버시간을 받아올 수 있다.

728x90

'CS(Computer Science) > 20) 자바' 카테고리의 다른 글

자바를 자바 fp  (0) 2020.12.30
자바를 자바 26 : Multithreaded Programming with Java (2)  (0) 2020.12.30
자바를 자바 24 (Networking with Java(3))  (0) 2020.12.30
자바를 자바 23  (0) 2020.12.30
자바를 자바 22  (0) 2020.12.03