본문 바로가기
학원수업/12월

12/23 국비학원 20회차(서버, 채팅프로그램 만들기)

by 코딩마스터^^ 2022. 12. 23.

네트워크 프로그래밍 구현

 

1.프로토콜 설계가 필요하다

100#tomato

 

200#tomato#apple#오늘 스터디할까?-protocol.java-main메소드가 필요없다.

 

OSI 는 각종 시스템간의 연결을 위하여 ISO 에서 제안한 모델로써, OSI(Open System Interconnection Reference Model)에서 유추할수 있듯이, 시스템에 상관없이 서로의 시스템이 연결될수 있도록 만들어주는 모델이다. OSI 는 아래와 같이 7 개의 계층으로 되어 있다.

 

 

서버-ip주소

 

소켓통신

우리가 서버와 통신을 한다고 가정해보자. 그 서버의 특정 App에 접근을 하기위해서는 특정 port와 연결이 되어야한다. 

해당 port에 가면 우리는 바로 app에 접근하는것이 아니라 소켓을 거친뒤에 app에 접근 & 이용을 한다.

우리가 설계하는 소켓프로그래밍은 이 소켓부분을 설계에서 어떠한 방식으로 통신을 할지 정해주는 것이다.

 

1. 먼저 Client class 를 생성한다. 클래스의 파라미터는 보기와 같다.

2. 해당  클래스에 서버의 ip주소와 port 번호를 넣고 출력 스트림으로 넘어간후 Server Socket에 접근한다.

3. Server Socket class는 client가 접속을 했는지 체크만하는 용도이다.

4. 접근이 옴이 인식되면 서버 클래스는 제빨리 Socket.accept() 메소드를 실행한다.

 

출처 : https://mainpower4309.tistory.com/25

 

[JAVA Networking] 소켓(socket) 통신의 기본구조 및 기본개념

- 소켓 ( socket ) 왜 배우는가?좀뭔가 멋있어보이는 새로운 개념이 등장했다. 먼저 왜 발명되었는지 장점이 뭔지 좀 체크하자.우리는 앞서 openStream() 과 같은 메소드로 타입을 바꾸고 그 바뀐타입

mainpower4309.tistory.com

https://kadosholy.tistory.com/125

 

[Java] 자바 - 소켓통신이란? 소켓(Socket) 개념과 사용방법

자바 - 소켓통신이란? 소켓(Socket) 개념과 사용방법 네트워크를 통해 데이터를 주고받을 수 있도록 지원하는 소켓(Socket) 통신에 대해서 알아보도록 하겠습니다. 목차 소켓(socket)과 소켓통신이란?

kadosholy.tistory.com

만들어보기

client------Internet------sever

socket(ip,p)

생성자

serverSocket

 

object outputString

IO inputString

안정적이다.

 

내가 서버하기

서버- 서버소켓(파라미터=포트번호)

클라이언트 소켓(ip,port)

Thread를 통해서 제어를 한다. 

1초마다 전송을 해야한다

시,분,초->삼항연산자가

 

경합이 벌어져도 쓰레드

지속적으로 해야되도 스레드...

 

TimeServer 만들어보기

package dev_java.network1;

import java.io.IOException;
import java.io.ObjectInputStream;//듣기
import java.io.ObjectOutputStream;//말하기
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Calendar;
//서버를 기동하고 클라이언트가 접속해 오기를 기다린다-기다리면 스레드
public class TimeServer extends Thread{
  //선언부
  //객체직렬화가 가능하고 듣기와 쓰기가 가능한 I/O관련 클래스 선언
  ObjectOutputStream oos=null;//소켓이있어야만 객체 생성 가능함-말하기
  ObjectInputStream ois=null;//소켓이 있어야만 객체생성 가능함-듣기
  Socket client=null;//전변위치
  //run메소드 재정의 -스레드에서 할일을 처리함
  //반드시 선언부가 일치해야한다-부모가 가진 메소드와(쓰레드)
  //TimeServer is a Thread관계가 성립하니까 상속처리 가능하다

