2022.08.16/자바 정리/네트워크/Socket/IP Address/Port number

Jimin·2022년 8월 16일
0

비트캠프

목록 보기
22/60
post-thumbnail
  • board-app 프로젝트 수행
      1. 네트워킹을 이용한 파일 공유: client/server app. 아키텍처로 전환(계속)
  • 네트워크 프로그래밍 개요
    • ServerSocket, Socket 클래스 사용법
    • IP 주소와 포트 번호

네트워킹을 이용한 파일 공유: Client&Server Application Architecture

  • 현재 내 컴퓨터의 IP Address 알아내기(mac 기준)
    → cmd: ifconfig -a

Socket 생성

  • java.net
new Socket("localhost", 8888);
new Socket("127.0.0.1", 8888);

new Socket("접속하려는 상대편의 네트워크 주소, IP Address", 포트번호(port number));

  • 주소
    • 도메인명: www.naver.com
    • IP 주소: 123.4.12.75
    • localhost: 127.0.0.1, local 컴퓨터를 가르키는 도메인 명과 IP 주소
  • 포트 번호 = 식별 번호
  • 같은 컴퓨터 내에 서버와 클라이언트가 존재한다면 실제 IP Address 대신, host IP Address를 사용할 수 있다.
// 네트워크 준비
    try {
      Socket socket = new Socket("127.0.0.1", 8888 ); 
      // 같은 컴퓨터 내에 서버와 클라이언트가 존재하므로 정확한 IP address 대신, 127.0.0.1(local host)이 가능하다. 
      // (접속할 IP address, port num)
    } catch (Exception e) {
      e.printStackTrace();
    }

IP Address

IP Address: 네트워크 상에서의 컴퓨터 식별 번호

= Ram 카드에 부여된 번호= 지구에서 유일한 값 = 국제 기구에서 번호 관리

  • 램이 여러개 꽂혀 있을 경우, 한 컴퓨터에 여러개의 IP Address를 지정할 수 있다.
  • IPv4: 203.4.5.75
    • 각각은 0~255까지 가능하다. → 8 비트이기 때문이다.
    • 32buts = 4byte
    • 42억대의 장치에 대해 IP 주소를 부여해야하는데, 4byte는 부족하다.

Port 번호

Port 번호: Application 식별 번호


연결 기다림: 서버 소켓

  • clientApp class
    • server와 정상적으로 연결되었으면, Socket 객체를 리턴한다.
    • server와 client가 서로를 식별하여 연결되기 위한 port number는 8888로 정한다.
    • 같은 컴퓨터 내에 server와 client가 모두 있기에 로컬 호스트 주소를 적는다.
package com.bitcamp.board;

import java.net.Socket;

public class ClientApp {

  public static void main(String[] args) {
    System.out.println("[게시글 관리 클라이언트]");

    // 네트워크 준비
    //=> 정상적으로 연결되었으면, Socket 객체를 리턴한다.
    try {
      Socket socket = new Socket("127.0.0.1", 8888 ); 

      System.out.println("연결되었음!");

      // 네트워크 끊기
      // => 서버와 연결된 것을 끊는다.
      socket.close();
      System.out.println("연결을 끊었음!");
    } catch (Exception e) {
      e.printStackTrace();
    }//try-catch()

    System.out.println("종료");
  }//main()

}
  • serverApp class
    • 클라이언트 연결을 관리할 객체를 준비한다.
      • ServerSocket의 port num 또한 8888로 설정한다.
    • client에서 응답이 올 때까지 기다린다.
    • client에서 응답이 오면 accept()을 통해 socket 객체를 받는다.
package com.bitcamp.board;

import java.net.ServerSocket;
import java.net.Socket;

public class ServerApp {

  public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println("[게시글 데이터 관리 서버]");

