Go의 net/http 패키지가 TCP 연결을 관리하는 방법
Daniel Hayes
Full-Stack Engineer · Leapcell

서론
시작하기 전에 나중에 더 쉽게 이해할 수 있도록 소켓과 파일 디스크립터라는 두 가지 개념에 대해 간략하게 논의해 보겠습니다.
소켓이란 무엇입니까?
소켓은 네트워크 통신을 위한 기본적인 추상화입니다. 애플리케이션이 네트워크 프로토콜 스택에 액세스하기 위한 표준 인터페이스를 제공합니다. 간단히 말해서 소켓은 네트워크 통신을 위한 엔드포인트이며, 서로 다른 컴퓨터의 프로그램이 네트워크를 통해 데이터를 교환할 수 있도록 합니다.
소켓의 주요 기능:
- 애플리케이션 계층과 전송 계층 간의 인터페이스 역할을 합니다.
- 읽기 및 쓰기 작업을 지원하는 특수한 유형의 파일로 간주할 수 있습니다.
- TCP 소켓(연결 지향), UDP 소켓(연결 없음) 등 다양한 유형이 있습니다.
파일 디스크립터란 무엇입니까?
파일 디스크립터는 운영 체제가 열린 파일을 식별하고 관리하는 데 사용하는 정수 값입니다. Unix/Linux 시스템에서는 일반 파일, 디렉터리, 장치, 심지어 네트워크 연결까지 모든 것이 파일로 간주됩니다.
파일 디스크립터에 대한 주요 사항:
- 음수가 아닌 정수이며 일반적으로 0부터 시작합니다(0은 표준 입력, 1은 표준 출력, 2는 표준 오류).
- OS 커널에서 파일 디스크립터는 파일 테이블 항목을 가리키는 인덱스입니다.
- 각 프로세스에는 자체 파일 디스크립터 테이블이 있습니다.
소켓과 파일 디스크립터 간의 관계
Unix/Linux 시스템에서 소켓도 특수한 종류의 파일로 간주되므로 해당 파일 디스크립터도 있습니다. 소켓을 만들 때:
- 운영 체제는 파일 디스크립터를 할당합니다.
- 이 파일 디스크립터는 후속 네트워크 작업(읽기, 쓰기, 닫기 등)에 사용할 수 있습니다.
- 애플리케이션은 이 파일 디스크립터를 통해 소켓과 상호 작용합니다.
TCP 연결 설정 프로세스
소켓 생성
// net 패키지 내부 구현 fd, err := socket(family, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
이 단계에서는 시스템 호출을 통해 소켓 파일 디스크립터를 생성합니다.
서버 바인딩(Bind) 및 리스닝(Listen)
// 단순화된 서버 측 코드 흐름 bind(fd, addr) listen(fd, backlog)
서버는 소켓을 특정 주소 및 포트에 바인딩한 다음 연결 요청을 수신하기 시작합니다.
연결 수락 (Accept)
// net/http/server.go 단순화된 버전 func (srv *Server) Serve(l net.Listener) error { for { rw, err := l.Accept() // 새 연결 수락 if err != nil { // 오류 처리 continue } go srv.newConn(rw).serve(ctx) // 각 연결에 대해 새 goroutine 생성 } }
클라이언트 연결(Connect)
// net/http/transport.go 단순화된 버전 func (t *Transport) dialConn(ctx context.Context, addr string) (*conn, error) { // TCP 연결 생성 netConn, err := t.dial(ctx, "tcp", addr) if err != nil { return nil, err } // HTTP 연결로 래핑 return &conn{ conn: netConn, // ... 기타 필드 }, nil }
데이터 전송:
// 데이터 읽기 n, err := syscall.Read(fd, buf) // 데이터 쓰기 n, err := syscall.Write(fd, data)
연결 닫기:
err := syscall.Close(fd)
주요 구현 세부 사항
멀티플렉싱
- HTTP/1.1은 Keep-Alive 메커니즘을 사용하여 TCP 연결을 재사용합니다.
- HTTP/2는 스트림을 통해 멀티플렉싱을 구현하여 여러 HTTP 요청이 단일 TCP 연결을 공유할 수 있도록 합니다.
연결 풀 관리
// net/http/transport.go type Transport struct { // 유휴 연결 풀 idleConn map[connectMethodKey][]*persistConn // 최대 유휴 연결 수 maxIdleConns int // ... 기타 필드 }
시간 초과 제어
// 연결 시간 초과 설정 conn.SetDeadline(time.Now().Add(timeout))
오류 처리 및 재시도 메커니즘
// 단순화된 재시도 로직 for retry := 0; retry < maxRetries; retry++ { conn, err := dial() if err == nil { return conn } // 대기 및 재시도 time.Sleep(backoff) }
워크플로
클라이언트가 HTTP 요청을 시작할 때:
- 먼저 연결 풀에 사용 가능한 연결이 있는지 확인합니다.
- 그렇지 않으면 새 TCP 연결을 만듭니다.
- HTTP 요청 데이터를 보냅니다.
- 응답을 기다리고 읽습니다.
서버가 요청을 처리할 때:
- Accept 루프는 새 연결을 수신합니다.
- 각 연결에 대해 goroutine을 만듭니다.
- HTTP 요청을 파싱합니다.
- 요청을 처리하고 응답을 반환합니다.
이것이 net/http
패키지가 TCP 프로토콜을 기반으로 HTTP 연결을 구현하는 핵심 메커니즘입니다. 추상화 및 캡슐화를 통해 개발자는 TCP 연결의 기본 세부 사항을 직접 처리할 필요가 없으며 효율적인 연결 관리 및 재사용 메커니즘이 제공됩니다.
Leapcell은 Go 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 지불하십시오. 요청도 없고 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼 지불하십시오.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성 및 고성능
- 고도의 동시성을 쉽게 처리하기 위한 자동 확장.
- 운영 오버헤드가 전혀 없으며 빌드에만 집중하십시오.
설명서에서 자세히 알아보십시오!
X에서 팔로우하세요: @LeapcellHQ