  //생성자
  TimeServer(){
  }
  //아래생성자가 필요한 이유는 서버소켓에 접속해온 클라이언소켓을 
  //런 메소드에서 사용해야하니까 전변으로 치환해야함.
  TimeServer(Socket client){
    this.client=client;
  }
  //스레드 구현 메소드에서 서버 소켓에 접속해온 클라이언트 소켓을 가지고 
  //말하기와 듣기에 필요한 oos와 ois객체를 생성함
  // I/O도 지연과 데드락 상태에 빠질수 있으므로 반드시 예외처리할것.
  @Override
  public void run(){
    //여기서 처리해야한다 
    try {
      oos=new ObjectOutputStream(client.getOutputStream());
      ois=new ObjectInputStream(client.getInputStream());
      while(true){
        oos.writeObject(getTimeMessage());//12:05:45
        try {
          sleep(1000);//sleep은 1000 즉, 1초만큼 정지한다. 그래서 1초에 한번씩 나오는것이다.
        } catch (InterruptedException ie) {
          
        }
      }//end of while
    } catch (Exception e) {
     
    }//end of try-catch
  }
  //메인메소드
  public static void main(String[] args) {
    //서버소켓 생성시 파라미터로 포트 번호가 필요하다
    int port=7891;
    //동시에 여러사람이 접속을 시도함

    ServerSocket server =null;
    Socket client=null;//여기에 들어오는 소켓은 서버소켓에 접속해온 클라이언트의 소켓이다. 주소번지이다.클라이언트를 참조하고있다.
   try {
      server=new ServerSocket(port);
    } catch (IOException ie) {
      System.out.println("Cant't bind port"+ port);
      ie.printStackTrace();//에러메세지를 라인번호와 함께 출력-힌트문-디버깅
      try {
        server.close();
      } catch (Exception e) {
        System.exit(0);//서버 강제종료시킴
      }
    }//end of try-catch
    System.out.println("TimeServer start successfully...");
    while(true){
      try {
        //클라이언트가 접속해오기를 기다리다가 접속(new Socket("192.168.10.47",7891))을 해 오면 
        //아래 메소드가 클라이언트 소켓 정보를 취득함
        client=server.accept();
        System.out.println(client.getInetAddress());//클라이언크의 네트워크 정보
        System.out.println("New Client connected...");
        //쓰레드의 개입이 필요하다-1초동안 sleep(1000)멈춰라는뜻...호출하고 현재 시분초 정보를 출력해야한다
        //스레드를 동작시킴-스레드에 추상 메소드 런을 호출해야한다.
        //주의사항: 런을 직접 호출하는게 아니라 start() 호출하면 JVM이 순서를 따져서 출발시킴-콜백
        //스레드 대기방에 모여있다가 순서대로 나가는 느낌/ 스레드가 메소드를 지원하고있다.
        TimeServer ts =new TimeServer(client);//왜냐면 런에서 사용하고 싶으니까
        ts.start();//런 메소드가 호출
        System.out.println("New TimeServer Thread Started...");
      } catch (Exception e) {
        
      }
    }//end of while
  }//end of main
  public String getTimeMessage(){
    Calendar cal=Calendar.getInstance();
    int hour=cal.get(Calendar.HOUR_OF_DAY);
    int min=cal.get(Calendar.MINUTE);
    int sec=cal.get(Calendar.SECOND);
    return (hour<10?"0"+hour:""+hour)+":"
    +(min<10?"0"+min:""+min)+":"
    +(sec<10?"0"+sec:""+sec)+" 얄루^-^";
  // return "얄루^^";
  }
  
}
package dev_java.network1;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.JLabel;

public class TimeClient extends Thread{
  Socket socket=null;
  ObjectOutputStream oos=null;
  ObjectInputStream ois=null;
  JLabel jlb_timer=null;
  public TimeClient(){}
  public TimeClient(JLabel jlb_timer) {
    this.jlb_timer=jlb_timer;
  }
  @Override
  public void run(){
    //서버로부터 읽어온 시간정보를 담기
    String timeMsg=null;
    try {
      //아래의 문장이 실행되자마자  TimeServer의 serverSocket이 accept()호출하여
      //클라이언트 소켓정보를 서버가 취득함
      socket=new Socket("192.168.10.69", 7891);
      oos=new ObjectOutputStream(socket.getOutputStream());
      ois=new ObjectInputStream(socket.getInputStream());
      while(true){
        try {
          while((timeMsg=(String)ois.readObject())!=null){
            System.out.println(timeMsg);
            jlb_timer.setText(timeMsg);
          }//end of inner while
          sleep(1000);
        } catch (IOException ie) {
          System.out.println("timeserver에 접속할 수 없습니다.");
          
        }finally{
        try {
          oos.close();
          ois.close();
          socket.close();
        } catch (Exception e) {
          // TODO: handle exception
        }}
      
      }
    } catch (Exception e) {
    
    }
  }
  public static void main(String[] args) {
    //선언부와 생성부의 이름이 다를 수 있다.-그래야 다형성 구현이 가능함-권장
    //자바에서는 new 다음에 오는 이름으로 객체가 생성됨
    Thread th=new TimeClient();
    th.start();//run호출

  }
  
}
/*
 * TimeSever에서 제공하는 현재 시간 정보를 읽어오는 클래스를 구현
 * 그런데 스레드를 상속 받은 이유는 1초마다 읽어와야 하니까
 * sleep(7891)이라는 메소드를 호출해야한다. 그러니까 나는 스레드이어야 한다.
 * 1초마다 계속 읽어 와야하니까 무한루프 돌린다.-핸드폰 종료하면
 */

