본문 바로가기

IT

[TCP] 서버는 TCP 소켓 연결을 몇개 까지 할 수 있을 까?

들어가기 앞서

Release의 모든 것이라는 책을 읽다가 보니, 포트가 1024 ~ 65535까지 총 64511개가 있는데 많은 사용자가 서버에 접속할 수 있는 이유에 대해서 설명하는 부분이 있었다.

주된 이유는 운영체제가 가상 IP 주소를 동일한 네트워크 인터페이스에 부여하기 때문이라고 적혀있다.

내가 예상하는 바와는 다른 내용이라 분석을 시도했다.

 

Contents

우선 64511개 보다 더 많은 TCP 연결을 동시에 할 수 있는 이유는, 소켓을 구성하는 데 있어서, 포트 번호로만 연결이 식별되지 않기 때문이다.

보통 (Source IP, Source Port, Destination IP, Destination Port)을 기준으로 튜플 key로 연결이 구분된다.

 

Client가 Server에 접속한다고 가정하면 다음과 같이 구성된다.

source ip source port destination ip destination port
1.1.1.1 40000 10.0.0.1 443
1.1.1.1 40001 10.0.0.1 443
1.1.1.2 50000 10.0.0.1 443
1.1.1.3 60000 10.0.0.1 443

 

즉, Source IP당 Source Port가 계속 달라짐으로 서버측 포트 번호만으로 연결 수가 제한되지는 않는다.

 

그러면 이런 생각을 할 수 있을 것이다.

 

서버 입장에서는 소켓제한이 없는거는 이해했는데, 그럼 client가 connection close를 하지 않고 64511개를 초과해서 TCP를 연결하면 더 이상 연결 못하는 거 아닐까?

 

docker compose 환경을 이용해서, connection을 close하지 않고, client가 계속해서 소켓 연결을 시도하도록 만들었다.

 

server.py

import socket

HOST = "0.0.0.0"
PORT = 5000

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((HOST, PORT))
    s.listen()
    print(f"[SERVER] Listening on {HOST}:{PORT}", flush=True)

    count = 0
    while True:
        conn, addr = s.accept()
        count += 1
        if count % 1000 == 0:
            print(f"[SERVER] Accepted connections: {count}", flush=True)
        # 연결을 유지하기 위해 close 하지 않음

if __name__ == "__main__":
    main()

 

client.py

import socket
import time

HOST = "server"
PORT = 5000

def main():
    sockets = []
    success = 0

    while True:
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((HOST, PORT))
            sockets.append(s)
            success += 1

            if success % 1000 == 0:
                print(f"[CLIENT] Connected: {success}", flush=True)

        except ConnectionRefusedError as e:
            print(f"[CLIENT] Server not ready ({e})", flush=True)
            time.sleep(1)
            continue

        except OSError as e:
            print(f"[CLIENT] Failed after {success} connections: {e}", flush=True)
            time.sleep(5)
            break

if __name__ == "__main__":
    main()

 

Dockerfile

FROM python:3.13-slim

COPY server.py client.py ./

CMD ["python", "server.py"]

 

docker-compose.yml

version: "3.9"

services:
  server:
    build: .
    container_name: tcp-server
    command: ["python", "server.py"]
    ulimits:
      nofile:
        soft: 1000000
        hard: 1000000
    networks:
      - testnet

  client1:
    build: .
    container_name: tcp-client-1
    command: ["python", "client.py"]
    depends_on:
      - server
    ulimits:
      nofile:
        soft: 100000
        hard: 100000
    networks:
      - testnet

  client2:
    build: .
    container_name: tcp-client-2
    command: ["python", "client.py"]
    depends_on:
      - server
    ulimits:
      nofile:
        soft: 100000
        hard: 100000
    networks:
      - testnet

networks:
  testnet:
    driver: bridge

 

 

위와 같이 구성 후 docker compose up --build로 실행 시켜 보면, 다음과 같이 나온다.

 

약 60000개는 커녕, 28232개까지만 connection을 생성한 것을 볼 수 있다.

 

이유는, 운영체제에 있는데, 기본적으로 linux의 ephemeral port range는 32768 60999로 설정되어 있다.

즉,  32768 ~ 60999 포트만 TCP 소켓으로 사용 가능하다.

 

cat /proc/sys/net/ipv4/ip_local_port_range

# 32768 60999

 

 

 

그래서, 아래와 같이 직접 1024 ~ 65535 까지 사용하라고 변경해 보겠다.

docker-compose.yml

version: "3.9"

services:
  server:
    build: .
    container_name: tcp-server
    command: ["python", "server.py"]
    ulimits:
      nofile:
        soft: 1000000
        hard: 1000000
    networks:
      - testnet

  client1:
    build: .
    container_name: tcp-client-1
    command: ["python", "client.py"]
    sysctls:
      net.ipv4.ip_local_port_range: "1024 65535"
    depends_on:
      - server
    ulimits:
      nofile:
        soft: 100000
        hard: 100000
    networks:
      - testnet

  client2:
    build: .
    container_name: tcp-client-2
    command: ["python", "client.py"]
    sysctls:
      net.ipv4.ip_local_port_range: "1024 65535"
    depends_on:
      - server
    ulimits:
      nofile:
        soft: 100000
        hard: 100000
    networks:
      - testnet

networks:
  testnet:
    driver: bridge

 

 

각 Client의 connection 수 상승했다는 것을 알 수 있다.

 

그 외에 오차를 보이거나, connection이 연결되지 않는 이유를 보면, 대부분 운영체제의 설정이나, FD 개수 문제 등이 있다.

즉, TCP 연결은 linux의 커널의 역할이 큰데, TCP 관리 정책에 의해서 조금의 오차가 있는 것으로 보인다.

 

중요한 점은, 원하는 통신을 이루고 나면, TCP close를 해야 한다는 것이고, 실시간 서비스라 한다고 하더라도, 수만 개의 연결을 단일 client에 하는 경우는 없어야 한다는 점이다.