13_네트워크(Network)
네트워크: Net(그물망) + work(일) == 여러 대의 컴퓨터들이 그물망처럼 얽히고 설켜 일을 할 수 있는 구조
여러 대의 컴퓨터를 통신 회선으로 연결한 것(홈 네트워크, 지역 네트워크, 인터넷 등이 해당)
서버 | (== 가게주인) 클라이언트의 요청이 있기 전까지는 항상 대기 상태 클라이언트의 요청을 받고 해당 내용을 처리 후 응답을 보냄 |
클라이언트 | (== 손님) 서버의 IP, port번호를 통해 필요로 하는 데이터를 요청함 |
IP주소 | 네트워크상에서 컴퓨터를 식별하는 번호(== 컴퓨터의 주소지) => 고유한 식별값이므로 같은 네트워크 안의 ip주소는 중복될 수 없음 |
포트(Port) | 같은 컴퓨터 내에서 프로그램을 식별하는 번호(== 상세주소) => 고유의 식별값이므로 중복 불가 == 상대방의 IP주소와 포트 번호를 알고 있어야 데이터를 주고받을 수 있음 |
*네트워크(Network)
여러 대의 컴퓨터들이 연결되어 있는 통신망을 일컫는 용어
네트워크를 통해 서로간의 데이터들을 교환 가능함
*IP주소와 port번호
- IP주소: 네트워크상에 각 컴퓨터들을 식별할 수 있는 고유의 주소 번호
(중복 불가, 컴퓨터의 주민번호)
- port번호: 컴퓨터 안에 작동하는 프로그램을 찾을 때
각각의 프로그램을 식별해 주는 내부 주로
=> IP 주소와 port번호는 고정값이 아니라 변동이 가능한 유동적인 값임
단, 값을 바꿀 때 IP주소 같은 경우 같은 네트워크 범위 안에서
port번호 같은 경우 같은 컴퓨터 범위 안에서 중복이 일어나면 충돌 일어남
*서버와 클라이언트
- 서버: 클라이언트(고객)에게 서비스를 제공하는 PC 또는 프로그램
즉, 클라이언트의 요청을 처리해서 응답해 주는 역할 => 요청이 언제 들어올지 모르니 항상 켜져 있고 대기해야 하는 상태
- 클라이언트: 서버에 요청하는 PC 또는 프로그램을 일컫는 단어
즉, 서비스를 제공받는 역할
=> 클라이언트 입장에서는 서버에 요청을 하기 위해서는 항상 그 요청하고자 하는 서버의 IP주소, port번호를 알고 있어야 함
<사용 메소드>
localHost: 지역 호스트 => 나의 PC를 지칭
.getlocalHost(): 나의 PC에 대한 정보를 반환
.getHostName(): pc명 반환
.getHostAddress(): IP주소 반환
InetRun 클래스
public static void main(String[] args) {
// InetAddress: 네트워크 정보(IP주소 관련)를 확인할 수 있는 클래스
try {
InetAddress localhost = InetAddress.getLocalHost();
// localhost: 지역호스트 => 내 PC를 지칭
// getLocaHost(): 내 PC에 대한 정보를 반환해 주는 메소드 (InetAddress 형태로 반환)
System.out.println(localhost); // 내 PC명 + "/" + 내 IP주소
System.out.println("내 PC명: " + localhost.getHostName());
System.out.println("내 IP주소: " + localhost.getHostAddress());
System.out.println("--------------------------------------");
// 내가 알고 있는 "도메인 주소"를 제시해서 그 서버와 관련된 정보를 얻기
// 도메인 주소: 사용자가 웹사이트에 접속할 때 매번 IP주소를 외우기 어렵기 때문에
// 외우기 쉬운 구조로 사용자에게 제공해 주는 주소
// 원래의 구조: IP주소 ----------------------------------------------> 바로 접속
// 도메인 주소 적용: 도메인 주소 -----> DNS(Domain Name Server) -----> IP주소로 연결
// 구글의 정보를 InetAddress 객체에 담기
InetAddress googleHost = InetAddress.getByName("www.google.com");
// getByName(): 도메인 주소라는 고유한 주소를 통해서 해당 서버 PC의 정보를 얻어내서 반환(InetAddress 형태로 반환)
System.out.println(googleHost);
System.out.println("구글 서버명: " + googleHost.getHostName());
System.out.println("구글 IP주소: " + googleHost.getHostAddress());
// 대형 사이트의 서버는 여러 개로 이루어져 있음!! (분산구조)
// => 한 곳에만 요청이 몰빵될 경우 서버에 과부화 걸림
System.out.println("--------------------------------------");
// 도메인 주소를 통해서 그 서버 관련한 것들을 배열로도 받아 볼 수 있음
InetAddress[] naverHost = InetAddress.getAllByName("www.naver.com");
System.out.println("네이버 서버 개수: " + naverHost.length);
// 반복문 돌려가며 모든 서버의 정보 출력
/*
for(int i = 0; i < naverHost.length; i++) {
System.out.println(i + "번째 서버: " + naverHost[i]);
}
*/
// 향상된 for문
// for(변수선언문 : 반복돌배열명)
for(InetAddress n : naverHost) {
System.out.println(n);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
* 현재 구동 중인 서버가 있으면 클라이언트는 그 서버로 요청을 보낼 수 있음
=> 응답 결과가 항상 돌아옴!
요청과 응답에 의해서 프로그램은 돌아감("통신"한다라고 표현)
=> 웹에서의 통신 방식: HTTP 프로토콜 통신(HTTPS: HTTP에 보안 절차가 추가된 버전)
*자바만을 가지고 서버와 클라이언트 간의 간단한 통신 해 보기
=> 이때 데이터를 입출력하고자 한다면 서버와 클라이언트 간에 스트림 (연결 통로)가 있어야 함!
*소켓: 프로세스간에 통신을 담당하는 것
즉, 스트림을 연결하기 위한 문
*소켓 프로그래밍(TCP 방식 / UDP 방식)
- TCP방식: 데이터 전송 속도가 느리나 데이터를 정확하고 안정적으로 전달 가능함
주로 신뢰성이 요구되는 프로그램에서 많이 사용(웹, 이메일, 파일전송, ...)
- UDP방식: 데이터 전송 속도는 빠르나 신뢰성이 없는 데이터가 전송될 수 있음(중간에 데이터 손실될 가능성 있음)
주로 데이터를 빠른 속도로 전송하고자 하는 프로그램에서 사용(실시간 스트리밍)
*TCP(Transmission Control Protocol)
- 서버, 클라이언트간의 1:1 소켓 통신 방식
- 데이터를 교환하기에 앞서서 서버, 클라이언트에 연결되어 있어야 함
(항상 서버가 먼저 실행되어 클라이언트의 요청을 기다릴 것)
- 신뢰성 있는 데이터 전달 가능
*Socket
- 프로세스 간의 통신을 담당
- Socket 프로그래밍을 위한 기반스트림인 Input / OutputStream을 가지고 있음
- 기반스트림만으로는 제한이 있으니 보조스트림을 추가시켜 성능을 향상시킬 것
*ServerSocket
- 포트번호와 연결(Bind: 엮다)되어 외부의 연결 요청을 기다리다 연결 요청이 들어오면 수락해 줄 용도
수락하는 순간 Socket 객체가 생성됨!
<진행 순서>
0. 소켓 만들기
1. 스트림 열기
2. 볼 일 보기
3. 스트림 끊기
ServerProgram 클래스
package com.kh.chap02.tcp.run;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
// 서버용 프로그램
public class ServerProgram {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 1) 이 프로그램에서 사용할 port번호 지정
// => 서버측에서 몇 번 포트로 통로를 열겠냐는 의미
int port = 3000;
ServerSocket server = null;
BufferedReader br = null;
PrintWriter pw = null;
// 2) ServerSocket 객체 생성 및 포트 결합(Bind)
// => 앞으로 클라이언트의 연결 요청을 받아 줄 용도로 사용됨
try {
server = new ServerSocket(port);
// 3) 클라이언트로부터 접속 요청이 들어올 때까지 대기 상태
System.out.println("클라이언트의 요청을 기다리고 있습니다..");
// 4) 연결 요청이 오면 요청을 수락 후 해당 클라이언트와 통신할 수 있는 Socket 객체가 생성됨
Socket socket = server.accept(); // socket == 클라이언트와 통신하기 위한 소켓 객체
// 상대방의 정보 얻기: getInetAddress() 메소드 사용
System.out.println(socket.getInetAddress().getHostAddress() + " 가 연결을 요청함..");
// ----- 이 시점까지 연결하겠다고 약속은 된 상태, 아직 스트림은 열리지 않음 -----
// 5) 클라이언트와 통신할 수 있는 입력용, 출력용 스트림을 생성 => socket 객체에서 제공해 줌
// 6) 보조스트림을 추가하여 성능 개선
// 입력용 스트림(클라이언트로부터 전달된 값을 한 줄 단위로 읽어들일 수 있게끔)
// socket.getInputStream()
// + 1byte 와 2byte 사이의 호환이 가능한 보조 스트림(InputStreamReader)
// BufferedReader
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 출력용 스트림 (클라이언트에게 값을 전달할 용도)
// socket.getOutputStream()
// + 1byte 와 2byte 사이의 호환이 가능한 보조 스트림(OutputStreamWriter)
// PrintWriter
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
// => 기반스트림에 성능 향상을 위해 보조스트림을 각각 2개씩 붙여 줌
// 7) 클라이언트와 스트림을 통해 읽고 쓰기
// => 통신을 주거니 받거니 반복을 위한 반복문
while(true) {
// 클라이언트로부터 전달된 메시지가 있을 경우 서버측에서 읽어들이기(input)
String message = br.readLine();
System.out.println("클라이언트로부터 전달받은 메시지: " + message);
// 반대로 클라이언트에게 데이터를 전달하기(output)
System.out.println("클라이언트에게 보낼 내용: ");
String sendMessage = sc.nextLine();
pw.println(sendMessage); // 클라이언트에게 1줄 단위로 출력
pw.flush(); // 현재 스트림에 남아 있는 잔여 데이터를 강제로 내보내는 역할을 해 주는 메소드
// 여태까지는 입출력 후 자원 반납 해 주었기 때문에 flush 메소드를 쓰지 않았음
// 자원 반납 시 자동으로 flush()가 진행되기 때문!
// 지금은 주고받고 해야 하니까 스트림 막힐까 봐 flush() 메소드를 사용함
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 8) 통신 종료를 위한 자원 반납(생성된 순서의 역순으로)
try {
pw.close();
br.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ClientProgram 클래스
package com.kh.chap02.tcp.run;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class ClientProgram {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Socket socket = null;
BufferedReader br = null;
PrintWriter pw = null;
// 1) 접속하고자 하는 서버의 IP주소, port번호 지정
// 요청하고자 하는 서버의 IP주소: 192.168.40.30 또는 127.0.0.1 (루프백 IP) 또는 localhost
String serverIP = "127.0.0.1";
int port = 3000;
// 2) 서버에 연결 요청을 보내는 구문
// => 요청하고자 하는 서버의 IP주소와 port번호를 제시하면서 Socket 객체 생성
try {
socket = new Socket(serverIP, port);
// => Socket 객체 생성 시 연결이 제대로 되었다면 객체가 잘 생성될 것
// 연결이 실패할 경우에는 null 값이 Socket 객체에 담김
// NullPointerException은 충분히 예상 가능한 오류이기 때문에 조건문으로 선처리
if(socket != null) { // 연결이 잘되었을 경우 => 통신 진행
System.out.println("서버와 연결 성공!");
// 3) 서버와 통신할 수 있는 입력용 스트림, 출력용 스트림 열기
// 4) 보조 스트림을 추가하여 성능 개선
// 입력용 스트림
// socket.getInputStream() + InputStreamReader + BufferedReader
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 출력용 스트림
// socket.getOutputStream() + OutputStreamWriter + PrintWriter
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
// 반복문
while(true) {
// 5) 스트림을 통해 읽고 쓰기
// 서버에게 데이터를 전달 (output)
System.out.print("서버에게 보낼 내용: ");
String sendMessage = sc.nextLine();
pw.println(sendMessage);
pw.flush();
// 서버로부터 전달된 메시지를 읽어들이기 (input)
String message = br.readLine();
System.out.println("서버로부터 전달받은 메시지: " + message);
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pw.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
헐
개쩐다