---
# System prepended metadata

title: 네트워크(5)
tags: [과외(하희영)]

---

# TCP(Transmission Control Protocol)

## TCP란? 

- TCP는 인터넷 프로토콜 스택, 즉 TCP/IP 프로토콜 스택의 핵심 프로토콜 중 하나로, 전송 계층에서 작동한다.
- 이 프로토콜은 신뢰성 있는 데이터 전송을 보장한다. 즉, 데이터는 손상, 손실, 중복 또는 순서 변경 없이 정확하게 도착한다.
- TCP는 데이터를 패킷 단위로 분할하여 IP 네트워크를 통해 전송하며, 수신측에서는 이 패킷들을 원래의 순서대로 재조립한다.
- TCP는 3-way Handshake를 통해 연결을 설정하고 4-way Handshake를 통해 연결을 종료한다. 이렇게 연결 지향적인 통신을 제공한다.
- 각 TCP 패킷에는 순서 번호 및 확인 응답 번호(ACK 번호)가 포함되어 있어, 데이터의 정확한 전송을 보장한다.
- 또한, TCP는 흐름 제어와 혼잡 제어 기능을 제공하여 네트워크의 효율적인 사용을 지원한다.

## TCP 통신 과정

1. TCP 연결 확립 - 3-way Handshake
2. TCP 데이터 전송
3. TCP 연결 끊기 - 4-way Handshake