    try {
      // 네트워크 준비
      // => 클라이언트 연결을 관리할 객체를 준비한다.
      ServerSocket serverSocket = new ServerSocket(8888);
      System.out.println("서버 소켓 준비 완료!");

      // 클라이언트의 연결을 기다림
      // => 클라이언트와 연결되면 그 클라이언트와 통신할 준비를 한다.
      // 즉, Socket 객체 리턴
      Socket socket = serverSocket.accept();
      System.out.println(" 클라이언트와 연결 되었음!");

      // 클라이언트와 연결된 것을 끊는다.
      // => 클라이언트와 연결될 때까지 리턴하지 않는다.
      socket.close();
      System.out.println("클라이언트와 연결 되었음!");

      // 네트워크 종료
      // => 더 이상 클라이언트와 연결하고 싶지 않다면 네트워크를 종료한다.
      serverSocket.close();
    } catch (Exception e) {
      e.printStackTrace();
    }//try-catch()
    System.out.println("서버 종료!");
  }//main()

}
  • app-server를 실행시키면, app-client가 응답할 때까지 기다린다.
  • app-client가 응답을 보내면 app-server가 소켓을 받고 연결을 마친다.

Socket을 통해 데이터 입출력


Socket을 통해 데이터 입출력 + 데코레이터

Decorator: I/O 객체에 덧붙여서 기능을 확장시킨다.


InputStreamReader

  • byte -> char 으로 변환(UTF-16)
  • 내가 원하는 건 String인데 b는 그냥 byte임
    ⇒ 그래서 InputStreamReader라는 도구를 연결한다.

  • 사람으로 비유해서 생각하기!
  • inputStream이 byte를 전달해준다. → read() 두 번씩
    • 한글일경우, read() 세 번 진행
  • InputStreamReader
    • \n(엔터)나올때까지 read()한다.
    • byte → char로 변환
    • \n나오면 BufferedReader에 변환시킨 문자열을 리턴한다.

  • HTTPClient class
    • auction 사이트의 메인 웹 페이지의 정보를 요청하여, 가져올 수 있다.
package com.bitcamp.board;

import java.net.Socket;

public class HTTPClient {
  public static void main(String[] args) throws Exception {
    try(
        Socket socket = new Socket("www.auction.co.kr", 80); // web server port number: 80
        BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream()));
        PrintStream out = new PrintStream(socket.getOutputStream());){

      // HTTP 프로토콜에 따라서 메인 웹 페이지를 요청한다.
      out.println("GET / HTTP/1.1"); // root 에 있는 메인 페이지를원한다.
      out.println("Host: www.auction.co.kr");
      out.println(); // 요청에 대한 정보 끝

      // 웹서버의 응답을 출력한다.
      String line;
      while((line = in.readLine()) != null) {
        System.out.println(line);
      }
    }
  }
}
  • HTTPServer class
    • http://127.0.0.1/ 이 다음은 내가 원하는 옵션인데,
      이 서버단에서 설정을 안해줘서 어떠한 옵션을 넣어주더라도, 그냥 계속 안녕하세요!만 출력한다.
    • portnum 80만 입력 → 127.0.0.1로 웹에서 접속 가능
    • 즉, 이 경우에서는 웹이 클라이언트이다!
package com.bitcamp.study;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class HTTPServer {

  public static void main(String[] args) throws Exception{
    try(ServerSocket ss = new ServerSocket(80);){ // http://127.0.0.1/ 이 다음은 내가 원하는 옵션인데 이 서버단에서 설정을 안해줘서 그냥 계속 안녕하세요!만 출력한다.
      System.out.println("서버 시작!!!!!!");
      while(true) {

        try(
            Socket socket = ss.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintStream out = new PrintStream(socket.getOutputStream());
            ){//try()
          System.out.println("클라이언트가 연결됨!!!!!!!");
          // 클라이언트가 보낸 데이터를 읽는다.
          System.out.println("----------------------------");
          String line;
          while((line = in.readLine())!=null) {
            if(line.length() == 0) { // 클라이언트가 빈 줄을 보내면, 읽기를 끝낸다.
              break;
            }
            System.out.println(line);
          }

          // 클라이언트에게 응답한다.
          out.println("HTTP/1.1 200 OK");
          out.println("Content-Type: text/html;charset=UTF-8"); // content의 정보
          out.println(); // 이제부터 본격적으로 콘텐트를 보내겠다고 알린다.
          out.println("<html>");
          out.println("<head>");
          out.println("<title>Hello!</title>");
          out.println("</head>");
          out.println("<body>");
          out.println("<h1>안녕하세요!</h1>");
          out.println("</body>");
          out.println("</html>");
        }// try()
      }// while
    }// try()
  }//main()
}//class


