네트워크4

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

SwingUtils

이 SwingUtils 클래스 안에는 method가 두개가 있다.
invokeLater 메소드와 invokeAndWait 메소드.

스윙은 스레드세이프하지않습니다 라고 선언을 해놓음.
이벤트가 발생한 순서에 따라서 event-queue.
큐는 들어오는 구멍과 나오는 구멍이 다른 데 있다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
큐모양
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
ui를 건들이는 코드들을 EDT한테 위임해라.

러너블 객체가 해야되는 일이 이벤크 큐에 넣겠다 -> 이렇게 하면 EDT가 수행함.
new MyFrame()을 결국 EDT가 한다.

EDT : event dispatch(ing) thread
-> 스윙은 거의 다 thread-safe 하지 않기 때문에.
-> 스윙과 관련된 작업들은 모두 이 쓰레드를 통해서 진행된다.

💡💡쉽게 얘기하면은
invokeLater(runnable객체) -> invokeLater 수행하는 스레드는 main이지만, runnable이 정의하고 있는 것은 EDT가 처리한다. 스윙의 안전성을 보장하겠끔 해놨다.

💡💡invokeAndWait(), invokeLater()을 사용하면, ()안의 runnable의 run()을 EDT가 수행하게 된다!!

<동기방식>

invokeAndWait(b라는 일) : main이 b라는 작업을 이벤트큐에 잡아넣는다. 이건, b라는 작업넣어놓고, 수행완료할 때까지 기다린다. 일을 wait()가 시켰으니까 b라는 작업의 완료(이건 EDT가 한다.)를 기다린다. . =>join()과 비슷하다.

  • 동기방식 : 딱딱 맞아떨어진거.-a결과 확인하고 b결과 확인하는거..
    아이디입력하고 버튼을 누른다-> 이결과가 날아올때까지 기다리는거.

<비동기방식>

invokeLater(a라는 일) : main이 Later를 수행하면서, 이벤트큐에 a를 잡아넣어. 이때, 먼저 큐에 들어가있는 애들이 있을 수 있다. 먼저 들어간 애들 상관없이 a를
일을 시킨 시점과 일이 실제로 수행되는 시점이 다르다. (이미 들어간 애들이 있어서)
a가 수행되냐 안되냐는 상관없이 later은 자기일을 계속한다.

  • 비동기방식 : a하라고 일을 시켜놓고 걍 자기일 하는 거.
    아이디입력, 비번입력 쭉 하고 있는데, 내가 다른 거 적고 있는 도중에 아이디 사용가능하다고 알려주는 느낌.(백그라운드작업)
    ui만든다고 한다면 비동기화방식으로 사용하기.
class MyFrame extends JFrame {
	public MyFrame(String mode) {
		System.out.println(mode + ":" + Thread.currentThread().getName());

		try {
			Thread.sleep(2000);
		}catch(Exception e) {}
	}
}
public class TestCase {
	public static void main(String[] args) {
		SwingUtilities.invokeLater(
			new Runnable() {
				public void run() {
					new MyFrame("invokeLater");
				}
			}
		);
		System.out.println("invokeLater");
		try {
			SwingUtilities.invokeAndWait(
				new Runnable() {
					public void run() {
						new MyFrame("invokeWait");
					}
				}
			);
		}catch(InterruptedException e ) {
		}catch(InvocationTargetException e) {}
		System.out.println("invokeWait");

		new MyFrame("normal");
	}
}

//결과
invokeLater
invokeLater:AWT-EventQueue-0
invokeWait:AWT-EventQueue-0
invokeWait
normal:main

클라이언트는 지맘대로 동작을 결정하면안된다.
chat에는 서버로부터 받은 것만 나와야한다.
똑같은 채팅을하는데 다른결과를 나타낼 수도 있다.
input은 전송만 돼야 한다.
끌때도 너 그래 꺼라 하고 꺼져야한다.
서버로부터 오더를 받아서 움직여야한다.
클라이언트만 바뀌고 나머지는 똑같음.

