(18)-23.10.18[JAVA]

DANA·2023년 10월 18일

KDT-구디아카데미

목록 보기
18/56

/*
서버측
ServerSocket 생성함 - new ServerSocket(5000);
기다림 지속 --
그 다음 코드는 진행되지 못한다. - 서버 측에도 클라이언트와의 소통으로 인한 상태값을 로그로 남긴다.- JFrame
initDisplay()
서버가 준비되기 전에 화면은 먼저 연다(출력,보여주자-로그를 출력하는 화면-JTextArea-멀티로우입력가능함)

클라이언트측

  • new Socket("서버의 IP주소", 5000) //인스턴스화 -> 서버측에 클라이언트측의 소켓 객체를 전달하는 것과 동일한 결과를 갖게 될 것이다-통신채널 열린다
    Socket client = server.accept();// 서버소켓에 접속한 소켓 정보를 쥐게 된다
    */

chat 클래스 설계

스레드가 뭘까?

  • java.lang.Thread

어떤 역할을 하지?

  • 한정(하나)된 자원을 여러 사람이 공유하거나 나누어 쓸 때 필요한 클래스이다

결론

  • Thread가 제공하는 메소드를 사용하기 위해서는 Thread 구현해야만 한다

<Runnable 예제>
package achat_step1;

import javax.swing.JFrame;

public class Customer extends JFrame implements Runnable {

	public static void main(String[] args) {
		Customer ct = new Customer();
		Thread th = new Thread(ct);
		th.start();
	}

	@Override
	public void run() {
		// (빵을 고르고 카운터 가서) 빵 주세요~ 이런 거
		
	}

}
<참고1>
package achat_step1;

import javax.swing.JFrame;

public class Customer extends JFrame implements Runnable {

	public static void main(String[] args) {
		Customer ct = new Customer();
		Thread th = new Thread(ct);
		th.start(); // run을 직접 호출하는 것이 아니라 start 호출하면 시스템이 적당한 시기에 대신 호출함. - 콜백 메소드
	}

	@Override
	public void run() {
		// (빵을 고르고 카운터 가서) 빵 주세요~ 이런 거
		System.out.println("run 호출 성공");
	}

}
<참고2>
package com.step5;
// FlyBehavior 이름 안에서 오리에 대한 생각
// 비행기도 헬리콥터도 모두 난다 - 상속으로 한다.
// 그래서 나는 기능을 상속으로 재정의하지 않고 인터페이스로 처리하는 것이
// 클래스 사이의 결합도를 낮춰서 단위테스트가 가능하고 독립적인 기능을 구사할 수 있을 것이다
// 추상클래스와 인터페이스 중심의 코딩을 전개하라
// 구현체 클래스를 구현하기 전에 설계 단계에서 필요한 명세서를 작성해 보시오
// 기능에 대한 확장이나 추가적인 클래스 구현시에 도움이 된다(시간절약, 진화, 개선, 발전..)
public interface FlyBehavior {
	// 분명히 메소드 선언인데 세미콜론으로 끝남 - 추상메소드
	public abstract void fly();
}
<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초라도 먼저 들어오면 관리를 해준다. 읽기 쓰기 속도가 느리다)
	// 챗서버에 접속해온 사용자에 대한 정보를 담는 vector 선언이다.
	// 다수의 클라이언트 요청을 처리하는 서버를 개발할 때 사용함
	List<ChatServerThread> globalList = null;
	ServerSocket server = null;
	// 서버는 들은 내용을 말하는 것이다.
	// 실제로 듣고 말하기는 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<>();
		boolean isStop = false;
		try {
			server = new ServerSocket(3002);
			jta_log.append("Server Ready.........\n");
			while(!isStop) {
				// 서버소켓에 접속해온 사용자의 소켓정보를 담는다
				socket = server.accept();
				jta_log.append("client info:"+socket+"\n");	
				// 아래에서 this는 ChatServer 가리킨다
				ChatServerThread tst = new ChatServerThread(this);
				tst.start(); // ChatServerThread의 run 호출된다.
//				break;
			}
		} 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);
		cs.start(); // 여기서 대기 탄다 - this.wait(); -> this.notify();잠자는 스레드를 깨운다
		// 두 메소드가 호출되었을 때 지연이 발생되는 메소드는 start()이다.
		// 같은 메소드라도 처리되는 시간이 다르다 - 스케쥴링이 필요함
		// this.sleep(10000), this.wait(); this.notify();
		// 대기상태에 있는 손님이 있다
		cs.initDisplay();
	
	}
}
<ChatServerThread>
package achat.step1;
//생성은 ChatServer , 사용은 ChatServerThread
import java.net.Socket;

