본문 바로가기

이글루스

flexible gameserver

검색페이지 이동

사이드 메뉴

이글루스 블로그 정보

TCP로 작은 크기의 패킷 전송

본문 폰트 사이즈 조절

이글루스 블로그 컨텐츠

원문

Winsock TCP로 작은 크기의 data segment를 전송

Summary

TCP를 사용하여 작은 패킷을 보내야 할 경우, 어플리케이션의 디자인은 매우 중요합니다. 지연된 응답의 상호작용(네이글 알고리즘)을 고려하지 않으면 Winsock 버퍼링이 성능에 큰 영향을 끼칩니다. 이 글에서는 몇 가지 케이스 스터디를 통해 작은 패킷을 효율적으로 전송하는 방법을 알아봅니다.

Background

Microsoft TCP 스택이 패킷을 받으면 200ms의 지연 타이머가 시작됩니다. 수신 측에서 송신 측으로 ACK가 전송되면 지연 타이머는 리셋되고, 다음 패킷을 받으면 또 다시 시작됩니다. 수신된 패킷의 ACK를 언제 전송할 것인가를 결정하기 위해 MS TCP스택은 아래의 기준을 사용합니다.
  • 지연 타이머가 만료되기 전에 다음 패킷을 수신하면 ACK 전송
  • 다음 패킷을 수신하거나 지연 타이머가 만료되기 전에 전송 할 다음 패킷이 있으면 그 패킷에 편승하여 ACK 전송
  • 지연 타이머가 만료되면 ACK 전송
작은 패킷들이 네트워크를 혼잡하게 만드는 것을 회피하기 위해, MS TCP스택은 네이글을 기본적으로 on 합니다. 네이글 알고리즘은 원격에서 이전 패킷의 전송에 대한 ACK를 수신하기 전까지 작은 패킷들을 모으고 전송을 지연시킵니다. 아래는 네이글 알고리즘의 두 가지 예외 상황입니다.
  • MTU(최대 전송 유닛)보다 더 큰 패킷이 모아졌을 때, ACK를 기다리지 않고 바로 전송합니다. 이더넷 네트워크에서 TCP/IP의 MTU는 1460 byte 입니다.
  • TCP_NODELAY 옵션이 활성화 되어 있으면 작은 패킷들을 원격으로 바로 전송합니다.
어플리케이션 레이어의 성능을 향상 시키기 위해, Winsock은 어플리케이션의 전송 버퍼의 데이터를 Winsock 커널 버퍼로 복사합니다. 그리고 나서 스택은 패킷을 언제 원격으로 전송할지 결정하기 위해 휴리스틱을 사용합니다. SO_SNDBUF 옵션을 사용하여 Winsock 커널 버퍼의 크기를 변경할 수 있습니다. 기본값은 8K 입니다. 필요하다면 Winsock은 SO_SNDBUF 사이즈보다 더 큰 사이즈를 버퍼링 할 수 있습니다. 대부분의 경우 어플리케이션이 인지할 수 있는 전송 완료는 어플리케이션의 버퍼가 Winsock 커널 버퍼에 복사되었을 때 이고, 실제로 네트워크에 전송 중인지는 알 수 없습니다. SO_SNDBUF 를 0으로 설정함으로써 Winsock 버퍼링을 껐을 때는 예외입니다.

Winsock은 전송 완료를 어플리케이션에 통지하기 위해 아래의 룰을 따릅니다. 
  • SO_SNDBUF에 남는 버퍼가 있다면 어플리케이션 버퍼에서 복사하고, 어플리케이션에 전송 완료를 통보합니다.
  • SO_SNDBUF에 남는 버퍼가 없지만, 아직 보내지 않은 이전 패킷이 하나만 쌓여있다면 어플리케이션 버퍼에서 복사하고 어플리케이션에 전송 완료를 통보합니다.
  • SO_SNDBUF에 남는 버퍼가 없지만, 아직 보내지 않은 이전 패킷이 하나 이상 쌓여있다면 일단 어플리케이션 버퍼에서 복사하고 어플리케이션에는 전송 완료를 나중에 통보합니다.
