190509_Day22 소켓


RMI

SOCKET

네트워크 중 두가지가 있다.

  • TCP : 전화기,데이터 손실 없는 신뢰성
  • UDP : 우편
  • 오늘은 TCP~!

img

  • Socket이라는것은 양쪽이 가지고 있는 전화기 같은것(통신수단)

  • <TCP전송방식>

    1. 연결방식
      • 연결기반(connection-oriented)
      • 연결 후 통신(전화기)
      • 1:1 통신방식
    2. 특징
      • 데이터의 경계를 구분 안 함.(byte-stream)
      • 신뢰성 있는 데이터 전송
      • 데이터의 전송순서가 보장.
      • 데이터의 수신여부를 확인함(데이터가 손실되면 재전송)
      • 패킷을 관리할 필요가 없음
      • UDP보다 전송속도가 느림
    3. 관련 클래스
      • Socket, ServerSocket

    <TCP소켓프로그래밍>

    1. 서버 프로그램: 서버소켓을 사용, 서버 컴퓨터의 특정포트에서
      클라이언트의 연결요청을 처리할 준비
    2. 클라이언트 프로그램: 접속할 서버의 IP주소와 포트 정보를 가지고 소켓을 생성해서
      서버에 연결을 요청.
    3. 서버소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서
      클라이언트의 소켓과 연결되도록 한다.
    4. 클라이언트의 소켓과 서버의 소켓이 일대일 통신.

<Socket프로그램>

  • Socket은 전화기!!
  • Server와 Client사이에 통신하려면 Socket을 통해 연결

=>Server ( 클라이언트의 연결 대기 상태를 표현 )

  • ServerSocket객체를 가져야 함.

  • //=>Server
    ServerSocket ss = new ServerSocket(서비스 할 포트넘버 예5000); //[작업순서1]
    //만약 '서버소켓'객체를 실행하는 JVM의 IP가 192.168.0.x라고 했을 때 이는 PC를 구별하는 번호
    //192.168.0.x의 5000번 포트로 소켓 서비스를 실행하겠음!
    Socket s = ss.accept();// 연결대기상태표현, 클라이언의트 접속 대기 : [2]
    
    //'서버' => '클라이언트'에게 메시지 전달
    
    OutputStream out = s.getOutputStream(); // 클라이언트에 메세지 전송
    out.write("전송할 메세지");//[4]
    
    InputStream in = s.getInputStream();//클라이언트가 전달한 메시지 읽어오기
    in.read(); //스트림으로부터 읽기 작업[7]
  • //=>Client
    //Socket객체를 생성
    Socket s = new Socket("HOST정보", port번호);//서버소켓 연결 요청 [3]
    InputStream in = s.getInputStream(); //서버가 전달한 메세지 읽어오기
    in.read();//스트림으로부터 읽기 작업[5]
    OutputStream out = s.getOutputStream();//서버에 메시지 전송
    out.write("나도안녕"); //스트림에 쓰기 작업 [6]
    
  • package j0509;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class EchoClient
    {
    
    
  public static void main(String[] args) throws UnknownHostException, IOException
  {
      Socket s = new Socket("localhost", 5000);

      System.out.println("서버연결 성공");

      //[4] 소켓을 통헤 입출력 (통신) 하기 위한 객체 생성
      InputStream is = s.getInputStream(); // 읽기 객체
      //읽기작업을 편하게 하기 위해
      BufferedReader in = new BufferedReader(new InputStreamReader(is));
      OutputStream out = s.getOutputStream(); // 쓰기 객체

      //[5]write [6]read [7]write [8]read 서버와 클라가 각각 번달아가며

      //[6]메시지 읽기
      String msg = in.readLine();
      System.out.println("from Server => " + msg);

      //[7]메시지 보내기
      String toMsg="햄벅\n";
      out.write(toMsg.getBytes());

  }//클라이언트main    

}