public class ChatServerThread extends Thread {
	ChatServer cs = null;
	
	// 생성자의 파라미터
	public ChatServerThread() {}
	public ChatServerThread(ChatServer cs) {
		this.cs = cs;
		System.out.println(this.cs.globalList.size());//0
	}
	
	// run 메소드도 콜백메소드입니다. - 개발자가 직접 호출하지 않아요.
	// 시스템에서 대신 호출해 줌
	@Override // annotation
	public void run() {
		System.out.println("ChatServerThread의 run메소드 호출");
		// insert here - 듣고 말하기(방송)
		// 말하기 처리는 메소드로 분리시킨다. 이유는? 서버에 접속한 회원들에게 오늘 스터디 할까요?라고 전달을 해야 하니까 메소드로 분리하자.
		// for문이 필요하겠다고 떠올려야 한다. 왜? 3명의 사람에게 방송을 하니까. 서버가 들은 걸 말하는 거니까.
	}
	// 서버가 클라이언트에게 들은 것을 방송(여러사람-스레드를 붙임)하는 것이다
	// 왜 파라미터 자리인가? 매번 입력되는 메세지가 달라진다. - 지역변수다
	public void broadCasting(String message) {
		for(int i=0;i<cs.globalList.size();i++) { // cs.globalList.size()=3
			// 클라이언트들에게 서버가 들은 메세지를 보낸다 - OutputStream
		}
	}
}
<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.x.xxx",3002);
				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() { // 키위(양파|토마토)님의 창 - 전변으로 하나? 아님 지변으로 할까?
		//사용자의 닉네임 받기
		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) {
		// TODO Auto-generated method stub

	}
	@Override
	public void actionPerformed(ActionEvent e) {
		// 말하기 구현 -> oos.writeObject("200|kiwi|tomato|오늘 스터디할까?"); // 프로토콜 설계
		
	}

}
<ChatClientThread>
package achat.step1;
// 서버가 한 말을 듣는 클래스 설계임
public class ChatClientThread extends Thread {
	ChatClient cc = null;
	public ChatClientThread(ChatClient chatClient) {
		// 아래 초기화를 생략하면 ChatClientThread클래스 안에서 ChatClient의 
		// 변수나 메소드를 호출할 때 NullPointerException 발동
		this.cc = cc;
	}

}
<ProtocolTest> -> 다시 확인해 보기
package achat.step1;

import java.util.StringTokenizer;

public class ProtocolTest {

	public static void main(String[] args) {
		String msg = "200|kiwi|tomato|오늘 스터디할까?";
		StringTokenizer st = new StringTokenizer(msg,"|");//token
		// String protocol = st.nextToken(); //200
		// String from = st.nextToken(); //kiwi
		// String to = st.nextToken(); //tomato
		// String msg1 = st.nextToken(); // 오늘 스터디할까?
		// System.out.println(protocol+","+from+","+to+","+msg1);
		
		// while(boolean){
		while(st.hasMoreElements()){
			st.nextToken(); //200
			st.nextToken(); //kiwi
			// String s = st.nextToken();
			String s = (String)st.nextElement();
			System.out.println(s);
		}
	}
}
// api 보고 while문이나 for문으로 찍기+
<프로토콜 참고>
package achat.step1;

public class Protocol {
	public final static int LOGIN = 100;
	public final static int MESSAGE = 200;
	public final static int DM = 210;
	public final static int LOGOUT = 190;
}
<List>
package achat.step1;

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

public class List1 {

	public static void main(String[] args) {
		List<String> list = new Vector<>();
		list.add(0,"사과");
		list.add(1,"키위");
		list.add(2,"토마토");
		String apple = list.get(0);
		System.out.println(apple);
		System.out.println(list);
	}

}
//Thread 예시
package achat.step1;
// Baker.class, Baker$1.class
public class Baker {

	public static void main(String[] args) {
		Thread th = new Thread() { // annoymous 클래스이다
			public void run() {
				
			}
		};
	}
}