![](https://hackmd.io/_uploads/HJMjY0DF2.png)

---

![](https://hackmd.io/_uploads/HypKTCwth.png)


## TCP 연결 확립 - 3-way Handshake

![](https://hackmd.io/_uploads/SkfL_RwK2.png)

- 3-way Handshake는 TCP (Transmission Control Protocol)에서 연결을 시작할 때 사용되는 프로세스이다.
- 이 프로세스는 클라이언트와 서버 사이의 통신이 시작되기 전에 세션을 정확하게 설정하고 동기화하기 위해 사용된다.
- 3단계 프로세스는 다음과 같다
   1. SYN: 클라이언트가 서버에게 연결 요청을 보내기 시작하며, 이를 'SYN' 패킷이라고 한다. 이 패킷은 초기 시퀀스 번호(ISN)를 포함하며, 이 번호는 랜덤하게 생성된다.
   2. SYN-ACK: 서버가 클라이언트의 요청을 받으면, 요청을 수락하기 위해 'SYN-ACK' 패킷을 클라이언트에게 보낸다. 이 패킷은 서버의 초기 시퀀스 번호와, 클라이언트의 시퀀스 번호에 1을 더한 ACK 번호를 포함한다.
   3. ACK: 클라이언트는 마지막으로 'ACK' 패킷을 보내어 서버의 SYN-ACK 패킷을 확인한다. 이 패킷은 서버의 시퀀스 번호에 1을 더한 ACK 번호를 포함한다.
- 이렇게 3-way Handshake를 통해 클라이언트와 서버 사이에 안정적인 연결이 성립된다.

> `SYN` = `Synchronize`
> `ACK` = `Acknowledgement`

## TCP 데이터 전송

![](https://hackmd.io/_uploads/HJzlcAwY3.png)

- TCP는 데이터를 송신하기 전에 패킷으로 분할하는 프로세스를 사용한다. 이렇게 하면 각 패킷은 IP 네트워크를 통해 개별적으로 전송될 수 있다.
- 패킷 분할 과정
   1. 먼저, 데이터는 TCP 세그먼트라는 작은 단위로 나뉜다.
   2. 각 TCP 세그먼트에는 헤더가 추가되며, 헤더에는 중요한 제어 정보(예: 시퀀스 번호, ACK 번호, 플래그 등)가 포함된다.
   3. TCP 세그먼트는 IP 데이터그램에 캡슐화되고, 이 데이터그램은 IP 네트워크를 통해 수신측으로 전송된다.
- 패킷 조립 과정
   1. 수신측에서는 각 IP 데이터그램을 해독하여 TCP 세그먼트를 추출한다.
   2. 각 TCP 세그먼트의 헤더를 검사하여 세그먼트가 올바른 순서대로 조립되고, 손실되거나 중복되는 세그먼트가 없는지 확인한다.
   3. TCP 세그먼트들이 올바르게 조립되면, 데이터는 원래의 순서대로 재조립되고, 응용 프로그램에 전달된다.
- TCP의 이런 패킷 분할과 조립 메커니즘은 신뢰성 있는 데이터 전송을 가능하게 한다.
- TCP에서 데이터를 분할하는 단위를 MSS(Maximum Segment Size, 최대 세그먼트 길이)라고 한다.
  - 네트워크에서 한 번에 보낼 수 있는 데이터 크기를 MTU(Maximum Transmission Unit)라고 한다.
  - ![](https://hackmd.io/_uploads/SJAObAqFh.png)

![](https://hackmd.io/_uploads/r1ByCRDF2.png)

![](https://hackmd.io/_uploads/S1pg0CPY3.png)


### TCP의 흐름 제어

![](https://hackmd.io/_uploads/SJlkkyOKn.png)


- 흐름 제어는 TCP 통신에서 중요한 역할을 하는 메커니즘이다. 이는 송신자가 수신자의 처리 능력을 초과하는 데이터를 전송하는 것을 방지하는 역할을 한다.
- TCP는 이를 위해 '슬라이딩 윈도우'라는 기법을 사용한다. 이 기법에서, 각 TCP 세그먼트는 순차적인 번호를 가지며, 수신자는 이 번호를 바탕으로 데이터를 올바르게 재조립하고, 누락된 세그먼트를 감지한다.
- 또한 수신자는 '윈도우 크기'라는 정보를 보내서 현재 자신이 받아들일 수 있는 데이터의 양을 알려준다. 송신자는 이 정보를 바탕으로 데이터 전송량을 조절한다.
- 이 기법은 네트워크의 혼잡도나 수신자의 처리 능력에 따라 동적으로 데이터의 전송 속도를 조절할 수 있게 해 주므로, TCP 통신의 효율성과 신뢰성을 크게 높인다.

![](https://hackmd.io/_uploads/ByoXJkOKn.png)

## TCP 연결 끊기 - 4-way Handshake

![](https://hackmd.io/_uploads/BJJHp0PYn.png)

- TCP 연결을 종료할 때는 4단계의 '핸드셰이크' 과정이 진행된다. 이 과정은 연결 설정에 사용되는 3-way handshake와 유사하지만, 종료를 위한 추가적인 단계가 있다.
- 연결 종료 과정은 다음과 같다
   1. FIN: 연결을 종료하고자 하는 호스트는 'FIN' 플래그가 설정된 TCP 세그먼트를 보낸다. 이는 "나는 더 이상 데이터를 보낼 것이 없으며 연결을 종료하고 싶다"라는 의미다.
   2. ACK: 수신 호스트는 이 FIN 세그먼트를 받고, 확인 응답으로 'ACK' 세그먼트를 보낸다. 이는 "나는 당신의 종료 요청을 받았으며 이를 처리하고 있다"라는 의미다.
   3. FIN: 이후 수신 호스트는 자신이 보낼 모든 데이터를 전송한 후, 자신의 'FIN' 세그먼트를 보낸다. 이는 "나도 이제 더 이상 데이터를 보낼 것이 없으며 연결을 종료하려 한다"는 의미다.
   4. ACK: 원래의 호스트는 마지막으로 'ACK' 세그먼트를 보내, 상대방의 'FIN' 세그먼트를 확인한다. 이는 "나는 당신의 연결 종료 준비를 인지하였으며 이제 연결을 종료해도 좋다"라는 의미다.
- 이렇게 4-way handshake 과정을 통해 TCP 연결은 안정적으로 종료되며, 이 과정은 양측이 데이터 전송을 완료했음을 보장한다.

# TCP/UDP Echo 서버/클라이언트 예시

> Wireshark를 설치하여 실재 패킷을 주고받는 과정을 확인해보기
> - Wireshark : 네트워크 패킷 분석을 위한 가장 널리 사용되는 오픈소스 도구로, 실시간으로 네트워크 트래픽을 캡처하고 분석할 수 있다. 또한, Wireshark는 이미 캡처된 데이터의 로그 파일을 분석하는 데도 사용할 수 있다.


### UDP 서버
``` java
import java.io.*;
import java.net.*;

public class UdpEchoServer {
    public static void main(String[] args) throws IOException {
        // 데이터그램 소켓을 생성하고, 8000번 포트로 수신 대기한다.
        DatagramSocket serverSocket = new DatagramSocket(8000);
        // 데이터를 받을 바이트 배열을 준비한다.
        byte[] receiveData = new byte[1024];

        while (true) {
            // 데이터그램 패킷을 생성하고, 데이터를 받는다.
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            serverSocket.receive(receivePacket);
            // 받은 데이터를 문자열로 변환한다.
            String message = new String(receivePacket.getData());
            // 메시지를 출력한다.
            System.out.println("Received: " + message);
        }
    }
}

```

---

### UDP 클라이언트
``` java
import java.io.*;
import java.net.*;

public class UdpEchoClient {
    public static void main(String[] args) throws IOException {
        // 데이터그램 소켓을 생성한다.
        DatagramSocket clientSocket = new DatagramSocket();
        // 서버의 IP 주소를 가져온다.
        InetAddress IPAddress = InetAddress.getByName("localhost");
        
        // 메시지를 준비한다.
        String message = "Hello Server!";
        // 메시지를 바이트 배열로 변환한다.
        byte[] sendData = message.getBytes();
        // 데이터그램 패킷을 생성하고, 서버로 보낸다.
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 8000);
        clientSocket.send(sendPacket);
    }
}
```

### TCP 서버

``` java
import java.io.*;
import java.net.*;

public class TcpEchoServer {
    public static void main(String[] args) throws IOException {
        // 서버 소켓을 생성하고, 8000번 포트로 수신 대기한다.
        ServerSocket serverSocket = new ServerSocket(8000);

        while (true) {
            // 클라이언트로부터 연결 요청을 받는다.
            Socket socket = serverSocket.accept();
            // 소켓으로부터 입력 스트림을 가져와서 버퍼드 리더로 읽는다.
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 클라이언트로부터 받은 메시지를 읽는다.
            String message = in.readLine();
            // 메시지를 출력한다.
            System.out.println("Received: " + message);
        }
    }
}

```

---

### TCP 클라이언트

``` java
import java.io.*;
import java.net.*;

public class TcpEchoClient {
    public static void main(String[] args) throws IOException {
        // 소켓을 생성하고, localhost의 8000번 포트로 연결 요청을 보낸다.
        Socket socket = new Socket("localhost", 8000);
        // 소켓으로부터 출력 스트림을 가져와서 프린트 라이터로 출력한다.
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        // 메시지를 보낸다.
        out.println("Hello Server!");
    }
}
```

---


# TCP 파일 송수신 서버/클라이언트 예시

``` java
import java.io.*;
import java.net.*;

public class TcpFileServer {
    public static void main(String[] args) throws IOException {
        // 서버 소켓을 생성하고, 8000번 포트로 수신 대기한다.
        ServerSocket serverSocket = new ServerSocket(8000);

        while (true) {
            // 클라이언트로부터 연결 요청을 받는다.
            Socket socket = serverSocket.accept();

            // 전송할 파일을 읽는다.
            File file = new File("file.txt");
            FileInputStream fileIn = new FileInputStream(file);

            // 소켓으로부터 출력 스트림을 가져온다.
            OutputStream out = socket.getOutputStream();

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fileIn.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }

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

            // 메시지를 출력한다.
            System.out.println("File sent successfully!");
        }
    }
}

```

---

``` java
import java.io.*;
import java.net.*;

public class TcpFileClient {
    public static void main(String[] args) throws IOException {
        // 소켓을 생성하고, localhost의 8000번 포트로 연결 요청을 보낸다.
        Socket socket = new Socket("localhost", 8000);

        // 소켓으로부터 입력 스트림을 가져와서 버퍼드 인풋 스트림으로 읽는다.
        InputStream in = socket.getInputStream();

        // 서버로부터 받은 파일을 읽는다.
        File receivedFile = new File("received.txt");
        FileOutputStream out = new FileOutputStream(receivedFile);
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

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

        System.out.println("Received file successfully!");
    }
}

```

> 위의 코드를 실행할 때는 네트워크를 통해 전송되는 것이 아니라, TCP 소켓을 통해 직접 데이터를 주고 받고 있기 때문에, 이 경우 운영 체제는 네트워크 스택에 있는 TCP 버퍼를 직접 읽고 쓰며, 이는 MTU 제한에 영향을 받지 않는다.