com.eomcs.net.ex05.Server0110.java

연결 지향 (connection-oriented)

TCP 통신

연결이 이루어진 다음에 한 번 요청하고 한 번 응답 받고 끝낼 건지 (stateless)
연결을 끊기 전까지 계속 데이터를 주고 받을 건지 (stateful)
둘 다 일단 연결된 다음에 데이터를 주고 받는다.

소켓을 사용하는 게 전형적인 connection-oriented 방법

Connection-oriented

06-네트워킹_스레딩 / 17 페이지

connection-oriented와 connectionless는 사용하는 클래스가 다름

stateful과 stateless는 같은 클래스를 쓰는데 동작에 차이가 있다

① Stateful
연결을 끊을 때까지 데이터 송신
서버로부터 데이터를 수신하고
Client ---요청---> Server
Client <---응답--- Server
연결 끊기
예) FTP, Telnet, Chat, 게임

② Stateless
Client ---연결---> Server
Client ---요청---> Server
Client <---응답--- Server
서버에서 응답 받으면 즉시 연결을 끊는다
다시 서버에 요청하려면 다시 연결
다시 연결하니까 클라이언트가 전에 누구였는지 알 길이 없음
예) HTTP, 메신저 (채팅이 아니라 메신저)

com.eomcs.net.ex05

서버 ServerSocket 연결을 기다린다
클라이언트 대기열에 등록된다
서버쪽에서 대기열에 등록된 클라이언트 중에서 제일 먼저 등록된
소켓을 만들고 그 소켓과 입출력을 하기 위해서 입출력 스트림을 준비하고
데이터를 주고 받는다

클라이언트
일단 서버와 연결돼야 한다
서버와 연결이 안 되면 Socket 객체는 생성되지 않는다
일단 서버에 연결이 되어야 소켓 객체가 생성됨
서버에 연결된 후 입출력
연결이 이루어진 후 데이터 입출력을 할 수 있다.

클라이언트가 접속하더라도 대기열에 등록된 상태지 아직 처리하진 않음

java -Dfile.encoding=UTF-8 -cp bin/main com.eomcs.net.ex05.Client0110

서버에 연결이 됐고 데이터를 보냈다

accept를 하지 않아도 서버와 연결이 된다
서버랑 연결된 클라이언트 정보는 대기열에 등록된다
(클라이언트 연결 정보를 대기열에 순서대로)
이게 연결된 상태

그리고 입출력을 위한 스트림 객체가 준비된 후
서버에 데이터를 보낸다

서버에서 클라이언트 승인도 안 했는데
어떻게 클라이언트가 서버에 데이터를 보낼 수 있습니까?
06-네트워킹_스레딩 / 7 페이지 참고
출력하게 되면 서버에 바로 보내는 게 아니라
랜카드 메모리에 데이터를 넣는다
단, 서버에서 승인을 안 했기 때문에
메모리에 있는 값이 서버에 보내질 순 없는 거

서버가 클라이언트를 accept 하지 않았는데 어떻게 서버에 데이터를 보냅니까?
랜카드 메모리에 보낸 거

엔터를 치면 대기열에서 기다리고 있는 클라이언트의 소캣을 생성한다.>
엔터를 치면 드디어 accept가 호출되고 accept() : Socket
accept가 호출되면 대기열에 기다리고 있는 클라이언트가 있을 경우
그 클라이언트 정보를 꺼내서 소켓을 생성해서 리턴 new Socket
소켓이 생성되면 그 소켓을 통해서 입출력 스트림을 얻어서
socket.getInputStream() socket.getOutputStream()
클라이언트가 보낸 문자열을 한 줄 읽는다 in.nextLine()

accept가 되는 순간 클라이언트가 보낸 문자열이 올 것이고 그걸 읽는다
읽은 걸 클라이언트에 다시 출력

Connection-oriented : 연결된 이후에 데이터 송수신이 일어난다.

com.eomcs.net.ex05.Server0210.java