화면띄우기

package dev_java.network1;

import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Font;

public class TimeClientView extends JFrame{
  //선언부
  JLabel jlb_timer=new JLabel("현재시간", JLabel.CENTER);
  Font f= new Font("굴림체",Font.BOLD,46);
  //생성부
  TimeClientView(){
    initDisplay();
  }

  //화면처리부
  public void initDisplay(){
    Thread th = new TimeClient(jlb_timer);
    th.start();
    this.add("Center",jlb_timer);
    this.setSize(400,300);
    this.setVisible(true);
    jlb_timer.setFont(f);
  
  }
  //메인메소드
  public static void main(String[] args) {
    new TimeClientView();
  }
  
}

 

다중 채팅프로그램 만들기

TalkSever에서는

JFrame으로 로그기록

스레드

이벤트처리

  • initDisplay()
  • Run()

 

TalkseverThread에서는

  • Run()
  • TalkServerThread() 

스레드를 상속받아야해

들은것을 말하는 컨셉이다.

멀티 스레드

서버를 맡은 사람(나)는 장애까지 고려해야된다.

 

TalkClient에서는 

JFrame사용

  • ActionListener
  • initDisplay();

 

TalkclientThread에서는

  • Run()
  • TalkclientThread()
  • init()-소켓설정
 * final클래스 앞에 오면 상속을 못함
 * 메소드 앞에 오면 메소드 오버라이딩 못함
 * 변수 앞에 오면 상수임
 */

try-catch문에서
printStackTrace

 

package dev_java.network2;

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

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

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

//자바는 단일 상속만 가능하다
//선언과 생성을 분리하는 코딩전개
//자바는 단일 상속의 단점을 보완하기 위해서 인터페이스는 다중으로 처리 가능함(구현체 클래스)

public class TalkServer extends JFrame implements Runnable, ActionListener{//경합이 벌어져서 러너블을 넣은거다. 
  //선언부
  //클라이언트측에서 뉴소켓하면 그 소켓정보를 받아서 스레드로 넘김
  TalkServerThread 		tst 		= null;
 
