2022.08.22/자바 정리/Thread/네트워크

Jimin·2022년 8월 22일
0

비트캠프

목록 보기
26/60
post-thumbnail
  • board-app 프로젝트 수행
      1. Thread를 이용한 멀티 태스킹 구현하기: 동시 요청 처리하기
  • 네트워킹 사용법(com.eomcs.net.*)

board-app ver.40~41: 데이터 처리를 서버로 분리

board-app ver.42: Proxy pattern 적용

  • Stub: Client측 대행자, Client ORB(Object Request Broker)
  • Skeleton: Server측 대행자, Server ORB(Object Request Broker)

시스템 아키텍처의 변화

1. Standalone architecture

코드가 코드를 호출하는 형태

→ 업무가 복잡 ⇒ 동시 다중 사용자 자원이 필요하다.

2. Client/Server Architecture

→ 업무 규모 증가 → 동시 사용자 접속 증가 → 시스템(H/W, S/W, CPU, RAM)규모가 증가한다.
→ 한대의 컴퓨터에 (CPU 개수를 늘리는 것, RAM 용량 증가시키는 것)이 한계에 봉착하게 된다.
⇒ 하나의 서버로 감당 X
→ 효율적 관리를 위하여 시스템을 기능별 분리하게된다. (서버 분리)

3. 분산 시스템 아키텍처


서버를 분리했지만 각 시스템 끼리 서로 사용할 수가 없다.
즉, 시스템 간의 기능 호출이 필요하게 된다.

⇒ 원격의 메서드 호출 기술이 필요하다.

원격 메서드 호출 기술과 진화 과정

1. RPC(Remote Procedure Call)

  • Procedure: Function, Method
  • 원격 메서드를 만드는 개발자에게 부담이 가중된다.
    ⇒ 호출 대행자 생성 자동화
    ⇒ 메서드 생성기: 코드 자동생성
  • C, Pascal, ...

↓ 객체지향 프로그래밍 언어 지원

2. RMI(Remote Method Invokation)

  • RMI: Stub와 Skeleton을 만들어준다.
  • Stub: Client측 대행자, Client ORB(Object Request Broker)
  • Skeleton: Server측 대행자, Server ORB(Object Request Broker)
  • C++, Java
  • 자동생성을 하고 싶다면, 규칙을 따라야 한다.
    rmic

3. RMI: EJB(Enterprise Java Beans)

  • 언어중립 X, only Java만 가능!
  • EJB = Remote Object
  • Beans = Object
  • Enitity Bean
  • session Bean
  • Message-Driven Bean

4. RMI: CORBA

  • Common: 언어 중립 → 언어에 구애 받지 않고 호출할 수 있게 해준다.
    • RMI-IIOP 통신
      - Internet
      - Inter-ORB
      - Protocol
    • Object
    • Request
    • Broker
  • Architecture

5. Web Service

  • EJB/CORBA →웹통신+XML: Web Service
  • Stub 객체 자동 다운로드: 개발도구에서 Stub 객체를 자동으로 다운로드한다.
    ⇒ 개발자가 직접 다운로드 하지 않는다.
    예: Eclipse IDE

6. RESTful API

  • Web Sevice
    • 개발 도구를 사용하여 자동으로 Stub를 다운받더라도 결국 Client 측에서는 Stub객체를 사용해야한다.
    • 또한 프로그래밍 언어에 맞추어 Stub를 다운로드 해야한다.
  • RESTful API
    • 순수 HTTP 기술 사용
    • 즉, 프로그래밍 언어 별로 Stub이 필요가 없다.
    • 프로그래밍 언어에 독립적이라는 것이다.
    • 별도의 기술이 필요하지 않다.


SpringBoot와 REST API


Stateful 통신(Ver.043) vs. Stateless 통신(Ver.044)

Connection-Oriented

1. Stateful 방식 통신:

연결을 끊을 때까지 반복한다. → 한 클라이언트가 서버를 독점 ⇒ 여러 클라이언트가 동시에 작업할 수 없다!

2. Stateless 방식 통신:

