(19)-23.10.19[JAVA]

DANA·2023년 10월 19일

KDT-구디아카데미

목록 보기
19/56

채팅창 구현

클래스 설계하기

  • ChatServerThread의 run메소드에서는 듣고 (즉시) 클라이언트 측(다수에게-List<"ChatServerThread"> globalList)에 말하기 처리(broadCasting()한다
  • ChatClientThread의 run메소드에서는 듣기(ois.readObject())만 한다
  • ChatClient의 actionPerformed에서는 말하기(oos.writeObject())를 한다
<예제1>
package achat.step1;

import java.util.List;
import java.util.Vector;

class Customer extends Thread{

}
public class List2 {
	List<Customer> list = new Vector<>(); 
	void methodA(){// static이 있으면 this 못 쓰니까 일단 메소드를 빼자
		this.add(this);
	}
	public static void main(String[] args) {
		List2 list2 = new List2();
		list2.methodA();
	}

}

Q. this.add(this);의 에러를 해결하려면 어떻게 해야할까?
A1. List list = new Vector<>();
-> List list = new Vector<>();

<예제>
package achat.step1;

import java.util.ArrayList;
import java.util.List;

class Kiwi{
	String name = null; // 골드키위, 초록키위
}
class Tomato {
	
}
public class List3 {
	List<String> bread = new ArrayList<>();
	List<Kiwi> kiwis = new ArrayList<>();
	List<Tomato> tomatos = new ArrayList<>();
	void methodA() {
		bread.add("소보로");
		Kiwi kiwi = new Kiwi();
		kiwi.name = "골드키위";
		kiwis.add(kiwi); // bread.add(kiwi); 이러면 안 된다
		Kiwi kiwi2 = new Kiwi();
		kiwi2.name = "초록키위";
		kiwis.add(kiwi2);
		Tomato tomato = new Tomato();
		tomatos.add(tomato);
	}
	// 메인메소드는 개발자에게 제어권이 없다
	public static void main(String[] args) {
		List3 list3 = new List3();
		list3.methodA(); // 여기를 막으면 0 출력
		System.out.println(list3.bread.size()); //1
		System.out.println(list3.kiwis.size()); //1 -> 2(골드키위, 초록키위)
		System.out.println(list3.tomatos.size()); //1
	}
}

flow chart

A코스(서버)

B코스(클라이언트)

ChatServer cs = new ChatServer();
cs.initDisplay(); // 화면열림
cs.start();
cs.run(); // new Vector<>();
server = new ServerSocket(3002);
waiting 기다림..
기다림이 끝났다
socket = server.accept(); // while문에 처리함 - 무한루프(break)
// while문 안에서 인스턴스화 할 것
ChatServerThread cst = new ChatServerThread(this); // 장애 원인이 된다 - 안정화
// insert here -> 생성자 호출 - 현재 메모리에 로딩중인 원본 - charServer가 넘어감
this.cs = cs;
this.client = socket;
oos, ois 생성한다(인스턴스화) - 그런데 소켓에 의존한다 - 소켓 없이는 인스턴스화 불가함
cst.start()


ChatClient
new Socket("172.16.2.3",3002) -> server가 클라이언트 존재인지함

모든 메세지는 서버를 경유하여 받는다 - web개발 -> 외부(소셜로그인, 검색 API, 오픈소스) 서비스를 이용하기

actionPerformed(말하기-메세지입력-엔터친다) - oos.writeObject();

ChatServer - 내 안에는 oos, ois가 없다
ChatServerThread - 생성자(while문:ChatServerThread(this)로 cs를 받음
인스턴스화를 하여 받으면 복제본이 생성됨 - 원본이 아니다
-client 소켓생성(ChatServer생성됨) - oos, ois 생성함 - 듣고 말할 수 있는 곳이다

run() 재정의 -> protocol(100|200|500) 찾아내고 - StringTokenizer

while() {
	switch(int-100|200|500){
		case 100:
		case 200:
		case 500:{
 			cs.globalList.remove(this);
 		} 
	}
}
<ChatServer>
package achat.step1;

import java.awt.Color;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

//왜 스레드를 굳이 상속받는 건가요? - 화면처리하는 메인메소드와 통신채널 설정하는 코드 분리
//혜택 - 화면처리와 네트워크 처리 분리한다 - 안전한 서비스 제공하기 위해서
public class ChatServer extends Thread {
	//List의 구현체 클래스는 ArrayList(스레드불안전-조치가 없다, 배려가 없다.추가로직이 포함안됨), Vector(동시접속자를 배려하여 0.1초라도 먼저 들어오면 관리를 해준다.읽기 쓰기 속도가 느리다.)
	//챗서버에 접속해온 사용자에 대한 정보를 담는 벡터 선언임
	//다수의 클라이언트 요청을 처리하는 서버를 개발할때 사용함
	List<ChatServerThread> globalList = null;
	ServerSocket server = null;//run 메소드 사용함 - 39번라인에서 인스턴스화함- 가게문을 열었음
	//서버는 들은내용을 말하는 것이다.
	//실제로 듣고 말하기는 ChatServerThread에서 처리한다.(내가 그렇게 설계했다.)
	//run메소드에서 서버소켓에 접속해온 클라이언트측의 소켓정보를 받게 되니까 - 메소드 내부에서 사용하려면 전변으로 하자
	Socket socket = null;
	JFrame jf = new JFrame();
	JTextArea jta_log = new JTextArea(10,60);
	JScrollPane jsp_log = new JScrollPane(jta_log
			 , JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
			 , JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
	public ChatServer() {}

	
	@Override
	public void run() {//통신에 대한 처리 -  지연, 끊김, 노이즈, 대기
		//서버에 접속해온 클라이언트 스레드 정보를 관리할 벡터 생성하기 
		globalList = new Vector<>();//ChatServerThread담기 -동시에 3명이 들어온다면
		boolean isStop = false;
		try {
			//만일 3002번 포트를 오라클 서버에서 점유하고 있다면 예외가 발생할 것이다.
			server = new ServerSocket(3002);
			jta_log.append("Server Ready.........\n");//대기 중 - 손이 오기를 기다린다. - 다음코드가 실행기회를 못갖는다. - 흐름을 방해함 - 장애처리
			while(!isStop) {
				//서버소켓에 접속해온 사용자의 소켓정보를 담는다. - 멍때림
				//Socket s = new Socket("172.16.2.3", 3002);-  ChatClient - 입장하기
			    //socket은 ChatServer에서 선언한 전변인데 어떻게 클라이언트 소켓을 주소번지를 쥐고 있는걸까?
				socket = server.accept();
				jta_log.append("client info:"+socket+"\n");//여기 소켓은 클라이언트소켓을 쥐고 있다.			
				//아래에서 this는 ChatServer가리킨다
				System.out.println(this);//왜 파라미터로 넘기지? - socket필요하니까-> oos, ois
				//다형성
				Thread tst = new ChatServerThread(this);//this- ChatServer -why?: 협업 - 서버에 접속한 소켓정보를 ChatServerThread에서 사용해야 하니까 this로 넘긴다.
				//생성자 호출이 끝나고 나면 즉시 start()호출된다.-> run()호출한다.
				//100|kiwi
				tst.start();//ChatServerThread의 run호출된다.
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void initDisplay() {
		jta_log.setLineWrap(true);
		jf.setBackground(Color.orange);
		jf.add("Center", jsp_log);
		jf.setTitle("서버측 로그 출력화면 제공...");
		jf.setSize(800, 600);
		jf.setVisible(true);
	}
	//메인메소드도 스레드라 했잖아요?
	//entry point - 시작점 - exe 생성가능함 - 경합벌어진다. - 끼어든다
	public static void main(String[] args) {
		ChatServer cs = new ChatServer();
		System.out.println(cs);
		//두 메소드가 호출되었을 때 지연이 발생되는 메소드는 start()이다.
		//같은 메소드라도 처리되는 시간이 다르다- 스케쥴링이 필요함
		//this.sleep(10000), this.wait(); this.notify();
		//대기상태에 있는 손님이 있다.
		cs.start();//여기서 대기탄다 - this.wait(); -> this.notify();잠자는 스레드를 깨운다
		cs.initDisplay();
	}
}
<ChatServerThread>
  package achat.step1;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.List;
import java.util.StringTokenizer;

public class ChatServerThread extends Thread {
	ChatServer cs = null;
	Socket client = null;
	ObjectOutputStream oos = null;//말할때 사용
	ObjectInputStream ois = null;//들을 때 사용
	//
	String chatName = null;//현재 서버에 입장한 클라이언트 스레드의 닉네임 저장
	//생성자의 파라미터
	//public ChatServerThread(List<ChatServerThread> globalList, JTextArea jta_log) {
	public ChatServerThread(ChatServer cs) {
		cs.jta_log.append("ChatServerThread호출 "+"\n");
		//생성자 안에서 globalList에 현재 입장한 키위를 추가한다.
		this.cs = cs;
		this.client = cs.socket;
		cs.jta_log.append("client "+client+"\n");		
		try {
			oos = new ObjectOutputStream(client.getOutputStream());
			ois = new ObjectInputStream(client.getInputStream());
			//100|kiwi -입장하기 처리를 생성자에서 벌써 하였다.
			//생성자안이면 입장했다는걸 어떻게 증명한다는 건가요?
			String msg = (String)ois.readObject();//누가 보낸걸 듣나요? ChatClient
			cs.jta_log.append(msg+"\n");//로그를 남긴다(출력) - 추적하다
			StringTokenizer  st = new StringTokenizer(msg,"|");
			st.nextToken();//100
			chatName = st.nextToken();//kiwi
			cs.jta_log.append(chatName+"님이 입장하였습니다.\n");
			
			//내가 입장하기 전에 이미 입장해 있는 친구들 에게도 메시지를 전달해야 한다.
			//kiwi가 입장했을땐 cs.globalList.size()가 0이라서 안탄다
			for(ChatServerThread  cst: cs.globalList) {//cs.globalList.size()=0
				System.out.println(cst.chatName);
				System.out.println(this.chatName);
				this.send(100+"|"+cst.chatName);//나 입장했어요
			}
			//현재 서버에 입장한 클라이언트 스레드 추가하기
			cs.globalList.add(this);//3명이 채워짐 - ChatServerThread -> globalList(ChatServer) -> this(kiwi or tomato or  apple)
			this.broadCasting(msg);
			// apple이 입장했을때 이미 kiwi, tomato입장해 있다
			//cs.globalList.add(this);//this는 kiwi, tomato, apple, 아님 이걸 어떻게 서버가 구분할 수 있나요?
			//cs.globalList.add(this);//this는 ChatServerThread이다. - List<ChatServerThread> globalList
			//cs.globalList.size();//3명
			//cs.jta_log.append("");
			//System.out.println(this.cs.globalList.size());//0			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		

	}	
	
	//run메소드도 콜백메소드 입니다. - 개발자가 직접 호출하지 않아요
	//시스템에서 대신 호출해 줌
	@Override //annotation
	public void run() {
		//System.out.println("ChatServerThread 의 run호출");
		cs.jta_log.append("ChatServerThread 의 run호출"+"\n");
		//insert here - 듣고 말하기(방송)
		//말하기 처리는 메소드로 분리(for문-3사람)시킨다.
		boolean isStop = false;
		String msg = null;
		try {
			start:
				while(!isStop) {
					msg = (String)ois.readObject();
					cs.jta_log.append(msg+"\n");
					//로그가 append될때마다 스크롤바가 현재 문자열의 위치를 자동으로 인식해서 이동처리
					cs.jta_log.setCaretPosition(cs.jta_log.getDocument().getLength());
					//언제 선언부와 생성부를 나누어서 작성하나요?
					//msg가 null일 수도 있다.- 의심
					StringTokenizer st = null;
					int protocol = 0;
					if(msg !=null) {
						st = new StringTokenizer(msg, "|");
						protocol = Integer.parseInt(st.nextToken());
					}
					switch(protocol) {
						case 200:{
							cs.jta_log.append("ChatServerThread : 200번 청취완료");
							String nickName = st.nextToken();//kiwi
							String message = st.nextToken();//오늘 스터디할까?
							broadCasting(200+"|"+nickName+"|"+message);//oos.writeObject
						}break;
						case 500:{
							
						}break start;
					}
				}
			
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	//서버가 클라이언트에게 들은 것을 방송(여러사람-스레드를 붙임)하는 것이다.
	//왜 파라미터 자리인가? - 매번 입력되는 메세지가 달라진다. - 지역변수
	//oos.writeObject(message==>"200|kiwi|apple|오늘 스터디할까?");
	public void broadCasting(String message) {
		//for(int i = 0;i<cs.globalList.size();i++) {//cs.globalList.size()=3
		for(ChatServerThread cst:cs.globalList) {	
			cst.send(message);
			//클라이언트 들에게 서버가 들은 메시지를 보낸다.- OutputStream
			//ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
			//oos.writeObject(message);
		}
	}
	//DM을 처리하는데 필요한 메소드를 고려하여 메소드를 하나 더 추가해 본다.
	public void send(String message) {
		try {
			oos.writeObject(message);
		} catch (Exception e) {
			e.printStackTrace();//stack에 쌓여 있는 로그 히스토리를 출력함
		}
	}

}
<ChatClient>
package achat.step1;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;

import achat.step2.TalkClientThread;
//사용자가 하고싶은 말을 전달하는 클래스 설계이다.
public class ChatClient extends JFrame implements ActionListener{
	
	////////////////통신과 관련한 전역변수 추가 시작//////////////
	Socket 				socket 	= null;
	ObjectOutputStream 	oos 	= null;//말 하고 싶을 때
	ObjectInputStream 	ois		= null;//듣기 할 때
	String 				nickName= null;//닉네임 등록
	////////////////통신과 관련한 전역변수 추가  끝  //////////////
	JPanel jp_second	  = new JPanel();
	JPanel jp_second_south = new JPanel();
	JButton jbtn_one	  = new JButton("1:1");
	JButton jbtn_change	  = new JButton("대화명변경");
	JButton jbtn_font	  = new JButton("글자색");
	JButton jbtn_exit	  = new JButton("나가기");
	String cols[] 		  = {"대화명"};
	String data[][] 	  = new String[0][1];
	DefaultTableModel dtm = new DefaultTableModel(data,cols);
	JTable			  jtb = new JTable(dtm);
	JScrollPane       jsp = new JScrollPane(jtb);
	JPanel jp_first 		= new JPanel();
	JPanel jp_first_south 	= new JPanel();
	JTextField jtf_msg = new JTextField(20);//south속지 center
	JButton jbtn_send  = new JButton("전송");//south속지 east
	JTextArea jta_display = null;
	JScrollPane jsp_display = null;	

	//소켓 관련 초기화
		public void init() {
			try {
				//서버측의 ip주소 작성하기
				socket = new Socket("172.16.2.41",3002);//ServerSocket -> socket -> C.S.T client
				oos = new ObjectOutputStream(socket.getOutputStream());
				ois = new ObjectInputStream(socket.getInputStream());
				//initDisplay에서 닉네임이 결정된 후 init메소드가 호출되므로
				//서버에게 내가 입장한 사실을 알린다.(말하기)
				oos.writeObject(100+"|"+nickName);//말하기 시전 - 서버한테 - 듣고 말하기
				//서버에 말을 한 후 들을 준비를 한다.
				ChatClientThread tct = new ChatClientThread(this);//아직 일이없다
				tct.start();
			} catch (Exception e) {
				//예외가 발생했을 때 직접적인 원인되는 클래스명 출력하기
				System.out.println(e.toString());
			}
		}	
	
	public void initDisplay() {//키위(양파|토마토)님의 창- 전변으로 하나?아님 지변으로 할까?
		jtf_msg.addActionListener(this);
		//사용자의 닉네임 받기
		nickName = JOptionPane.showInputDialog("닉네임을 입력하세요.");
		this.setLayout(new GridLayout(1,2));
		jp_second.setLayout(new BorderLayout());
		jp_second.add("Center",jsp);
		jp_second_south.setLayout(new GridLayout(2,2));
		jp_second_south.add(jbtn_one);
		jp_second_south.add(jbtn_change);
		jp_second_south.add(jbtn_font);
		jp_second_south.add(jbtn_exit);
		jp_second.add("South",jp_second_south);
		jp_first.setLayout(new BorderLayout());
		jp_first_south.setLayout(new BorderLayout());
		jp_first_south.add("Center",jtf_msg);
		jp_first_south.add("East",jbtn_send);
		jta_display = new JTextArea();
		jta_display.setLineWrap(true);
		jta_display.setOpaque(false);
		Font font = new Font("굴림체",Font.BOLD,16);
		jta_display.setFont(font);
		jsp_display = new JScrollPane(jta_display);		
		jp_first.add("Center",jsp_display);
		jp_first.add("South",jp_first_south);
		this.add(jp_first);
		this.add(jp_second);
		this.setTitle(nickName);
		this.setSize(800, 550);
		this.setVisible(true);
	}	
	
	
	public static void main(String[] args) {
		JFrame.setDefaultLookAndFeelDecorated(true);
		ChatClient cc = new ChatClient();
		cc.initDisplay();
		cc.init();

	}
	@Override
	public void actionPerformed(ActionEvent e) {
		//말하기 구현 - > oos.writeObject("200|kiwi|tomato|오늘 스터디할까?");//프로토콜설계
		Object obj = e.getSource();
		String msg = jtf_msg.getText();
		//메시지 입력 후에 엔터 친거야?
		if(jtf_msg == obj) {
			try {
				oos.writeObject(200+"|"+nickName+"|"+msg);
				//메시지를 서버로 전송하고 나면 JTextField적힌 문자열은 지운다.
				jtf_msg.setText("");
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
	}

}
<ChatClientThread>
package achat.step1;

import java.util.StringTokenizer;
import java.util.Vector;

//서버가 한 말을 듣는 클래스 설계임
public class ChatClientThread extends Thread {
	ChatClient cc = null;
	public ChatClientThread(ChatClient cc) {
		//아래 초기화를 생략하면 ChatClientThread클래스안에서 ChatClient
		//의 변수나 메소드를 호출할 때 NullPointerException발동
		this.cc = cc;
	}
	//서버에서 말한 내용을 들어봅시다.
	//말하기는 ChatClient 의 actionPerformed에서 JTextField에 엔터 쳤을 때 처리합니다.
	public void run() {
		boolean isStop = false;
		start:
		while(!isStop) {
			try {
				String msg = "";
				msg = (String)cc.ois.readObject();
				StringTokenizer st = null;
				int protocol = 0;//100|200|500(나가기)
				if(msg !=null) {
					st = new StringTokenizer(msg,"|");
					protocol = Integer.parseInt(st.nextToken());//100
				}
				switch(protocol) {
					case 100: {// 100|kiwi - 입장하기 처리
						String nickName = st.nextToken();//kiwi
						cc.jta_display.append(nickName+"님이 입장하였습니다.\n");
						Vector<String> v = new Vector<>();
						v.add(nickName);
						cc.dtm.addRow(v);
					}break;
					case 200:{//다자간 대화 처리 - 청취하기 - 서버에서 보낸 메세지를 듣는곳
						String nickName = st.nextToken();
						String message = st.nextToken();
						cc.jta_display.append("["+nickName+"] "+message+"\n");
						cc.jta_display.setCaretPosition(cc.jta_display.getDocument().getLength());
					}break;
					case 500:{//나가기 처리
						
					}break start;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
<예제-LIst>
package achat.step1;

import java.util.ArrayList;
import java.util.List;

class Kiwi{
	String name = null; // 골드키위, 초록키위
}
class Tomato {
	
}
public class List3 {
	List<String> bread = new ArrayList<>();
	List<Kiwi> kiwis = new ArrayList<>();
	List<Tomato> tomatos = new ArrayList<>();
	void methodA() {
		bread.add("소보로");
		Kiwi kiwi = new Kiwi();
		kiwi.name = "골드키위";
		kiwis.add(kiwi); // bread.add(kiwi); 이러면 안 된다
		Kiwi kiwi2 = new Kiwi();
		kiwi2.name = "초록키위";
		kiwis.add(kiwi2);
		Tomato tomato = new Tomato();
		tomatos.add(tomato);
	}
	void methodB() {
		Kiwi kiwi = null;
		int i = 1;
		while(true) {
			kiwi = new Kiwi();
			System.out.println(kiwi);
			i++; 
			if(i==5) {
				break;
			}
		}
	}
	// 메인메소드는 개발자에게 제어권이 없다
	public static void main(String[] args) {
		List3 list3 = new List3();
		list3.methodB();
	}
}
<Map 예제>
package jdbc.oracle;

import java.util.HashMap;
import java.util.Map;

public class Map1 {
	Map<String,Object> map = null; // map은 한 개 로우만 담는다 - myBatis(SQL 오픈소스-생산성이 좋음. 코딩양이 30%이상 줄어들어서) 쓰게 되면 map을 자주 쓰게 됨
	// 컬럼 하나만도 담을 수 있다.
	// 전변 map에 대해서 초기화 해 보시오
	void methodA() {
		// insert here
		map = new HashMap<>();
		map.put("b_title", "자바의 정석");
		map.put("b_author", null);
		map.put("b_price", 30000);
	}
	void methodAPrint() {
		if(map!=null) {
		System.out.println(map.get("b_title"));
		System.out.println(map.get("b_author"));
		System.out.println(map.get("b_price"));
		} else {
			System.out.println("조회할 수 없습니다.");
		}
	}
	void methodB(Map<String,Object> pmap) {
		
	}
	Map<String,Object> methodC(Map<String,Object> pmap) {
		Map<String,Object> rmap = null;
		return rmap;
	}
	public static void main(String[] args) {
		Map1 m1 = new Map1();
		m1.methodA();
		m1.methodAPrint();
	}

}

SQL

SELECT seq_book1_no.nextval FROM dual; -- 여기는 ctrl+enter 계속 카운트 됨

SELECT max(empno)+1 FROM emp;

SELECT to_char(sysdate,'YYYY-MM-DD') FROM dual;

SELECT 1 FROM dual;

SELECT 1, 2, 3 FROM dual;

SELECT 1+2+3, 2*3, 5/2 FROM dual;

0개의 댓글