Case Study 1

Overview

Winsock TCP 클라이언트가 TCP 서버에게 DB에 저장하기 위한 10000개의 레코드를 보내야 합니다. 레코드의 크기는 20 ~ 100 바이트 입니다. 어플리케이션 로직을 단순화 하기 위한 디자인은 아래와 같습니다.
  • 클라이언트는 블로킹 전송만 하고, 서버는 블로킹 수신만 합니다.
  • 클라이언트 소켓은 SO_SNDBUF를 0으로 설정하여, 매번 데이터 세그먼트를 하나씩 전송합니다.
  • 서버는 루프 안에서 recv를 호출합니다. 수신 버퍼를 200 byte로 배정하여 각각의 레코드가 하나의 recv 호출로 수신이 가능하도록 합니다.
성능

테스트를 통해 클라이언트는 1초에 5개의 레코드만 서버로 전송할 수 있다는 사실을 알수 있습니다. 10000개의 레코드(976KB, (10000x100)/1024)를 전송하는데 30분의 시간이 걸립니다.

분석

클라이언트 TCP_NODELAY가 off이기 때문에, 네이글 알고리즘이 ACK가 도착하기 전까지 다음 패킷을 원격으로 전송하지 못하게 합니다. 하지만 SO_SNDBUF를 0으로 설정해서 Winsock 버퍼링을 off 했습니다. 따라서 10000번의 send 호출과 ACK는 개별적으로 수행됩니다. TCP서버 스택에 아래의 현상이 발생해서 ACK는 매번 200ms 지연 됩니다.
  • 서버가 패킷을 받을 때마다 200ms 지연 타이머가 시작됩니다.
  • 서버는 클라이언트로 전송할 패킷이 없기 때문에, 수신한 패킷에 대한 ACK를 편승해서 전송 할 수 없습니다.
  • 서버의 지연 타이머가 만료되면 ACK가 전송됩니다.
개선 방안

위 디자인에는 2개의 문제점이 있습니다. 첫번째로 지연 타이머 문제 입니다. 클라이언트는 2개의 패킷을 200ms안에 서버로 전송 할 수 있어야 합니다. 네이글 알고리즘 기본값이 on이기 때문에, 클라이언트는 SO_SNDBUF를 0으로 하지 말고 Winsock 버퍼링을 사용해야 합니다. TCP 스택이 MTU보다 큰 패킷을 모으면, full-size 패킷이 ACK를 기다리지 않고 한번에 원격으로 전송됩니다.
두번째로 이 디자인은 매번 작은 크기의 패킷마다 send를 호출합니다. 작은 크기의 패킷을 전송하는 것은 매우 비효율적 입니다. 이 경우에는 80개의 레코드를 모아서 send 호출을 할 수 있습니다. 서버에게 전송될 전체 레코드 개수를 알려주기 위해, 클라이언트는 레코드 개수가 포함된 고정 크기 헤더 전송으로 통신을 시작할 수 있습니다.


포스트 공유하기

썸네일
자바워크님의 글 구독하기
덧글 0 관련글(트랙백) 0
신고
맨 위로

공유하기

주소복사

아래의 URL을 길게 누르면 복사할수있습니다.

http://javawork.egloos.com/m/3176284
닫기

팝업

모바일기기에서만 이용이 가능합니다.
운영체제가 안드로이드, ios인
모바일 기기에서 이용해주세요.

덧글 삭제

정말 삭제하시겠습니까?

비밀번호 확인

게시글 신고하기

밸리 운영정책에 맞지 않는 글은 고객센터로
보내주세요.

신고사유


신고사유와 맞지 않을 경우 처리되지 않을 수 있습니다.
저작권 위반/명예훼손 등은 고객센터를 통해 권리침해
신고해주세요.
고객센터 바로가기