Search
Duplicate

자바를 자바 25 (Chat Program)

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Java
Scrap
태그
9 more properties

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(); } }
Plain Text
복사
여기에서 생각해 봐야 하는 것은 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(); }
Plain Text
복사
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()); } } } }
Plain Text
복사
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) {} } }
Plain Text
복사
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) {} } }
Plain Text
복사
서버를 우선 작동시킨다음에 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(); } } }
Plain Text
복사
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(); } } }
Plain Text
복사
위와 같이 서버시간을 받아올 수 있다.