채팅창 예시 코드

<TalkServer>
package achat.step2;

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;

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

public class TalkServer extends JFrame implements Runnable{
	TalkServerThread 		tst 		= null;
	List<TalkServerThread> 	globalList 	= null;
	ServerSocket 			server 		= null;
	Socket 					socket 		= null;
	JTextArea 				jta_log = new JTextArea(10,30);
	JScrollPane 			jsp_log = new JScrollPane(jta_log
			                                         ,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
			                                         ,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
	JPanel 		jp_north = new JPanel();
	public void initDisplay() {
		jp_north.setLayout(new FlowLayout(FlowLayout.LEFT));
		jta_log.setBackground(Color.orange);
		this.add("North",jp_north);
		this.add("Center",jsp_log);
		this.setSize(500, 400);
		this.setVisible(true);
		
	}
	//서버소켓과 클라이언트측 소켓을 연결하기
	@Override
	public void run() {
		//서버에 접속해온 클라이언트 스레드 정보를 관리할 벡터 생성하기 
		globalList = new Vector<>();
		boolean isStop = false;
		try {
			server = new ServerSocket(3002);
			jta_log.append("Server Ready.........\n");
			while(!isStop) {
				socket = server.accept();
				jta_log.append("client info:"+socket+"\n");				
				TalkServerThread tst = new TalkServerThread(this);
				tst.start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		TalkServer ts = new TalkServer();
		ts.initDisplay();
		Thread th = new Thread(ts);
		th.start();
	}

	public String setTimer() {
		Calendar cal = Calendar.getInstance();
		int yyyy = cal.get(Calendar.YEAR);
		int mm = cal.get(Calendar.MONTH)+1;
		int day =  cal.get(Calendar.DAY_OF_MONTH);
		return yyyy+"-"+
			   (mm < 10 ? "0"+mm:""+mm)+"-"+
			   (day < 10 ? "0"+day:""+day);
	}////////////////end of setTimer
}
<TalkClient>
package achat.step2;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
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;

public class TalkClient 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 TalkClient() {
		jtf_msg.addActionListener(this);
		jbtn_exit.addActionListener(this);
		jbtn_change.addActionListener(this);
	}
	public void initDisplay() {
		//사용자의 닉네임 받기
		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);
		TalkClient tc = new TalkClient();
		tc.initDisplay();
		tc.init();
	}
	//소켓 관련 초기화
	public void init() {
		try {
			//서버측의 ip주소 작성하기
			socket = new Socket("172.16.2.117",3002);
			oos = new ObjectOutputStream(socket.getOutputStream());
			ois = new ObjectInputStream(socket.getInputStream());
			//initDisplay에서 닉네임이 결정된 후 init메소드가 호출되므로
			//서버에게 내가 입장한 사실을 알린다.(말하기)
			oos.writeObject(100+"#"+nickName);
			//서버에 말을 한 후 들을 준비를 한다.
			TalkClientThread tct = new TalkClientThread(this);
			tct.start();
		} catch (Exception e) {
			//예외가 발생했을 때 직접적인 원인되는 클래스명 출력하기
			System.out.println(e.toString());
		}
	}
	@Override
	public void actionPerformed(ActionEvent ae) {
		Object obj = ae.getSource();
		String msg = jtf_msg.getText();
		if(jbtn_one == obj) {
			
		}
		else if(jtf_msg==obj) {
			try {
				oos.writeObject(201
						   +"#"+nickName
						   +"#"+msg);
				jtf_msg.setText("");
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		else if(jbtn_exit==obj) {
			try {
				oos.writeObject(500+"#"+this.nickName);
				//자바가상머신과 연결고리 끊기
				System.exit(0);
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		else if(jbtn_change == obj) {
			String afterName = JOptionPane.showInputDialog("변경할 대화명을 입력하세요.");
			if(afterName == null || afterName.trim().length()<1) {
				JOptionPane.showMessageDialog(this
				, "변경할 대화명을 입력하세요"
				, "INFO", JOptionPane.INFORMATION_MESSAGE);
				return;
			}
			try {
				oos.writeObject(202
						   +"#"+nickName
						   +"#"+afterName
						   +"#"+nickName+"의 대화명이 "+afterName+"으로 변경되었습니다.");
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}//////////////////////end of actionPerformed
}

0개의 댓글