네트워크3

제이·2023년 3월 22일
0
post-thumbnail

채팅

t1이 c1이 한 말을 c2에게 보낸다면?
c1이 말한 걸 c2가 볼 수 있게 된다. 이럼 채팅이 된다.
c1이 t1한테도 보내고, c2한테도 보낸다.
c2가 t2한테도 보내고, t2가 c1한테 보낸다면? 그게 채팅.

근데 왜 안됐냐?
통신을 담당하는 스레드들이 접속해있는 클라이언트를 다 알아야하는데 알지 못하고 있다.
각 클라이언트들의 정보가 모여있는 공유자원만들면 된다.
이 공유자원을 스레드들이 공유하면 된다.
t1 -> c1을 담당하는 것이라서 c1말만 듣는다.
여기서 t1이 모든 클라이언트에게 '쓰기'를 해야한다.
쓰기하려면 뭐가 필요한가? printWrite가지고 println()으로 함.
그럼 공유자원에 있어야 하는 게 무엇인가?
우리는 소켓이 아니라 쓰기할 수 있는 게 필요하다.
각 클라이언트의 소켓에서 뽑아낸 것을 공유자원으로 넣어두면 된다.
pw1.println() ,pw2.println() 해주면 된다.
그럼 클라이언트는 변화가 없는가?
어제 클라이언트가 하는 일은? - 내가 쓴 걸 보내면 다시 나한테 돌아온다는 걸 알고 있음.
내가 입력하는 도중에도 다른 누군가가 메시지를 쓰면 내가 받을 수 있다. 나 혼자할 때는 내가 끝나야 글을 받을 수 있었는데, 공유하면 그게 안된다.

일어나야되는 일은?
1.키보드입력 -> 서버로 전송 이걸 계속해야한다.

  1. 서버로부터 메시지읽기 기능
    서버가 언제 메시지를 보낼 지 알 수 없게 된다. 타이밍 예측할 수 없다. 이게 반복돼야 한다.

클라이언트와 서버 둘 다 바뀌어야 한다.
공유자원 -> 동기화에 대해서 신경써야 한다.
언제 동기화해야할까? 추가될 때. 새 클라이언트가 들어올때?
쓰는 도중에 없어질 수도 있다. -> 추가될 때, 삭제될 때, 꺼내서 사용할 때(상대클라이언트한테 메시지를 날릴 때) 이 세가지케이스에서 적절하게 동기화시켜줘야 한다.
특정한 상대한테 메시지를 보낼 수 있게 하려면? 프린트라이터가 누구것인지 알 수 잇으면...그렇게 하려면 식별자가 있어야 한다. 귓속말같은 게 필요.


ChatServer에서 hashMap<String, PrintWriter>을 보면,
여기서 PrintWriter은 서버가 가지는 모든 쓰기의 권한이다.
서버가 쓰기 권한을 다 가지고 있어야 내가 만약
'/to 수빈 고마워'라고 했을 때, 수빈이의 pw(클라이언트의 pw)로 가서 수빈이의 채팅창에 바로 적어준다.
그리고 synchronized에서 해쉬맵에서 id를 넣기(새로운 사람 넣기), id 제거(사람 탈퇴?), 귓속말(귓속말하고 있는 도중에 사람이 사라지면 안되니까.), 모든 사람에게 알리기(알릴 사람이 도중에 사라지면 안되니까.) 이걸 다 hm이라는 객체로 통해서 묶어준다.

ChatClient

public class ChatClient {
	private Socket sock;
	private BufferedReader br;
	private PrintWriter pw;
	public ChatClient() {
		String ip = JOptionPane.showInputDialog("접속할 IP를 입력하세요");
		String id = JOptionPane.showInputDialog("접속할 ID를 입력하세요");
		if(ip.length() == 0 || id.length() == 0) {
			System.out.println(
				"IP와 ID를 제대로 입력하지 않아 프로그램을 종료합니다."	);
			System.exit(-1);
		}
		try {
			sock = new Socket(ip, 10001);
			pw = new PrintWriter(
				new OutputStreamWriter(sock.getOutputStream()));
			br = new BufferedReader(
				new InputStreamReader(sock.getInputStream()));
			BufferedReader keyboard = new BufferedReader(
				new InputStreamReader(System.in)	
                //우리가 만든 거 아니어서 안닫아도 된다.
			);
			//사용자의 id를 전송한다.
			pw.println(id);
			pw.flush();
			new Thread() {
				public void run() {
					try {
						//사용자 메시지읽기
						String line = null;
						while((line = br.readLine()) != null) {	//블로킹. 
							if(line.contentEquals("/quit")) {
                            //글 중에 "/quit" 가 있는 지 확인하는 메소드
								throw new Exception();
                                //quit쓰면  예외 발생.catch나옴.
							}
							System.out.println(line);
						}
					} catch (Exception e) {
					} finally {
						exit();
					}
				}
			}.start();
			String line = null;
			//키보드입력 읽기
			// /quit->Thread에서 멈춤.
			while((line = keyboard.readLine()) != null) {
				pw.println(line);
				pw.flush();
			}
		} catch (Exception e) {
			System.out.println(e);
		} finally {
			exit();
		}//finally
	}
	private void exit() {
		try {
			if(pw != null) {
				pw.close();
			}
		} catch (Exception e) {}
		try {
			if(br != null) {
				br.close();
			}
		} catch (Exception e) {	}
		try {
			if(sock != null) {
				sock.close();
			}
		} catch (Exception e) {}
		System.out.println("클라이언트의 접속을 종료합니다.");
		System.exit(0);
	}
	public static void main(String[] args) {
		new ChatClient();
	}//main
}//class