- ```java
  package j0509;

  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.net.ServerSocket;
  import java.net.Socket;

  public class EchoServer
  {
      // 주된 업무 : 소켓 서비스
      // 필요자원 : ServerSocket!
      // 응답대기 : accept()
      // 소켓채널에 대한 입출력
      // 출력 write : 데이터 보내기 
      // 입력 read : 데이터 읽기


      public static void main(String[] args) throws IOException
      {
          //[1]
              ServerSocket ss = new ServerSocket(5000);
              //서버 소켓을 실행하는 JVM이 갖는 ip주소와 명시된 port 번호로 소켓서비스를 시작할 준비

              System.out.println( "서버 시작 (접속대기중) ......" );
              Socket s = ss.accept(); //[2] 클라이언트 접속 대기 메소드
              // s : 접속한 클라이언트의 소켓정조가 저장

              String addr = s.getInetAddress().getHostAddress(); // Client ip알아내기
              System.out.println("클라이언트 접속 성공!");

              //[4] 소켓을 통헤 입출력 (통신) 하기 위한 객체 생성
              InputStream is = s.getInputStream(); // 읽기 객체
              //읽기작업을 편하게 하기 위해
              BufferedReader in = new BufferedReader(new InputStreamReader(is));
              OutputStream out = s.getOutputStream(); // 쓰기 객체

              //[5]write [6]read [7]write [8]read 서버와 클라가 각각 번달아가며

              //[5]메시지 보내기
              String msg = " 점심 뭐 먹니? \n"; //\n이 구분자 안보내면 줄 안바뀌어서 readLine()이 계속 대기함
              out.write(msg.getBytes());

              //[8]메시지 읽기
              String fromMsg = in.readLine();
              System.out.println("From client[" + addr + "]" + fromMsg);



      }//서버main    

  }
  • ava.net.BindException 이런 에러 자주 발생한다. 같은 포트에 똑같이 접속 요구하면 이렇게 된다. => CMD로 서버 틀고 이클립스로 Client 실행하는 방식으로 해결

  • 그런데 이 방식으로는 주고 받는 것은 불가하고, 한개식 받는 방법 밖에 없다

  • 이를 해결하기 위해 쓰레드!

  • <서버>

    • (소켓)클라이언트 접속 대기
    • 클라이언트 한개가 메시지를 쓰면
      ----read()----->
      접속된 모든 클라이언트에게 메시지
      ----writeAll()---->
  • package j0509;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Vector;
    
    import j0509.Server.Service;
    
    public class Server implements Runnable //외부클래스 : 소켓을 통한 접속서비스 ( 접속대기 )
    {
        ServerSocket ss;
        Vector <Service>v; //접속한 클라이언트 관리
    
        public Server()
        {
            System.out.println("Server Start......");
            v = new Vector<>();
            try
            {
                ss = new ServerSocket(3000); // [1] 서버소켓 객체 생성
                new Thread(this).start();
            } catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }//외부 클래스 생성자
    
        public void run() //여러 클라이언트 접속
        {
            try
            {
                while(true)
                {
                Socket s = ss.accept(); // [2] 클라이언트 접속 대기 ( 클라이언트 프로그램 : new Socket()과 매핑 ) 
    //            Service serv = new Service(s); // 이걸 아래처럼 한줄로!
    //            v.add(serv);
                v.add(new Service(s));
                }
            } catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        //===============================================
        class Service extends Thread // 내부클래스 : 소켓을 통한 입출력 서비스
        {
            //※ Service객체 한개 == 클라이언트 한개!
    
            BufferedReader in; // 소켓을 통해서 읽기
            OutputStream out; // 소켓에 쓰기
    
            String clientAddr;
            String nickName;
    
            public Service(Socket s)
            {
                try
                {
                    //★☆ [4]소켓 입출력 객체 생성
                    in = new BufferedReader(new InputStreamReader(s.getInputStream()));
                    out = s.getOutputStream();
                    clientAddr = s.getInetAddress().getHostAddress();
                    start();
                } catch (IOException e)
                {
                    e.printStackTrace();
                }
    
            } //내부 클래스 생성자
    
            public void run() //클라이언트가 보내는 여러메시지를 읽어주는 기능
            {
            try
                {
                while(true)
                {
                        String msg = in.readLine();
    
                        //모니터링
                        System.out.println(" [ " + clientAddr +" ] > > > " + msg);
    
                        String arr[] = msg.split("\\|");
                        //arr = { "100", "홍길동"} arr = { "200", "안녕하세요"}
                        switch (arr[0])
                        {
                        case "100": //대화명 전달
                            nickName = arr[1];
                            break;
    
                        case "200" : //메시지 전달
                            messageAll(" [ " + nickName +" ] " + arr[1]); // [홍길동] -> 안녕하세요
                        }
    
                }
    //                    messageAll(msg);
                        // [6]번혹은 [8]번이 될 수 있다. 클라이언트가 tf보낸 메시지 읽기
                    } catch (IOException e)
                    {
                        System.out.println("[#클라이언트가 접속을 끊었습니다.]");
                    } 
    
    
      }//내부run

      public void messageTo(String msg) throws IOException //특정 클라이언트에 메시지 보내기
      {
          out.write((msg+"\n").getBytes()); //[7] 실제 클라이언트에게 메시지 보내기
      }//messageTo

      public void messageAll(String msg) //접속한 모든 클라이언트에게 메시지 보내기
      {
          for(int i = 0; i < v.size(); i++) // 전체 클라이언트 (Service벡터)
          {
              //i = 0 길동, i = 1 라임, i = 2 주원

              Service serv = v.get(i);
              try
              {
                  serv.messageTo(msg);
              } catch (IOException e)
              {
                  //에러발생 => 클라이언트가 접속 끊음!(소켓은 사라졌지만, Service객체가 벡터에 존재)
                  //접속 끊긴 클라이언트를 벡터에서 삭제
                  v.remove(i--);

// e.printStackTrace();
System.out.println("[#클라이언트가 접속을 끊었습니다.]");
}
}
}//messageAll

  } //내부 클래스 end
  //===============================================

  public static void main(String[] args)
  {
      new Server();
  }

}


- ```java
  package j0509;

  import java.awt.Font;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.net.Socket;
  import java.net.UnknownHostException;

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

  public class ChatClient extends JFrame implements ActionListener, Runnable
  {
      //프레임 Center => JTextArea (서버가 보낸 메시지를 출력)
      //프레임 South => JTextField (서버에게 보낼 메시지)

      JTextArea ta;
      JScrollPane scroll_ta;

      JTextField tf_send;
      OutputStream out;//소켓쓰기
      BufferedReader in;//소켓읽기
      JButton bt_change;

      JPanel southp, northp;

      public ChatClient()
      {
          setTitle("대화방");
          ta = new JTextArea();
          scroll_ta = new JScrollPane(ta);
          tf_send = new JTextField(15);
              tf_send.setFont(new Font("굴림체", Font.BOLD, 20));

          bt_change = new JButton("대화명 변경");
          /*
           * 최초 대화명 길동이
           * [길동이]안녕하세요
           * 
           * 에서
           * 
           * <미션>
           * 대화명변경 버튼 클릭시
           *     입력대화상자 보이고
           *     변경 대화명 입력
           *     대화명 변경
           */    


          add("Center", scroll_ta); //JScrollPane필요한 컴포넌트 : JList, JTable, JTextArea
          add("South", tf_send);

          setBounds(300,200,300,600);
          setVisible(true);
          setDefaultCloseOperation(EXIT_ON_CLOSE);
          connectProcess(); //in out 객체 생성
          new Thread(this).start(); // in객체 사용

          String nickname = JOptionPane.showInputDialog(this, "대화명");
          toServer("100|" + nickname);

          tf_send.addActionListener(this);
      }//생성자
      @Override
      public void actionPerformed(ActionEvent e)
      {
          String msg = tf_send.getText();
          toServer("200|"+msg);//"200|안녕하세요??"
          tf_send.setText("");

      }

      public void connectProcess() //서버소켓과 연결하는 작업
      {
          try
          {
              Socket s = new Socket("localhost", 3000); // [3] 서버소켓 연결

              //★4. 소켓 입출력 객체 생성
              out = s.getOutputStream();
              in = new BufferedReader(new InputStreamReader(s.getInputStream())) ;
          } catch (UnknownHostException e)
          {
              // TODO Auto-generated catch block
              e.printStackTrace();
          } catch (IOException e)
          {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
      }
      public void toServer(String msg)//서버에게 메시지 보내기
      {
          try
          {
              out.write((msg + "\n").getBytes()); //[5] 메시지 보내기 writhe
          } catch (IOException e)
          {
              e.printStackTrace();
          }
      }

      @Override
      public void run()
      {//스레드 사용이유? GUI와 상관없이 (동시에) 서버가 보내는 메시지를 대기해야 하기 때문
          try
          {
              while(true)
              {
                      String msg = in.readLine();//[8]
                      ta.append(msg + "\n");
              }
          } catch (IOException e)
          {
              e.printStackTrace();
          }


      }

      public static void main(String[] args)
      {
          new ChatClient();
      }//main



  }
  • //이건 대화명 변경
    package com.encore.j0509;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Vector;
    
    public class Server implements Runnable{
      //외부클래스: 1.소켓을 통한 접속서비스(접속대기)
      //          2.접속한 클라이언트를 관리(Vector통해)
    
       ServerSocket ss;
       Vector<Service>  v; //접속한 클라이언트 관리
    
       public Server() {
          System.out.println("Server Start......");
          v = new Vector<>();
         try {
            ss = new ServerSocket(3000);//★1. 서버소켓 객체생성        
            new Thread(this).start();
         } catch (IOException e) {
            e.printStackTrace();
         }
       }//외부클래스 생성자
    
       public void run() {//여러 클라이언트 접속에 대한 대기
           try {
            while(true) {
                 Socket s = ss.accept();//★2. 클라이언트 접속대기  (클라이언트 프로그램: new Socket()과 매핑)
                 //Service serv = new Service(s);
                 //v.add(serv);
                 v.add(new Service(s));
               }
        } catch (IOException e) {
            e.printStackTrace();
        }
       }//외부 run
    
       //---------------------------------------------------------------------------
          class Service extends Thread{//내부클래스: 소켓을 통한 입출력 서비스
              //※ Service객체 한개 == 클라이언트 한개!!
    
              BufferedReader in;//소켓통해 읽기
              OutputStream out;//소켓에 쓰기
    
              String clientAddr;
              String nickName;
    
              public Service(Socket s) {
    
                 try {
                    //★4 소켓 입출력 객체 생성 
                     in = new BufferedReader(new InputStreamReader(s.getInputStream()));
                     out = s.getOutputStream();
                     clientAddr = s.getInetAddress().getHostAddress();
                     start();
                } catch (IOException e) {
                    e.printStackTrace();
                }             
              }//내부클래스 생성자
    
              public void run() {//클라이언트가 보내는 여러메시지를 읽어주는 기능
    
                 try {
                    while(true) {
                        String msg =in.readLine();//★6. 클라이언트가 tf를 통해 보낸 메시지 읽기
                        //msg ==> "100|홍길동"   "200|안녕하세요"
                        //모니터링
                        System.out.println("["+clientAddr+"]>>>"+ msg);
    
                        String arr[] = msg.split("\\|");
                        //arr={"100","홍길동"}    arr={"200","안녕하세요"}
                        switch(arr[0]) {
                          case "100"://대화명 전달
                                      nickName = arr[1];
                                      break;
                          case "200"://메시지 전달
                              messageAll("["+nickName+"]▶ "+arr[1]);// [홍길동]▶ 안녕하세요
                        }
    
                     }
                } catch (IOException e) {
                    //e.printStackTrace();
                    System.out.println("[#클라이언트 접속 끊음]");
                }
    
              }//내부run
    
              public void messageTo(String msg) throws IOException {
              //특정 클라이언트에게 메시지 보내기
                  out.write(  (msg+"\n").getBytes() );//★7. 실제 클라이언트에게 메시지 보내기
              }//messageTo
    
              public void messageAll(String msg) {
              //접속한 모든 클라이언트에게 메시지 보내기
                  for(int i=0; i<v.size(); i++) {//전체 클라이언트(Service벡터)
                      //i=0길동    1라임      2주원     3유신
    
                      Service serv = v.get(i);
                      try {
                        serv.messageTo(msg);
                      } catch (IOException e) {
                          //에러발생  ----> 클라이언트 접속 끊음!!(소켓은 사라졌지만 Service객체가 벡터에존재)
                          v.remove(i--);//접속끊긴 클라이언트를 벡터에서 삭제!!
                          //e.printStackTrace();
                          System.out.println("[#클라이언트 접속 끊음]");
                      }
                  }//for
    
              }//messageAll
    
    
    }//내부클래스 end
 //---------------------------------------------------------------------------

 public static void main(String[] args) {
    new Server();
 }

}


- | 로 자르고 싶다면 \ \ | 로 잘라야 한다.






















































궁금상자

  • Runnable과 Thread 차이
    Thread클래스 상속받으면 다른 클래스 상속 불가이기에 Runnable인터페이스 구현하는것이 일반적
    Runnable인터페이스 구현하면 재사용성과 일관성 유지의 장점이 있다.
    Runnnable사용시 Thread 선언해줘야 함!

  • 내부클래스 이유?

  • 상속 extends 와 implements

    
    
    

+ Recent posts