자바를 자바 25 (Chat Program)

TonyHan·2020년 12월 11일
0

20) 자바

목록 보기
25/27

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();
		}
	}
}


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

profile
신촌거지출신개발자(시리즈 부분에 목차가 나옵니다.)

0개의 댓글