요청할 때마다 연결한다.
→ 한 클라이언트의 독점을 완화(완전 독점은 막을 수 없음 → 한 번에 하나의 클라이언트만 연결할 수 있기 때문이다.)
⇒ 짧은 연결을 통해서 여러 클라이언트의 동시 요청 어느 정도 지원


Thread를 이용한 멀티 태스킹 구현하기: 동시 요청 처리 구현

  • Thread로 멀티 태스킹을 구현하는 방법
  • 멀티 태스킹의 이해
  • 멀티 프로세스와 멀티 스레드의 이해 구동 원리

원리: 실행 흐름을 분리

  • serverApp: 프로세스
    • main 스레드 실행: main(){-}
      • RequestThread: run(){-}
      • RequestThread: run(){-}
      • RequestThread: run(){-}
        ...
        (while(ture)로 무한 반복)

board-app project

1단계 - 클라이언트 요청을 별도의 실행흐름으로 처리할 스레드를 만든다.

  • board.RequestThread 클래스 생성
    • ServerApp 클래스에서 생성하던 Socket이나 Stream을 RequestThread 클래스에서 만들어준다.
    • run() 메소드를 만들어준다.
    • run() 메소드에서 전역으로 이미 Socket 변수를 만들어 줬는데 또 생성해주는 이유?
      → try() 괄호 안에서 자동으로 close() 시키기 위함
  • RequestThread class
package com.bitcamp.board;

// 클라이언트 요청을 main 실행 흐름과 분리하여 별도의 실행으로 다루는 클래스
public class RequestThread extends Thread{
  private Socket socket;
  private Map<String, Servlet> servletMap;

  public RequestThread(Socket socket, Map<String, Servlet> servletMap) {
    this.socket = socket;
    this.servletMap = servletMap;
  }

  // 별도의 실행흐름에서 수행할 작업 정의
  @Override
  public void run() {
    try (Socket socket = this.socket; // 위에 있는데 또 선언하는 이유: () 하는 곳 안에서만 자동으로 Close 되기 때문.
        // socekt을 가지고 입출력 stream 얻기
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {

      System.out.println("클라이언트와 연결 되었음!");

      // 클라이언트와 서버 사이에 정해진 규칙(protocol)에 따라 데이터를 주고 받는다.
      String dataName = in.readUTF();

      Servlet servlet = servletMap.get(dataName);
      if (servlet != null) {
        servlet.service(in, out);
      } else {
        out.writeUTF("fail");
      }

      System.out.println("클라이언트와 연결을 끊었음!");
    } // 안쪽 try
    catch(Exception e) {
      System.out.println("클라이언트 여청 중 오류 발생!");
      e.printStackTrace();
    }
    super.run();
  }//run()

}

2단계 - 스레드를 통해 클라이언트 요청을 처리한다.

  • board.ServerApp 클래스 변경
    • 만들어둔 Thread인 RequestThread 객체를 만들어서 start() 한다.
    • 이제 여러 클라이언트가 서버에 접속하도록 할 수 있다.
  • ServerApp class
package com.bitcamp.board;

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

    try (ServerSocket serverSocket = new ServerSocket(8888);) {
	...
      while (true) {
        // 클라이언트가 연결되면,
        Socket socket = serverSocket.accept();

        // 클라이언트 요청을 처리할 스레드를 만든다.
        RequestThread t =new RequestThread(socket, servletMap); //servletMap은 RequestThread로 넘겨서 거기서 처리하도록 맡긴다.

        // main 실행 흐름에서 분리하여 별도의 실행 흐름으로 작업을 수행시킨다.
        t.start(); // 여러개 실행
      }
    } catch (Exception e) {
      e.printStackTrace();
    } // 바깥 쪽 try 

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

IP 주소

각각의 컴퓨터에 부여된 고유 번호
하지만 사람이 식별하기 어렵기 때문에 1byte씩 잘라서 10진수로 표기해서 사용한다.

  • v4 → 32bit 양의 정수 값
    예) C0A8004A(16진수) → 3,232,235,594(10진수) → 192.168.0.74
    (표현하기 쉽게 1byte씩 잘라서 10진수로 표기한다. 0~255 정수 나열)
    → 기억하기 쉽게 이름을 부여
    예: localhost