com.eomcs.net.ex05.Client0210.java

Connectionless

DatagramSocket, DatagramPacket을 사용하여 처리한다.
사용하는 클래스가 달라짐

모니터링 프로그램에서 많이 사용한다.

DatagramSocket socket = new DatagramSocket();
서버주소랑 포트번호 지정 안 함

DatagramSocket 데이터를 던지는 일을 하는 도구

보낼 데이터를 바이트 배열로 준비
new String("Hello") : Heap에 String 객체 생성
"Hello" : constant pool에 String 객체 생성
아래거 쓰기

굳이 두 줄로 하지 말고 한 줄로 쓰기
byte[] bytes = "Hello".getBytes("UTF-8");
constant pool에 String 객체 생성하는 건데
String 클래스의 인스턴스를 만드는 거
중간에 이상하게 보이겠지만

상대편에 String 못 보냅니까?
DatagramSocket을 통해서 데이터를 보낼 때는

Connectionless

06-네트워킹_스레딩 / 18 페이지

Connectionless는 DatagramSocket을 통해서 데이터를 보낸다
DatagramSocket을 보낼 때 데이터를 DatagramPacket 이라는 객체에 담는다
바이트 배열 byte[]에 Data를 담아서
DatagramPacket에 byte[] Data를 담아서
DatagramPacket을 send()를 호출해서 데이터를 전송한다

DatagramPacket(byte[] buf, int length, InetAddress address, int port)

Data(byte[]) + 받는 이의 주소 + 포트번호 바이트 배열에 데이터를 담아서
      ↓ 담는다
DatagramPacket 이라는 객체에 데이터를 담는다
      ↓ send()
DatagramSocket ------전송------> DatagramSocket
                                      ↓ receive()
                                DatagramPacket
                                      ↓ 포함
                                     byte[]

InetAdress는 안타깝게도
기본 생성자는 있는데
기본 생성자의 접근 범위 default
default
생성자를 호출할 수 있다.

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/InetAddress.html

Static Methods 탭 보기
팩토리 메서드가 있음

서버는 포트 번호 적음
받는 쪽에서는 소켓을 생성할 때 포트번호를 설정한다.
자기가 사용할 번호

받은 데이터를 저장할 버퍼 준비

상대편이 보낸 데이터를 패킷 객체의 바이트 배열에 저장한다.
바이트 배열이 넉넉해야 됨.

0번째부터 꺼내라

서버가 떠있으면 받겠지만
다 버려진다.
이미 버려진 데이터는 서버에서 못 받음
서버가 떠있는 동안만 받는다.
이게 바로 connectionless 방식

경로를 route
경로 안내 router

TCP 프로토콜의 문제가 있으니

HTTP2까지는 TCP 사용. Socket 클래스 사용

UDP(User Datagram Protocol)

handshake

chromium
Chrome, Edge, whale

UDP는 HTTP3에서 사용된다.

프로토콜과 Application

06-네트워킹_스레딩 / 19 페이지

프로토콜 : 데이터를 주고 받는 규칙

FTP

SMTP

Telnet

HTTP

HTTP Client (Web Browser) <----> HTTP Server (Web Server)
  curl(compact url) (콘솔용)               Apache
       wget (콘솔용)
         chrome
         safari
         firefox
         Edge
         Opera
         Whale

클라이언트가 빈 줄을 보낼 때까지 읽는다

JavaScript와 ECMAScript

스펙에 따라서 실제 작성하고 돌아가도록 만든 게 자바스크립트

1.3(1998.10) ← ECMA Script의 제품으로서 역할

http specification 검색

https://datatracker.ietf.org/doc/html/rfc2616

com.eomcs.net.ex07

URL을 다루는 클래스

url.getQuery()

url.getPort()); // 지정하지 않으면 -1 리턴. 실제 접속할 때는 기본 포트번호 사용.

QueryString: null

물음표만 있으면 빈 문자열 나옴
QueryString:

url.getRef()

com.eomcs.net.ex08