먜) 클라이언트에 Thread가 있는 이유.
계속 날아오는 메시지를 처리하기 위해 글을 읽는 부분을 스레드로 뺀다.
스레드를 활용하지 않으면, 글을 읽고 있을 때 상대방이 주는 메시지를
동시간으로 받지 못한다.

GUIChatClient

public class GUIChatClient extends JFrame implements ActionListener {
	
	public static final int NORMAL = 0;
	public static final int EXCEPTIONAL = -1;
	
	private JTextField input;
	private JTextArea display;
	private BufferedReader br;
	private PrintWriter pw;
	private Socket sock;
	
	public GUIChatClient() {
		super("채팅 클라이언트");
		init();
		connect();	
		setDisplay();
		addListeners();
		showFrame();
	}
	private void init() {
		input =  new JTextField();
		input.setBorder(new TitledBorder("Input"));
		display = new JTextArea();
		display.setEditable(false);
	}
	
	private void setDisplay() {
		JPanel pnlCenter = new JPanel(new BorderLayout());
		pnlCenter.add(new JScrollPane(display));
		pnlCenter.setBorder(new TitledBorder("Chat"));
		add(pnlCenter, BorderLayout.CENTER);
		add(input, BorderLayout.SOUTH);
	}
	
	private void addListeners() {	//루프를 돌지 않아도 되기 때문에, 리스너 써서 할 수 있다. 
		input.addActionListener(this);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e ) {
				pw.println("/quit");
				pw.flush();
			}
		});
	}
	
	private void showFrame() {
		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		setSize(500,500);
		setResizable(false);
		setVisible(true);
		input.requestFocus();
	}
	private void connect() {	//연결.
		String ip = null;
		do {
			ip = JOptionPane.showInputDialog(this, "아이피를 입력하시오");
		}while (ip == null || ip.equals(""));
		String id = null;
		do {
			id = JOptionPane.showInputDialog(this, "닉네임을 입력하시오.");
			
		}while(id == null || id.equals(""));
		
		try {
			sock = new Socket(ip, 10001);
			pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
			br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
			pw.println(id.trim());//id읽으려고 준비중.
			pw.flush();
			WinInputThread wit = new WinInputThread();
			wit.start();
		}catch(Exception e) {
			System.out.println("서버와 접속시 오류가 발생하였습니다.");
			System.out.println(e);
			System.exit(EXCEPTIONAL);
		}
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == input) {
			String msg = input.getText();
			pw.println(msg);
			pw.flush();
			input.selectAll();
			input.requestFocus();
		}
	}
	class WinInputThread extends Thread{
		public void run() {
			try {
				String line = null;
				while((line = br.readLine()) != null) {	//서버로부터 읽어온다.
					if(line.equals("/quit")) {
						throw new Exception();	//예외 던져서 끝내기.
					}
					display.append(line + "\n");	//붙여넣기
					//커서 위치 조절(스크롤문제)
					display.setCaretPosition(display.getDocument().getLength());	//커서위치잡는 거=>caretPosition
				}
			}catch(Exception e ) {
				System.out.println("client thread :" + e);
				JOptionPane.showMessageDialog(GUIChatClient.this, "프로그램을 종료합니다");
			}finally {
				try {
					br.close();
				}catch(Exception e) {}
				try {
					pw.close();
				}catch(Exception e) {}
				try {
					sock.close();
				}catch(Exception e) {}
				System.exit(NORMAL);
			}
		}
	}
	public static void main(String[] args) {
		SwingUtilities.invokeLater(
			new Runnable(){
				public void run() {
					new GUIChatClient();
				}
			}
		);
	}
}

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
}

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();	//여기서 멈춘다.
			
			broadcast(id + "님이 접속하였습니다.");	//나한테는 안온다 나는 26번줄에 들어오니까.
			System.out.println("접속한 사용자의 아이디는" + id +"입니다");
			
			synchronized (hm) {	//클라이언트 id..가 해쉬맵에 들어간다.
				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다. "나"도 포함.
			Iterator<PrintWriter> iter = collection.iterator();
			while(iter.hasNext()) {
				PrintWriter pw = iter.next();
				pw.println(msg);
				pw.flush();
			}
		}
	}
}

3월 23일, 3월 24일

profile
Hello :)

0개의 댓글