IP 주소를 직접 사용하는 대신에, IP주소에 부여된 알파벳과 숫자로된 텍스트 이름을 사용하는 것이 편하다.

DNS서버: IP 주소에 대한 텍스트 이름을 알려주는 서버
(DNS: Domain Name System)

서버이름IP 주소
네이버 서버www.naver.com223.10.200.104
카카오 서버www.daum.net211.249.220.24

Q. www.naver.com을 검색했을 때의 과정을 말해보시오

나:
www.naver.com 검색 → KT DNS → DNS naver.com 검색 서버 알려줌
→ Naver DNS: IP 주소를 가지고 있다가 알려준다, www 서버 IP 를 나에게 돌려준다.
→ 나 → www 요청 → www 응답 → 나

DNS → Naver DNS 서버 → www, shop, news, ... ip 주소를 가지고 있음
즉, DNS는 자세한 건 모르고 굵직한 DNS 서버를 알고 있다.


Server: 상대편으로부터 연결 요청 받기

  1. 다른 컴퓨터의 연결 요청을 기다린다.
ServerSocket ss = new ServerSocket(포트번호);
  • 포트번호:
    • 호스트에서 실행 중인 서버 프로그램을 구분하는 번호이다.
    • 1024 ~ 49151 사이의 값 사용한다.
    • 1 ~ 1023 사이의 포트 번호는 특정 서버가 사용하기 위해 미리 예약된 번호다.
      가능한 이 범위의 포트 번호는 사용하지 않는 것이 좋다.
    • 유명 프로그램의 포트 번호도 가능한 사용하지 말라.
      예) Oracle DBMS(1521), MySQL DBMS(3306) 등
    • 같은 컴퓨터에서 다른 프로그램이 이미 사용중인 포트 번호는 지정할 수 없다.
    • 포트 번호는 중복으로 사용될 수 없다.
  1. 연결을 기다리고 있는 클라이언트가 있다면 맨 먼저 접속한 클라이언트의 연결을 승인한다.

    • 클라이언트가 서버에 접속을 요청하면 그 정보를 "대기열"이라고 불리는 목록으로 관리한다.
    • accept()를 호출하면 대기열에서 순서대로 꺼내 해당 클라이언트와 연결된 소켓을 만든다.
  2. 소켓 객체를 통해 읽고 쓸 수 있도록 입출력 스트림을 얻는다.

    • 연결된 클라이언트로 데이터를 보내고 받으려면 입출력 스트림을 꺼내야 한다.
    • 소켓이 리턴해준 입출력 스트림에 적절한 데코레이터를 붙여서 사용한다.
  3. 상대편이 보낸 문자열을 한 줄 읽는다.

    • 상대편이 한 줄의 데이터를 보낼 때까지 리턴하지 않는다.
    • 이런 메서드를 블로킹 메서드라 부른다.
  4. 상대편으로 문자열을 한 줄 보낸다.

  5. 항상 입출력 도구는 사용 후 닫아야 한다.

  6. 네트워크 연결도 닫는다.


Client: 상대편에 연결을 요청하기

Socket s = new Socket(원격 호스트의 IP 주소/도메인이름, 원격 호스트 프로그램의 포트번호)
  1. 다른 컴퓨터와 네트워크로 연결한다.
    • 서버와 연결되면 Socket 객체가 생성된다.
    • 서버와 연결될 때까지 리턴하지 않는다.
    • 서버에 연결할 수 없으면 예외가 발생한다.
    • 기본으로 설정된 타임아웃 시간까지 연결되지 않으면 예외가 발생한다.
    • 로컬 호스트(애플리케이션을 실행하는 현재 컴퓨터)일 경우의 IP 주소: 127.0.0.1 또는 localhost
  2. 소켓 객체를 통해 읽고 쓸 수 있도록 입출력 스트림을 얻는다.
    (스트림: 데이터 입출력 통로)
  3. 상대편으로 문자열을 한 줄 보낸다.
  4. 상대편에서 보낸 문자열을 한 줄 읽는다.
    • 상대편이 한 줄 데이터를 보낼 때까지 리턴하지 않는다.
    • 이런 메서드를 블로킹 메서드라 부른다.
  5. 항상 입출력 도구는 사용 후 닫아야 한다.
  6. 네트워크 연결도 닫는다.

