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
복사
위와 같이 서버시간을 받아올 수 있다.