Chat Program with Multiple Users

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

https://images.velog.io/images/tonyhan18/post/7dfee12b-3f66-4b68-90a3-47e31d529633/image.png

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) {}
	}
}

https://images.velog.io/images/tonyhan18/post/145ef9f2-b5eb-44b9-b7ff-489ebd952b05/image.png

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

https://images.velog.io/images/tonyhan18/post/58aef404-28ac-4523-839a-4306f5a34966/image.png

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

https://images.velog.io/images/tonyhan18/post/66473b59-7f6b-46d4-84f1-8b429c8d9ce2/image.png

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