board-app project

board-app-client와 board-app-server의 통신 protocol



  • BoardHandler class
    • 여기서 서버로 command 명령어를 전송한다.
/*
 * 게시글 메뉴 처리 클래스
 */
package com.bitcamp.board.handler;


public class BoardHandler extends AbstractHandler {

  // 게시글 목록을 관리할 객체 준비
  private BoardDao boardDao;

  String dataName;
  DataInputStream in;
  DataOutputStream out;

  public BoardHandler(String dataName, DataInputStream in, DataOutputStream out){
    // 수퍼 클래스의 생성자를 호출할 때 메뉴 목록을 전달한다.
    super(new String[] {"목록", "상세보기", "등록", "삭제", "변경"});

    this.dataName = dataName;
    this.in = in;
    this.out =out;

    boardDao = new BoardDao(dataName);
    // 이제 로드는 서버애서!

  }

  @Override
  public void service(int menuNo) { 
    try {
      switch (menuNo) {
        case 1: this.onList(); break;
        case 2: this.onDetail(); break;
        case 3: this.onInput(); break;
        case 4: this.onDelete(); break;
        case 5: this.onUpdate(); break;
      }
    }catch(Exception e) {
      throw new RuntimeException(e); // 
    }
  }

  private void onList() {
    try {
      out.writeUTF(dataName);
      out.writeUTF("findAll");
      System.out.println(in.readUTF());
    }catch(Exception e) {
      throw new RuntimeException(e);
    }

  }

  private void onDetail() {

    try {
      out.writeUTF(dataName);
      out.writeUTF("findByNumber");
      System.out.println(in.readUTF());
    }catch(Exception e) {
      throw new RuntimeException(e);
    }

  }