Server ↔ Client 입력 문자열 주고 받기

Receiver(Server)

public class Receiver3 {

  public static void main(String[] args) throws Exception {
    System.out.println("미니쥐 서버 실행!");

    Scanner keyScan = new Scanner(System.in);

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    while (true) {
      // 클라이언트가 보낸 문자열을 수신한다.
      String str = in.nextLine();
      System.out.println(str);

      // 키보드 입력을 받아서 클라이언트로 송신한다.
      System.out.print("입력> ");
      String input = keyScan.nextLine();
      out.println(input);

      if (str.equals("quit"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    keyScan.close();
  }

}

Sender(Client)

public class Sender3 {

  public static void main(String[] args) throws Exception {
    System.out.println("클라이언트 실행!");

    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("localhost", 8888);

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    while (true) {
      // 키보드 입력을 받아서 서버에게 전송한다.
      System.out.print("입력> ");
      String input = keyScan.nextLine();
      out.println(input);

      // 서버가 보낸 데이터를 수신한다.
      String str = in.nextLine();
      System.out.println(str);

      if (input.equals("quit"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }

}

Server ↔ Client 파일 주고 받기

상대 서버에 내 파일을 업로드하는 로직이다.
Buffer(BufferedInputStream, BufferedOutputStream)를 이용해야 확실히 전송 및 수신 속도가 빨라진다.

Receiver(Server)

public class Receiver5 {

  public static void main(String[] args) throws Exception {
    System.out.println("미니쥐 서버 실행 중...");

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();
    System.out.println("클라이언트가 연결됨.");

    PrintStream out = new PrintStream(
        new BufferedOutputStream(socket.getOutputStream()));
    DataInputStream in = new DataInputStream(
        new BufferedInputStream(socket.getInputStream()));

    System.out.println("클라이언트로부터 데이터 수신 중...");

    //1) 파일 크기 읽기
    long filesize = in.readLong();

    //2) 파일 이름 읽기
    String filename = in.readUTF();

    //3) 파일 데이터 읽기
    File file = new File("temp/ok_라" + filename);
    BufferedOutputStream fileOut = new BufferedOutputStream( 
        new FileOutputStream(file));

    for (long i = 0; i < filesize; i++) {
      fileOut.write(in.read());
    }
    System.out.println("클라이언트로부터 데이터 수신 완료!");

    //4) 클라이언트에게 응답하기
    out.println("OK!");
    out.flush(); // 버퍼의 남아있는 데이터 방출하기

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    fileOut.close();
  }

}

Sender(Client)

public class Sender5 {

  public static void main(String[] args) throws Exception {
    File file = new File("temp/cuties.jpeg");

    BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(file));

    System.out.println("미니쥐가 서버에 연결 중...");
    Socket socket = new Socket("localhost", 8888);
    System.out.println("서버에 연결 완료!");

    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
    Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()));

    System.out.println("서버에 데이터 송신 중...");

    long startTime = System.currentTimeMillis();

    // 1) 파일 크기 보내기
    out.writeLong(file.length());

    // 2) 파일 이름 보내기
    out.writeUTF(file.getName());

    // 3) 파일 데이터 보내기
    int b;
    while ((b = fileIn.read()) != -1) {
      out.write(b);
    }
    out.flush(); // 버퍼에 남아있는 데이터를 방출하기

    long endTime = System.currentTimeMillis();

    System.out.printf("서버에 데이터 송신 완료!(%d밀리초)\n", endTime - startTime);

    // 4) 서버의 응답받기
    String response = in.nextLine();
    System.out.println(response);

    in.close();
    out.close();
    socket.close();
    fileIn.close();
  }

}
profile
https://github.com/Dingadung

0개의 댓글