chatThread

public class ChatThread extends Thread{
	private Socket sock;
	private String id;
	private BufferedReader br;
	private HashMap<String, PrintWriter> hm;
	public ChatThread(Socket sock, HashMap<String, PrintWriter> 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에 담기)
            //id만 담아오는 것은 클라아언트가 id만 보내줘서인듯.
			
			broadcast(id + "님이 접속하였습니다.");	
            //각 클라이언트에 적힌다. 나한테는 안온다 나는 26번줄에 들어오니까. 
            
			System.out.println("접속한 사용자의 아이디는" + id +"입니다");
            //서버에 적힌다.
			
			synchronized (hm) {	
            //클라이언트 id..가 해쉬맵에 들어간다. 
            //맵에 id : key, pw(클라이언트가 적은 글) : value 넣음
				hm.put(id,pw);
			}
		}catch(Exception ex) {
			System.out.println("server thread constructor:" + ex);
		}
	}//생성자
	
	public void run() {
		try {
			String line =  null;
			while((line = br.readLine())!=null && !line.equals("/quit")) {	
            //quit이면 false돼서 반복문 끝남.
				if(line.indexOf("/to ") == 0){	//to가 0번 인덱스에 있다면..
					sendmsg(line);	
				}else {
					broadcast(id + ":" + line);
				}
			}
		}catch(Exception ex) {
			System.out.println("server thread run :" + ex);
		}finally {
			PrintWriter pw = null;
			synchronized (hm) {
				pw = hm.remove(id);
				pw.println("/quit");
				pw.flush();
			}
			String info = id + "님이 접속을 종료하였습니다.";
			broadcast(info);
			try {
				pw.close();
			}catch(Exception e) {}
			try {
				br.close();
			}catch(Exception e) {}
			try {
				sock.close();
			}catch(Exception e) {}
		}
	}//run
	
	
	//sendmsg 
	public void sendmsg(String msg) {
		int start = msg.indexOf(" ")+1;	
        //to 다음 공백+1 'd'이라는 거.(/to doo hi~) index=4
		
        int end = msg.indexOf(" ",start);
        // 'doo'의 앞 뒤 공백.
        
		if(end != -1) { //공백 발견했따.
			String to = msg.substring(start, end);//4번부터 6번까지
			String msg2 = msg.substring(end + 1);//8번부터 끝까지
			synchronized (hm) {
				PrintWriter pw = hm.get(to);	
                //doo를 뜻함. 사용자에게 쓸 수 있는 pw를 구해와라.
				String resultMsg = "귓말전송에 실패했습니다.";
				if(pw != null) {	
                //doo가 key 존재하지 않을 때. 
                //doo라는 사람 없는데? 하면 귓말 실패했다. 있다면 밑에코드..
					pw.println(id + "님이 다음의 귓속말을 보내셨습니다 : "
                    + msg2);	//자기id
					pw.flush(); 
					resultMsg = to + "님께서 다음의 귓속말을 보냈습니다." 
                    + msg2;	//doo님께 보냈습니다.
				}
				pw = hm.get(id);	//자기 id에게 귓말전송 실패했다고 보낸다.
				pw.println(resultMsg);
				pw.flush();
			}	
		}
	}
	
	//
	public void broadcast(String msg) {
		synchronized (hm) {
			Collection<PrintWriter> collection = hm.values();
            //맵에 들어가있는 모든 클라이언트에게 write할 수 있는 pw다. 
            //"나"도 포함. value()값만 가져오기.
			Iterator<PrintWriter> iter = collection.iterator();
			while(iter.hasNext()) {
				PrintWriter pw = iter.next();
				pw.println(msg);
				pw.flush();
			}
		}
	}
}

ChatServer

public class ChatServer {
	public static void main(String[] args) {
		try {
			ServerSocket server = new ServerSocket(10001);
			System.out.println("접속을 기다립니다.");
			HashMap<String, PrintWriter> hm 
            	= new HashMap<String, PrintWriter>();	
			//이건 id랑pw연결해주는 거. 클라이언트를 담당하는 스레드가 하나씩 있음. 
          //-> 접속해있는 클라이언트들한테 쓸 수 있는 println()하려면 pw있어야 한다.
			
			while(true) {
				Socket sock = server.accept();
				ChatThread chatthread = new ChatThread(sock, hm);
				chatthread.start();
			}//while
		}catch(Exception e) {
			System.out.println("server main : " + e);
		}
	}//main
}

코드 결과창

03.2ㄷ 수업과 03.23 수업을 합침

profile
Hello :)

0개의 댓글