  private void onInput() throws Exception{

    try {
      out.writeUTF(dataName);
      out.writeUTF("insert");
      System.out.println(in.readUTF());
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
    
  }

  private void onDelete() throws Exception{

    try {
      out.writeUTF(dataName);
      out.writeUTF("delete");
      System.out.println(in.readUTF());
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
    
  }

  private void onUpdate() throws Exception{
    try {
      out.writeUTF(dataName);
      out.writeUTF("update");
      System.out.println(in.readUTF());
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
    
  }
}
  • ClientApp class
    • 0을 입력했을 경우, exit을 출력하여 프로그램 종료
    • 그 밖의 경우, 해당 번호에 맞는 handler를 실행한다.
public class ClientApp {

  //breadcrumb 메뉴를 저장할 스택을 준비
  public static Stack<String> breadcrumbMenu = new Stack<>();

  public static void main(String[] args) {
    System.out.println("[게시글 관리 클라이언트]");

    // 네트워크 준비
    //=> 정상적으로 연결되었으면, Socket 객체를 리턴한다.
    try (Socket socket = new Socket("127.0.0.1", 8888 ); 
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream());){

      System.out.println("연결되었음!");

      welcome();


      // 핸들러를 담을 레퍼런스 배열을 준비한다.
      Handler[] handlers = new Handler[] { // 파일명을 목적에 맞게 각각 전달
          new BoardHandler("board", in, out), // 게시판
          new BoardHandler("reading", in, out), // 독서록
          new BoardHandler("visit", in, out), // 방명록
          new BoardHandler("notice", in, out), // 공지사항
          new BoardHandler("daily", in, out), // 일기장
          //new MemberHandler("member", in, out) // 회원
      };

      // "메인" 메뉴의 이름을 스택에 등록한다.
      breadcrumbMenu.push("메인");

      // 메뉴명을 저장할 배열을 준비한다.
      String[] menus = {"게시판", "독서록", "방명록", "공지사항", "일기장", "회원"};

      loop: 
        while (true) {

          // 메인 메뉴 출력
          printTitle();

          printMenus(menus);

          System.out.println();

          try {
            int mainMenuNo = Prompt.inputInt("메뉴를 선택하세요[1..6](0: 종료) ");

            if (mainMenuNo < 0 || mainMenuNo > menus.length) {
              System.out.println("메뉴 번호가 옳지 않습니다!");
              continue; // while 문의 조건 검사로 보낸다.

            } else if (mainMenuNo == 0) {
              out.writeUTF("exit"); // client가 서버와의 연결에서  나가겠다고 하는 순간. // out은 서버로 보내는 것이다.
              break loop;
            }

            // 메뉴에 진입할 때 breadcrumb 메뉴바에 그 메뉴를 등록한다.
            breadcrumbMenu.push(menus[mainMenuNo - 1]);

            // 메뉴 번호로 Handler 레퍼런스에 들어있는 객체를 찾아 실행한다.
            handlers[mainMenuNo - 1].execute();

            breadcrumbMenu.pop();

          } catch (Exception ex) {
            System.out.println("입력 값이 옳지 않습니다.");
          }


        } // while

      Prompt.close();

      // 네트워크 끊기
      // => 서버와 연결된 것을 끊는다.
      //socket.close();
      System.out.println("연결을 끊었음!");
    } catch (Exception e) {
      e.printStackTrace();
    }//try-catch()

    System.out.println("종료!\n");
  }//main()


  static void welcome() {
    System.out.println("[게시판 애플리케이션]");
    System.out.println();
    System.out.println("환영합니다!");
    System.out.println();
  }

  static void printMenus(String[] menus) {
    for (int i = 0; i < menus.length; i++) {
      System.out.printf("  %d: %s\n", i + 1, menus[i]);
    }
  }

  protected static void printTitle() {
    StringBuilder builder = new StringBuilder();
    for(String title: breadcrumbMenu) {
      if(!builder.isEmpty()) {
        builder.append(" > ");
      }
      builder.append(title);
    }
    System.out.printf("%s:\n", builder.toString());
  }
}
  • board-app-server pkg에서 handler를 모두 servlet으로 수정하기
  • BoardServlet class
/*
 * board 데이터 처리
 */
package com.bitcamp.board.servlet;
public class BoardServlet implements Servlet{

  // 게시글 목록을 관리할 객체 준비
  private BoardDao boardDao;

  public BoardServlet(String dataName){
  
  }

  @Override
  public void service(DataInputStream in, DataOutputStream out) {
    try {

      String command = in.readUTF();

      switch (command) {
        case "findAll": 
          out.writeUTF("success");
          break;
        case "findByNumber":
          out.writeUTF("success");
          break;
        case "insert": 
          out.writeUTF("success");
          break;
        case "update":
          out.writeUTF("success");
          break;
        case "delete": 
          out.writeUTF("success");
          break;
        default:
          out.writeUTF("fail");
      }
    }catch(Exception e) {
      throw new RuntimeException(e); // 
    }
  }
}
  • ServerApp class
    • 클라이언트와 데이터를 주고 받고 싶은데, socket이 주는 데이터는 byte로만 전달한다.
      → 부품, dataInputStream을 장착한다.
      ⇒ 즉, 데이터를 읽을 때 primitive type 또는 String 타입의 값을 보다 손쉽게 읽을 수 있도록 기존의 입력 도구에 보조 도구(decorator)를 붙여서 사용한다.
    • DataInputStrean 안에 File을 넘기면 File을, socket을 넘기면 socket을 알아서 읽어준다.
    • 클라이언트와 서버 사이에 정해진 규칙(protocol)에 따라 데이터를 주고 받는다.
package com.bitcamp.board;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.bitcamp.board.servlet.BoardServlet;

public class ServerApp {

  public static void main(String[] args) {
    System.out.println("[게시글 데이터 관리 서버]");

    try (
        // 네트워크 준비(TCP 방식)
        // => 클라이언트 연결을 관리할 객체를 준비한다.
        ServerSocket serverSocket = new ServerSocket(8888);)
    {
      System.out.println("서버 소켓 준비 완료!");

      try(
          // 클라이언트의 연결을 기다림
          // => 클라이언트와 연결되면 그 클라이언트와 통신할 준비를 한다.
          // 즉, Socket 객체 리턴 (연결 -> socket 객체 받기)
          Socket socket = serverSocket.accept(); //처음에 여기서 소켓 받을 때까지 기다린다.


          // 클라이언트와 데이터를 주고 받는다.
          // 클라이언트가 보낸 데이터를 읽을 때 사용할 도구를 준비한다.
          //InputStream in = socket.getInputStream(); → 곧바로 ()안에 넣어 준다.

          // 클라이언트와 데이터를 주고 받고 싶은데, socket이 주는 데이터는 byte로만 전달한다. => 부품, dataInputStream을 장착한다.
          // => 즉,  데이터를 읽을 때 primitive type 또는 String 타입의 값을 보다 손쉽게 읽을 수 있도록 기존의 입력 도구에 보조 도구(decorator)를 붙여서 사용한다. 
          DataInputStream in = new DataInputStream( socket.getInputStream()); 

          // => 클라이언트로 데이터를 보낼 때 사용할 도구를 준비한다.
          //OutputStream out = socket.getOutputStream();

          // => 즉,  데이터를 출력할 때 primitive type 또는 String 타입의 값을 보다 손쉽게 출력할 수 있도록 기존의 입력 도구에 보조 도구(decorator)를 붙여서 사용한다. 
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
          )//try()
      {
        System.out.println(" 클라이언트와 연결 되었음!");

        // 클라이언트 요청을 처리할 객체 준비
        BoardServlet boardServlet = new BoardServlet("board");
        BoardServlet readingServlet = new BoardServlet("reading");
        BoardServlet visitServlet = new BoardServlet("visit");
        BoardServlet noticeServlet = new BoardServlet("notice");
        BoardServlet dailyServlet = new BoardServlet("daily");

        while(true) {
          // 클라이언트와 서버 사이에 정해진 규칙(protocol)에 따라 데이터를 주고 받는다.
          //
          // 데이터 명 읽기
          // 클라이언트가 문자열 보낼때 까지 기다린다. 
          // read는 내가 원하는 값이 들어올 때까지 기다린다. 내가 원할 때 읽는 것 X.
          String dataName = in.readUTF();

          if(dataName.equals("exit")) { // client에서 exit할때까지 반복
            break;
          }

          switch(dataName) {
            case "board":  boardServlet.service(in, out); break;
            case "reading": readingServlet.service(in, out); break;
            case "visit": visitServlet.service(in, out); break;
            case "notice": noticeServlet.service(in, out); break;
            case "daily": dailyServlet.service(in, out); break;
            default:
              out.writeUTF("fail");
          }
        }

        // 클라이언트와 연결된 것을 끊는다.
        // => 클라이언트와 연결될 때까지 리턴하지 않는다.
        System.out.println("클라이언트와 연결을 끊었음!");
        // 네트워크 종료
      }// 안쪽 try

    }catch (Exception e) {
      e.printStackTrace();
    }// 바깥쪽 try-catch()

    System.out.println("서버 종료!");
  }//main()
}

중간 과정



결과



profile
https://github.com/Dingadung

0개의 댓글