TCP/IP 프로토콜
TCP 프로토콜은 두 시스템 간에 신뢰성 있는 데이터의 전송을 관장하는 통신 프로토콜로서 IP 프로토콜 위에서 동작한다.
TCP 프로토콜을 사용하는 응용프로그램으로는 e-mail, FTP, 웹 HTTP 등이 있다.
IP는 패킷 교환 네트워크에서 송신 호스트와 수신 호스트가 데이터를 주고받는 것을 관장하는 프로토콜로서 TCP의
하위 레벨 프로토콜이다. TCP는 IP 기능을 활용하여 두 시스템 사이에 데이터가 손상 없이 안전하게 전송되도록 하며,
TCP와 IP를 묶어 TCP/IP로 표기한다.
IP 주소와 PORT 번호
IP 주소는 네트워크상에서 유일하게 식별될 수 있는 네트워크 장치의 주소로서 형태는 192.156.11.14와 같이
4개의 숫자가 ’.’ 으로 연결된다. 하나의 숫자 범위는 0 ~ 255로서 한 바이트로 표현이 가능하다.
IP 주소는 마치 전화번호나 집 주소와 같아 이 주소를 통해 네트워크에 연결된 장치를 식별할 수 있으며,
동일한 주소를 여러 네트워크 장치에 중복해서 사용할 수 없다.
숫자로 된 주소는 기억하기 어려우므로 https://sey2.github.io 같은 문자열로 구성된 도메인 이름으로 바꿔 사용한다.
이렇게 사용자가 문자열로 구성된 도메인 이름을 사용하면 DNS 서버에 의해 숫자로 구성된 IP 주소로 자동 변환하게 된다.
현재는 4개의 숫자로 구성된 IP 주소를 표현하기 위해 32비트 IPv4가 사용되고 있지만,
세계적으로 네트워크 장치의 개수가 급격하게 증가하여 각 장치에 고유하게 부여할 수 있는 IP주소가 고갈됨에 따라
128비트의 (IPv6)이 점점 사용되는 추세이다.
한 컴퓨터에는 여러 응용 프로그램이 네트워크를 사용하고 있기 때문에, IP 주소만 가지고 통신하고자 하는
응용프그램을 식별할 수 없다. 이를 위해 한 컴퓨터 내의 각 응용프로그램은 통신을 위해 가상의 연결단인
PORT를 생성하고, 이 포트 번호로 상태방이 자신을 식별하게 된다.
IP 주소는 아파트의 동 번호와 같고, 포트 번호는 그 동에 있는 호 번호에 비유할 수 있다.
포트 번호는 응용프로그램 개발자가 임의로 선택하여 선택 할 수 있다.
하지만 기존 응용 프로그램에서 사용하고 있는 포트 번호나 시스템 포트 번호는 피하는게 좋다.
예를 들어 Telnet 23번 포트, HTTP는 80번 포트, FTP는 21번 포트 등이며, 이들은 주로 0 ~ 1023 사이의 번호를
가지므로 이 범위의 포트 번호는 피해서 선택하는 것이 좋다.
소켓
소켓 통신은 개발자가 TCP/IP 네트워크를 이용하여 쉽게 통신 프로그램을 작성하도록 지원하는 기반 기술이다.
TCP/IP의 네트워크 기능을 활용하여 다른 컴퓨터의 소켓과 데이터를 주고 받는다.
소켓을 활용하는 통신의 모양은 아래와 같다.
소켓을 이용하는 통신 사례
응용 프로그램은 소켓과 연결한 후 소켓에 데이터를 주기만 하면, 소켓이 상대방 응용 프로그램에 연결된 소켓에
데이터를 보낸다. 또는 응용 프로그램은 연결된 소켓으로부터 도착한 데이터를 단순히 받기만 하면 된다.
소켓과 서버 클라이언트 통신
소켓을 이용하는 통신에서는 서버 응용 프로그램과 클라이언트 응용 프로그램으로 구분된다.
정보를 제공하는 쪽을 서버라고 하고, 정보를 이용하는 쪽을 클라이언트라고 부른다.
서버가 먼저 클라이언트의 접속을 기다리고, 클라이언트에서 서버에 접속하면, 그 때부터 서버나 클라이언트가
데이터를 주고 받을 수 있다.
### 서버 소켓과 클라이언트 소켓
- 서버 소켓은 서버 응용 프로그램이 사용자의 접속을 기다리는 목적으로만 사용된다.
- 클라이언트 응용 프로그램에서는 클라이언트 소켓을 이용하여 서버에 접속한다.
- 서버 소켓은 클라이언트가 접속해오면, 클라이언트 소켓을 추가로 만들어 상대 클라이언트와 통신하게 한다.
소켓을 이용한 서버 클라이언트 통신 프로그램의 전형적인 구조
서버 소켓 객체를 생성하고 클라이언트의 접속을 받기 위해 기다린다. 서버 소켓을 생성할 때 포트 번호를 주어 해당 포트로 접속해 오는 클라이언트를 기다리게 한다.
클라이언트 응용프로그램은 Client Socket 객체를 생성하고 서버에 접속을 시도한다. 객체를 생성할 때 접속할 서버 소켓의 IP 주소와 포트 번호를 지정한다.
서버와 클라이언트로부터 접속 요청을 받으면, accept() 메소드에서 접속된 클라이언트와 통신하도록 전용 클라이언트 소켓을 따로 생성한다.
서버와 클라이언트 모두 소켓으로부터 입출력 스트림을 얻어내고 데이터를 주고 받을 준비한다.
서버에 생성된 클라이언트 전용 소켓과 클라이언트의 소켓이 상호 연결된 채 스트림을 이용하여 양방향으로 데이터를 주고 받는다.
서버는 클라이언트가 접속해 올 때마다 accpet() 메소드에서 따로 전용 클라이언트 소켓을 생성하여
클라이언트와 통신하도록 한다. 통신이 끝나면 소켓을 닫는다.
소켓 클래스의 주요 메소드
메소드 | 설명 |
---|---|
Socket | 연결되지 않은 상태의 소켓 소켓을 생성 |
Socket(InetAddress address, int port) | 소켓을 생성하고, 지정된 UP 주소와 포트 번호에서 대기하는 원격 응용 프로그램의 소켓에 연결 |
Socket(String host, int port) | 소켓을 생성하여 지정된 호스트와 포트 번호에 연결한다. 호스트 이름이 null인 경우 루프백 주소로 가정 |
void bind(SocketAddress bindpoint) | 소켓에 local IP 주소와 local 포트 지정 |
void close() | 소켓을 닫는다. |
void connect(SocketAddress endpoint) | 서버에 연결 |
InetAddress getInetAddress() | 소켓에 연결된 서버 IP 주소 반환 |
InputStream getInputStream() | 소켓의 입력 스트림 반환. 이 스트림을 이용하여 소켓이 상대편으로부터 받은 데이터를 읽을 수 있음 |
InetAddress getLocalAddress() | 소켓의 로컬 주소 반환 |
int getLocalPort() | 소켓의 로컬 포트 번호 반횐 |
int getPort() | 소켓에 연결된 서버의 포트 번호 반환 |
OutputStream getOutputStream() | 소켓의 출력 스트림 반환. 이 스트림에 출력하면 소켓이 서버로 데이터 전송 |
boolean isBound() | 소켓이 로컬 주소와 결합되어 있으면 true 반환 |
boolean isConnected() | 소켓이 서버에 연결되어 있으면 true 반환 |
boolean isClosed() | 소켓이 닫혀 있으면 true 반환 |
void setSoTimeout(int timeout) | 데이터 읽기 타임아웃 시간 지정. 0이면 타임아웃 해제 |
ServerSocket 클래스
Server Socket 클래스는 서버 소켓을 구현한다.
ServerSocket은 클라이언트로부터 연결 요청을 기다리는 목적으로만 사용되며, 서버가 클라이언트의
연결 요청을 수락하면 Socket 객체를 별도로 생성하고, 이 Socket 객체가 클라이언트와 데이터를 주고 받는다.
서버 소켓 생성 예
1
ServerSocket listener = new ServerSocket(9999);
이 포트 번호는 클라이언트의 접속을 기다릴 자신의 포트 번호이다.
이미 사용 중인 포트 번호를 지정하면 오류가 발생한다.
1
Socket socket = listenr.accept();
ServerSocket 클래스의 accept() 메소드를 이용하여 클라이언트로부터의 연결 요청을 기다린다.
acppet() 메소드가 연결을 수락하면 위와 같이 Socket 객체를 하나 생성하여 리턴한다.
ServerSocket 클래스의 주요 메소드
메소드 | 설명 |
---|---|
Socket accpet() | 클라이언트로부터 연결 요청을 기다리다 요청이 들어오면 수락하고 클라이언트와 데이터를 주고 받을 |
새 Socket 객체를 반환 | |
void close() | 서버 소켓을 닫는다. |
InetAddress getInetAddress() | 서버 소켓의 로컬 IP 주소 반환 |
int getLocalPort() | 서버 소켓의 로컬 포트 번호 반환 |
boolean isBound() | 서버 소켓이 로컬 주소와 결합되어 있으면 true 반환 |
boolean isClosed() | 서버 소켓이 닫혀 있으면 true 반환 |
void setSoTimeOut(int timeout) | accpet()가 대기하는 타임 아웃 시간 지정. 0이면 무한정 대기 |
서버 클라이언트 채팅 프로그램
Server.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Server {
public static void main(String [] args) {
BufferedReader in = null;
BufferedWriter out = null;
ServerSocket listener = null;
Socket socket = null;
Scanner scanner = new Scanner(System.in);
try {
listener = new ServerSocket(9999);
System.out.println("연결을 기다리는 중..");
socket = listener.accept();
System.out.println("연결되었습니다.");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
String inputMessage = in.readLine();
// equalsIgnoreCase는 대소문자 구분하지 않는다.
if(inputMessage.equalsIgnoreCase("bye")) {
System.out.println("클라이언트에서 bye로 연결을 종료 하였음");
break; // bye를 받으면 연결 종료
}
System.out.println("클라이언트: " + inputMessage);
System.out.print("보내기>>");
String outputMessage = scanner.nextLine();
out.write(outputMessage + "\n");
out.flush();
}
}catch(IOException e) {
System.out.println(e.getMessage());
} finally {
try {
scanner.close();
socket.close(); // 통신용 소켓 닫기
listener.close(); // 서버 소켓 닫기
}catch(IOException e) {
System.out.println("클라이언트와 채팅중 오류가 발생했습니다.");
}
}
}
}
Client.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
BufferedReader in = null;
BufferedWriter out = null;
Socket socket = null;
Scanner scanner = new Scanner(System.in);
try {
// 다른 서버의 컴퓨터면 localhost 대신 IP 주소를 적어야함
socket = new Socket("localhost", 9999); // 클라이언트 소켓 생성, 서버에 연결
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
System.out.print("보내기>>");
String outputMessage = scanner.nextLine();
if(outputMessage.equalsIgnoreCase("bye")) {
out.write(outputMessage + "\n"); // bye 문자열 전송
out.flush(); // out의 스트림 버퍼에 있는 모든 문자열 전송
String inputMessage = in.readLine();
System.out.println("서버: " + inputMessage); // 서버로 부터 받은 메시지를 화면에 출력
}
out.write(outputMessage + "\n");
out.flush();
String inputMessage = in.readLine();
System.out.println("서버: "+ inputMessage);
}
}catch (IOException e) {
System.out.println(e.getMessage());
}finally {
try {
scanner.close();
if(socket!=null) socket.close(); // 클라이언트 소켓ㅎ 닫기
}catch (IOException e) {
System.out.println("서버와 채팅 중 오류가 발생 했습니다.");
}
}
}
}
결과