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


<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
}