  //동시에 여러명이 접속하니까List-Vector<>();멀티스레드 안전, 속도 느림
	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);
	JButton jbtn_log = new JButton("로그저장");
	String logPath = "src\\athread\\talk1\\";
  
  //화면그리기
	public void initDisplay() {//화면을 먼저 호출해라
		jbtn_log.addActionListener(this);
		this.add("North",jbtn_log);
		this.add("Center",jsp_log);
		this.setSize(500, 400);
		this.setVisible(true);
	}
	//서버소켓과 클라이언트측 소켓을 연결하기
	@Override
	public void run() {
		//서버에 접속해온 클라이언트 스레드 정보를 관리할 벡터 생성하기 
		globalList = new Vector<>();//멀티 스레드가 안전해서 ArrayList(싱글스레드 안전)대신 사용함
		boolean isStop = false;
		try {
			server = new ServerSocket(7891);//서버포트 번호 설정하기
			jta_log.append("Server Ready.........\n");//대기 탐-멈춤-wait
			while(!isStop) {
				socket = server.accept();
				jta_log.append("client info:"+socket+"\n");				
				jta_log.append("client info:"+socket.getInetAddress()+"\n");				
				TalkServerThread tst = new TalkServerThread(this);
				tst.start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

  //생성자
  public TalkServer(){
    //initDisplay();//망한다. 화면을 먼저 호출해라. 시점의 문제가 발생한다. 스케줄링의 해야된다.
  }
 
  public static void main(String[] args) {
    //메인 메소드 자체도 메인 스레드이다. 엔트리 포인트이다. 우선순위가 있다.
    TalkServer ts= new TalkServer();
    ts.initDisplay();//여기서 호출
    Thread th=new Thread(ts);
    th.start();//밑에 오버라이드 호출된-지연발생함-클라이언트가 접속할때까지 대기함
  }
  //서버소켓과 클라이언트 소켓을 연결
  @Override
  public void actionPerformed(ActionEvent e) {
    //로그를 파일로 저장하기
  }
  
}
package dev_java.network2;

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

public class TalkServerThread extends Thread {
  TalkServer ts =null;
  Socket client=null;
  ObjectOutputStream oos=null;
  ObjectInputStream ois=null;
  String chatName=null;
  //생성자
  public TalkServerThread(){
  
  }
  public TalkServerThread(TalkServer ts) {
    this.ts=ts;
    this.client=ts.socket;
    try {
      oos=new ObjectOutputStream(client.getOutputStream());//말하기
      ois=new ObjectInputStream(client.getInputStream());//듣기
      String msg=(String)ois.readObject();
      ts.jta_log.append(msg+"\n");
      StringTokenizer st=new StringTokenizer(msg,"#");
      st.nextToken();//100 skip
      chatName=st.nextToken();//토마토저장
      ts.jta_log.append(chatName+"님이 입장하였습니다.\n");
      //현재 서버에 입장한 클라이언트 스레드 추가하기
    
      ts.globalList.add(this);
      this.broadCastring(msg);
    } catch (Exception e) {
      // TODO: handle exception
    }
  }
  public void broadCastring(String msg){
    for(TalkServerThread tst:ts.globalList){
      tst.send(msg);
    }

  }
  //클라이언트에게 말하기 구현
  public void send (String msg){
    try {
      oos.writeObject(msg);
    } catch (Exception e) {
      e.printStackTrace();//디버깅할때 유익함 외우세요
      // TODO: handle exception
    }
  }
  @Override
  public void run(){
  
  } 
}

 

package dev_java.network2;

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 java.awt.GridLayout;
import java.awt.BorderLayout;

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 = new JTextArea(15,38);
	JScrollPane jsp_display = new JScrollPane
			(jta_display
			,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
			, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

  //생성자
  public TalkClient(){

  }

  //소켓 관련 초기화
	public void init() {
		try {
			//서버측의 ip주소 작성하기
      //socket = new Socket("192.168.0.244",3000);
      //new SeverSocket(7891)이 받아서 accept()통해서 client 소켓에 저장됨 
			socket = new Socket("192.168.10.70",7891);
			oos = new ObjectOutputStream(socket.getOutputStream());
			ois = new ObjectInputStream(socket.getInputStream());
			//initDisplay에서 닉네임이 결정된 후 init메소드가 호출되므로
			//서버에게 내가 입장한 사실을 알린다.(말하기)
			oos.writeObject(100+Protocol.sperator+nickName);
			//서버에 말을 한 후 들을 준비를 한다.
			TalkClientThread tct = new TalkClientThread(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.setLineWrap(true);
		jp_first.add("Center",jsp_display);
		jp_first.add("South",jp_first_south);
		this.add(jp_first);
		this.add(jp_second);
		this.setSize(800, 550);
		this.setVisible(true);

  }

  //말하기 -서버에 전달-List<TalkServerThresd>->반복문->전송
  @Override
  public void actionPerformed(ActionEvent e) {
    Object obj= e.getSource();
    
  }
  
  //메인
  public static void main(String[] args) {
    TalkClient tc= new TalkClient();
    tc.initDisplay();
    tc.init();
  }
}
package dev_java.network2;

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

public class TalkClientThread extends Thread{
  TalkClient tc=null;

  public TalkClientThread(TalkClient tc) {
      this.tc=tc;
    }
  
  //서버쪽에서 클라인어트가 접속하면 접속자의 정보를 List <TalkServerThread> add(스레드 생성자)했고
  //메세지를 청취 하자마자 클라이언트쪽에 반복문을 돌려서 쓰기 한다.(전송함)-broadCasting(String msg)
  @Override
  public void run(){
    boolean isStop=false;
    // :run_stop//라벨을 쓰면 바깥쪽 와일문 탈출할수있다.
    while(!isStop){
      try {
        //100#tomato 님이 입장하였습니다.
        String msg="";
        msg=(String)tc.ois.readObject();
        System.out.println("서버에서 전송된 msg:"+msg);
        StringTokenizer st =null;
        int protocol=0;//100 200 300 400 500
        if(msg!=null){
          st=new StringTokenizer(msg, "#" );
          protocol=Integer.parseInt(st.nextToken());
        }
        System.out.println("protocol :"+protocol);
        switch(protocol){
          case Protocol.TALK_IN:{
            String nickName=st.nextToken();
            tc.jta_display.append(nickName+"님이 입장하였습니다.\n");
            //JTable은 양식일 뿐이고 데이터셋은 디폴트테이블모델이니까 거기에 
            //닉네임을 출력함
            Vector<String> v =new Vector<>();
            v.add(nickName);
            //데이터셋 객체에 한개 로우 추가하기
            tc.dtm.addRow(v);
          }break;
          default:
          System.out.println("해당하는 프로토콜이 존재하지 않습니다.");
        }
      } catch (Exception e) {
        e.printStackTrace();
      }

    }

  }
  
}

 

분석 꼼꼼히 하자....ㅜ

댓글