응답 헤더의 다양한 값을 추출할 수 있다.

com.eomcs.net.ex11.test

serverSocket.accept();
클라이언트와 대화를 나눌 수 있는 소켓을 리턴

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

소켓을 먼저 닫으면 안 됨

out.flush(); 무조건 쓰기

try 안에 두기

      try (
          Socket socket = serverSocket.accept();
          Scanner in = new Scanner(socket.getInputStream());
          PrintStream out = new PrintStream(socket.getOutputStream());
          ) {
          out.println("계산기 서버에 오신 걸 환영합니다!");
          out.println("계산식을 입력하세요!");
          out.println("예) 23 + 7");
      System.out.println(in.nextLine());
      System.out.println(in.nextLine());
      System.out.println(in.nextLine());

// CalculatorServer

          out.println("계산기 서버에 오신 걸 환영합니다!");
          out.println("계산식을 입력하세요!");
          out.println("요청 형식: ");
          out.println("예) 23 + 7");
          out.println();
// CalculatorClient

      while (true) {
        String str = in.nextLine();
        if (str.length() == 0) {
          break;
        }
        System.out.println(str);
      }
          try (BufferedReader welcomeIn = new BufferedReader(new FileReader("welcome.txt"));) {
            while (true) {
              String str = welcomeIn.readLine();
              if (str == null ) {
                break;
              }
              out.println(str);
            }
          } catch (Exception e) {
            out.println("환영합니다!");
          }

CalculatorServer 생성자 만들기

서버 스타트할 때 한 번만

  String logo;

  public CalculatorServer() {
    StringBuilder logoStr = new StringBuilder();

    try (BufferedReader welcomeIn = new BufferedReader(new FileReader("welcome.txt"));) {
      while (true) {
        String str = welcomeIn.readLine();
        if (str == null ) {
          break;
        }
        logoStr.append(str + "\n");
      }
    } catch (Exception e) {
      logoStr.setLength(0);
      logoStr.append("환영합니다!\n");
    }

    logo = logoStr.toString();
  }

out.print(logo);
println이 아니라 print

별도의 실행 흐름을 만들자

RequestHandler requestHandler = new RequestHandler(socket, logo);

RequestHandler requestHandler = new RequestHandler(serverSocket.accept(), logo);

new RequestHandler(serverSocket.accept(), logo).start();

RequestHandler 다른 클래스가 쓸 일이 없음
어차피 Calculator 안에서만 쓸 거
특정 클래스 안에서만 사용되는 건 그 안에
중첩 클래스

out.print(CalculatorServer.this.logo);

중첩 클래스 이점

빌트-인 변수 this

inner class에서 바깥 클래스의
마치 자기 변수인마냥 생략 가능

      while (true) {        
        System.out.print("계산식> ");
        String input = keyScan.nextLine();

        out.println(input);
        out.flush();

        String str = in.nextLine();
        System.out.println(str);

        if (input.equals("quit")) {
          break;
        }
      }
        while (true) {
          String str = in.nextLine();
          if (str.equals("quit")) {
            out.println("Goodbye!");
            out.flush();
            break;
          }
          out.println(str);
          out.flush();
        }

전형적인 stateful 방식

// CalculatorClient

        if (input.split(" ").length != 3) {
          System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
          continue;
        }

계산 중 오류 발생 - (예외 메시지) <== 서버의 응답

계산식을 입력하세요!
예) 23 + 7
계산식> ok + no
서버 연결 오류 발생!

정수로 바꾸려고 할 때 오류 발생함

          String[] values = str.split(" ");
          int a = Integer.parseInt(values[0]);
          int b = Integer.parseInt(values[2]);
          String op = values[1];

quit이 아닌 경우 &&

        if (!input.equals("quit") && input.split(" ").length != 3) {
          System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
          continue;
        }

윈도우 껍데기
윈도우 객체와 연결
동료 peer
윈도우를 컨트롤하는 리모컨

0개의 댓글