com.eomcs.net.ex05.Server0110.java
연결 지향 (connection-oriented)
TCP 통신
연결이 이루어진 다음에 한 번 요청하고 한 번 응답 받고 끝낼 건지 (stateless)
연결을 끊기 전까지 계속 데이터를 주고 받을 건지 (stateful)
둘 다 일단 연결된 다음에 데이터를 주고 받는다.
소켓을 사용하는 게 전형적인 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, 메신저 (채팅이 아니라 메신저)
서버 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을 통해서 데이터를 보낼 때는
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에서 사용된다.
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
클라이언트가 빈 줄을 보낼 때까지 읽는다
스펙에 따라서 실제 작성하고 돌아가도록 만든 게 자바스크립트
1.3(1998.10) ← ECMA Script의 제품으로서 역할
http specification 검색
https://datatracker.ietf.org/doc/html/rfc2616
URL을 다루는 클래스
url.getQuery()
url.getPort()); // 지정하지 않으면 -1 리턴. 실제 접속할 때는 기본 포트번호 사용.
QueryString: null
물음표만 있으면 빈 문자열 나옴
QueryString:
url.getRef()
응답 헤더의 다양한 값을 추출할 수 있다.
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
윈도우를 컨트롤하는 리모컨