import java.net.*;
import java.io.*;
import java.util.*;
// 클라이언트-서버 프로그램
public class ChatServer {
public static void main(String[] args) {
try{
ServerSocket server = new ServerSocket(10001); //서버 소켓 인스턴스 생성! 소켓넘버를 파라미터로 받고 클라이언트의 접속을 확인해준다.
System.out.println("접속을 기다립니다.");
HashMap hm = new HashMap(); //해시맵 생성!
while(true){
Socket sock = server.accept(); // 클라이언트의 접속을 확인하고 동시에 소켓인스턴스를 생성한다.
ChatThread chatthread = new ChatThread(sock, hm); //서버 프로그램의 스레드인 Chatthread를 생성한다.
chatthread.start();
}
}catch(Exception e){
System.out.println(e);
}
}
}
// ChatThread는 서버 프로그램에서 서버의 일을 대신 해준다. 서버는 그저 while문을 통해 접속하는 클라이언트를 받아주는 역할만을 한다.
// 대신 동시에 자신의 일을 대신해줄 수 있는 스레드를 생성하여, 그 스레드에게 서버의 역할을 위임한다.
class ChatThread extends Thread{
private Socket sock;
private String id;
private BufferedReader br;
private HashMap hm;
private boolean initFlag = false;
// 생성자의 파라미터로 소켓 넘버와 해시맵 참조값을 전달한다.
public ChatThread(Socket sock, HashMap hm){
this.sock = sock;
this.hm = hm;
try{
PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream())); //클라이언트에게 데이터를 전달 해주는 출력 스트림 생성!
br = new BufferedReader(new InputStreamReader(sock.getInputStream())); //클라이언트로 부터 데이터를 받을 입력스트림 생성!
id = br.readLine(); // 맨 첫번째 배열이었던 id를 읽어와 저장한다.
broadcast(id + "님이 접속하였습니다."); // 같이 채팅하는 유저들에게 알린다.
System.out.println("접속한 사용자의 아이디는 " + id + "입니다.");
// 해시맵에 id와 서버의 출력스트림 참조값을 저장한다.
// 만약 스레드가 동시에 해시맵을 참조한다면, 충돌가능성이 있기 때문에 동기화를 시켜준다.
synchronized(hm){
hm.put(this.id, pw);
}
initFlag = true;
}catch(Exception ex){
System.out.println(ex);
}
} // 생성자
public void run(){
try{
String line = null;
while((line = br.readLine()) != null){
if(line.equals("/quit")) //사용자가 종료를 선언하는 문자열을 입력했을때 while문을 빠져나온다.
break;
if(line.indexOf("/to ") == 0){ //해당 문자열이 첫번째 글자의 위치 번호를 반환한다. 만약 "/to " 가 첫번째에 있다면 "/"의 위치 번호 0을 반환한다.
sendmsg(line);
}else
broadcast(id + " : " + line); // 위의 어느 조건도 만족하지 않는 다면 채팅방에 문자를 출력한다.
}
}catch(Exception ex){
System.out.println(ex);
}finally{
synchronized(hm){
hm.remove(id); //해시맵에 등록된 아이디 삭제
}
broadcast(id + " 님이 접속 종료하였습니다.");
try{
if(sock != null)
sock.close();
}catch(Exception ex){}
}
}
/* 귓속말을 보내는 메소드 */
public void sendmsg(String msg){
int start = msg.indexOf(" ") +1; // " "이 위치하고 있는 번호에 1을 더해 " " 다음이 시작지점임을 저장한다. 여기서는 "/to "<이부분>
int end = msg.indexOf(" ", start); // start이후 그 다음 " "가 나오는 곳의 위치번호를 저장하여 끝을 알린다.
if(end != -1){
String to = msg.substring(start, end); //처음번호와 과 끝번호 사이에 저장되어있는 문자를 출력한다. 여기서는 유저의 id를 의미한다.
String msg2 = msg.substring(end+1); // 끝번호 다음 부터 메세지가 입력 되므로 그다음부터 끌까지의 문자열을 ms2에 저장한다.
Object obj = hm.get(to); //to에 해당하는 데이터 즉 출력스트림의 참조값을 오브젝트 인스턴스에 저장. 모든 클래스가 Object 클래스를 상속하기 때문에 가능.
if(obj != null){
PrintWriter pw = (PrintWriter)obj; // 해당 참조값을 pw에 저장
pw.println(id + " 님이 다음의 귓속말을 보내셨습니다. :" + msg2); //해당 출력스트림을 가지고 있는 사람에게 귓속말 전달
pw.flush();
} // if
}
} // sendmsg
public void broadcast(String msg){
synchronized(hm){
Collection collection = hm.values(); //해시맵에 저장되어 있는 모든 출력스트림의 참조값을 collection에 저장한다.
Iterator iter = collection.iterator(); //iterator를 생성하여 각각의 데이터를 참조하여 잡근하도록 한다.
// 결국 해시맵에 저장되어 있는 모든 사람에게 메세지를 전잘하는 것이다.
// 해당 테이터 즉, 출력스트림으로 파라미터로 전달받은 메세지를 전송한다.
while(iter.hasNext()){ //데이터가 있다면.....
PrintWriter pw = (PrintWriter)iter.next();
pw.println(msg);
pw.flush();
}
}
} // broadcast
}