더 좋은게 뭐야? 많은 작은 TCP 패킷 또는 하나의 긴 패킷? [닫은]


16

나는 내가 만들고있는 게임을 위해 서버와의 데이터를주고받습니다.

현재 다음과 같은 위치 데이터를 보냅니다.

sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));

분명히 각각의 X, Y 및 Z 값을 전송합니다.

이와 같은 데이터를 보내는 것이 더 효율적입니까?

sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));

2
제한된 경험에서 패킷 손실은 일반적으로 5 % 미만입니다.
mucaho

5
sendToClient는 실제로 패킷을 전송합니까? 그렇다면 어떻게 그렇게 했습니까?
user253751

1
@mucaho 나는 스스로 또는 다른 것을 측정 한 적이 없지만 TCP가 가장자리에서 거칠다는 것에 놀랐습니다. 0.5 % 이하의 것을 원했을 것입니다.
Panzercrisis

1
@Panzercrisis 동의합니다. 개인적으로 5 %의 손실은 용납 될 수 없다고 생각합니다. 예를 들어, 게임에서 새 배가 생성 된 것과 같은 것을 보내는 경우, 보이지 않는 배를 얻을 수 있기 때문에 해당 패킷이 수신되지 않을 확률이 1 % 일지라도 재앙이 될 것입니다.
joehot200

2
사람들을 놀라게하지 마십시오. 나는 5 %를 상한으로 의미했습니다. :) 실제로 다른 의견에서 언급했듯이 훨씬 좋습니다.
mucaho

답변:


28

TCP 세그먼트에는 많은 오버 헤드가 있습니다. 하나의 TCP 패킷으로 10 바이트 메시지를 보낼 때 실제로 다음을 보냅니다.

  • 16 바이트의 IPv4 헤더 (IPv6가 일반화되면 40 바이트로 증가)
  • 16 바이트의 TCP 헤더
  • 페이로드 10 바이트
  • 사용 된 데이터 링크 및 물리 계층 프로토콜에 대한 추가 오버 헤드

10 바이트의 데이터를 전송하기 위해 42 바이트의 트래픽이 발생합니다. 따라서 사용 가능한 대역폭의 25 % 미만 만 사용하십시오. 그리고 이것은 이더넷이나 PPPoE와 같은 하위 수준 프로토콜이 소비하는 오버 헤드를 아직 설명하지 않습니다 (그러나 대안이 너무 많기 때문에 추정하기가 어렵습니다).

또한 많은 소형 패킷이 라우터, 방화벽, 스위치 및 기타 네트워크 인프라 장비에 더 많은 부담을 주므로 서비스 제공 업체와 사용자가 고품질 하드웨어에 투자하지 않으면 또 다른 병목 현상이 발생할 수 있습니다.

따라서 한 번에 사용 가능한 모든 데이터를 하나의 TCP 세그먼트로 보내야합니다.

패킷 손실 처리와 관련 하여 TCP를 사용할 때 걱정할 필요가 없습니다. 프로토콜 자체는 손실 된 패킷이 재전송되고 패킷이 순서대로 처리되도록하므로 전송하는 모든 패킷이 다른쪽에 도착하고 전송 한 순서대로 도착한다고 가정 할 수 있습니다. 이에 대한 가격은 패킷 손실이있을 때 손실 된 패킷 하나가 다시 요청 및 수신 될 때까지 전체 데이터 스트림을 중단하기 때문에 플레이어가 상당한 지연을 경험한다는 것입니다.

이것이 문제이면 항상 UDP를 사용할 수 있습니다. 그러나 당신이 잃어버린 순서가 잘못된 메시지에 대한 자신의 해결책을 찾기 위해 필요 (이것은 최소한 보장의 메시지 있다는 않습니다 도착은 완전하고 손상되지 않은 도착).


1
TCP가 패킷 손실을 복구하는 방식에 따른 오버 헤드는 패킷 크기에 따라 다른 가치가 있습니까?
Panzercrisis

@Panzercrisis 더 큰 패킷이있는 한 재전송해야합니다.
Philipp

8
OS가 Nagles 알고리즘 en.wikipedia.org/wiki/Nagle%27s_algorithm 을 나가는 데이터에 거의 확실히 적용한다는 것을 알고 싶습니다 . 따라서 앱에서 별도의 쓰기를 수행하거나 결합하면 문제가되지 않습니다. 실제로 TCP를 통해 전달하기 전에.
Vality

1
@Vality 내가 사용한 대부분의 소켓 API는 각 소켓에 대해 nagle을 활성화 또는 비활성화 할 수 있습니다. 대부분의 게임에서는 지연 시간이 낮은 것이 대역폭 보존보다 중요하기 때문에 비활성화하는 것이 좋습니다.
Philipp

4
Nagle의 알고리즘은 하나이지만 전송 측에서 데이터가 버퍼링되는 유일한 이유는 아닙니다. 데이터 전송을 안정적으로 강제 할 수있는 방법없습니다 . 또한 버퍼링 / 조각화는 NAT, 라우터, 프록시 또는 수신 측에서 데이터가 전송 된 후 어디서나 발생할 수 있습니다. TCP는 데이터를받는 크기 및 타이밍과 관련하여 어떠한 보장도하지 않으며, 순서대로 그리고 안정적으로 도착할뿐입니다. 크기 보장이 필요한 경우 UDP를 사용하십시오. TCP는 사실 것 같다 모든 문제에 대한 가장 좋은 도구가되지 않습니다 이해하기 쉽게!
Panda Pajama

10

하나의 큰 것이 (이유 내에서) 더 좋습니다.

당신이 말했듯이, 패킷 손실이 주된 이유입니다. 패킷은 일반적으로 고정 된 크기의 프레임으로 전송되므로 10 개의 작은 프레임이있는 10 개의 프레임보다 큰 메시지의 한 프레임을 사용하는 것이 좋습니다.

그러나 표준 TCP에서는 비활성화하지 않는 한 실제로 문제가되지 않습니다. ( Nagle의 알고리즘 이라고 하며 게임의 경우 비활성화해야합니다.) TCP는 고정 시간 초과를 기다리거나 패키지가 "풀"될 때까지 기다립니다. 여기서 "full"은 프레임 크기에 의해 부분적으로 결정되는 약간 마법의 숫자입니다.


Nagle의 알고리즘에 대해 들었지만 실제로 사용하지 않는 것이 좋습니다. 방금 누군가가 더 효율적이라고 말한 StackOverflow 답변에서 나왔습니다 (명확한 이유로 효율성을 원합니다).
joehot200

6
@ joehot200 그에 대한 유일한 정답은 "그것은 달려있다"입니다. 많은 양의 데이터를 보내는 것이 더 효율적이지만 예, 게임에 필요한 실시간 스트리밍에는 적합하지 않습니다.
D-side

4
@ joehot200 : Nagle의 알고리즘은 특정 TCP 구현에서 종종 사용하는 지연된 승인 알고리즘과 제대로 상호 작용하지 않습니다. 일부 TCP 구현은 나중에 더 많은 데이터가 올 것으로 예상되면 일부 데이터를 얻은 후 ACK 전송을 지연시킵니다 (나중에 패킷을 승인하면 이전 패킷도 암시 적으로 인식하므로). Nagle의 알고리즘에 따르면 데이터를 보내지 만 승인을받지 못한 경우 장치가 부분 패킷을 보내면 안됩니다. 때때로 두 접근 방식이 서로 잘못 작용하여 상대방이 무언가를하기를 기다릴 때까지 ...
supercat

2
"위생 타이머"는 상황을 시작하고 해결합니다 (1 초 정도).
supercat

2
불행히도 Nagle 알고리즘을 비활성화하면 다른 호스트 측에서 버퍼링을 방지 할 수있는 방법이 없습니다. Nagle의 알고리즘을 비활성화한다고해서 대부분의 사람들이 원하는 recv()send()호출 에 대해 하나의 호출을 받을 수있는 것은 아닙니다 . UDP처럼이를 보장하는 프로토콜을 사용하십시오. "당신이 가진 모든 것이 TCP이면 모든 것이 스트림처럼 보입니다"
Panda Pajama

6

이전의 모든 답변이 잘못되었습니다. 실제로, 하나의 긴 send()통화 또는 여러 개의 작은 send()통화 를 발행하는지는 중요하지 않습니다 .

Phillip이 말했듯이 TCP 세그먼트에는 약간의 오버 헤드가 있지만 애플리케이션 프로그래머는 세그먼트 생성 방법을 제어 할 수 없습니다. 간단히 말해서 :

send()번의 호출이 반드시 하나의 TCP 세그먼트로 변환되는 것은 아닙니다.

운영 체제는 완전히 무료로 모든 데이터를 버퍼와 하나 개의 세그먼트에 보내거나 긴 하나를 가지고 여러 개의 작은 세그먼트로 그것을 깰 수 있습니다.

여기에는 몇 가지 의미가 있지만 가장 중요한 것은 다음과 같습니다.

하나의 send()호출 또는 하나의 TCP 세그먼트가 recv()다른 쪽 끝에서 하나의 성공적인 호출로 변환 될 필요는 없습니다.

그 이유는 TCP가 스트림 프로토콜이라는 것입니다. TCP는 데이터를 긴 바이트 스트림으로 취급하며 "패킷"이라는 개념은 전혀 없습니다. 함께 send()해당 스트림에 바이트를 추가로 recv()당신은 다른 측면 오프 바이트를 얻을. TCP는 데이터가 가능한 한 빨리 다른쪽에 도달 할 수 있도록 데이터를 적절하게 버퍼링하고 분할합니다.

TCP를 사용하여 "패킷"을 보내고 받으려면 패킷 시작 마커, 길이 마커 등을 구현해야합니다. UDP와 같은 메시지 지향 프로토콜을 대신 사용하는 것은 어떻습니까? UDP는 하나의 send()호출이 하나의 전송 된 데이터 그램과 하나의 호출로 변환되도록 보장 recv()합니다!

TCP 만 있으면 모든 것이 스트림처럼 보입니다.


1
각 메시지 앞에 메시지 길이를 붙이는 것은 그리 까다 롭지 않습니다.
ysdx

Nagle의 알고리즘이 적용되는지 여부에 관계없이 패킷 집계와 관련하여 뒤집을 스위치가 하나 있습니다. 언더필 패킷의 신속한 전달을 보장하기 위해 게임 네트워킹에서 꺼지는 것은 드문 일이 아닙니다.
Lars Viklund

이것은 완전히 OS이거나 라이브러리에 따라 다릅니다. 또한 원하는 경우 많은 제어권을 갖습니다. 전체 제어 권한이 없거나 TCP가 항상 두 개의 메시지를 결합하거나 MTU에 맞지 않으면 하나의 메시지를 분할 할 수는 있지만 올바른 방향으로 힌트를 줄 수는 있습니다. 다양한 구성 설정, 수동으로 1 초 간격으로 메시지 전송 또는 데이터 버퍼링을 설정하고 한 번에 전송합니다.
Dorus

@ysdx : 아니오, 송신 측이 아니라 수신 측에 그렇습니다. 데이터를 정확히 얻을 수있는 위치에 대한 보장이 없으므로이를 recv()보완하기 위해 자체 버퍼링을 만들어야합니다. UDP를 통한 신뢰성 구현과 같은 난이도로 순위를 매길 것입니다.
Panda Pajama

@Pagnda Pajama : 수신 측의 순진한 구현은 다음 while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }과 같습니다. 이렇게하는 것이 select훨씬 더 복잡하지는 않으며 TCP를 사용하는 경우 부분 메시지를 버퍼링해야 할 수도 있습니다. UDP를 통해 강력한 안정성을 구현하는 것은 그보다 훨씬 복잡합니다.
ysdx

3

많은 작은 패키지가 좋습니다. 실제로 TCP 오버 헤드가 걱정된다면 bufferstream최대 1500 개의 문자 (또는 TCP MTU가 무엇이든 동적으로 요청하는 것이 가장 좋습니다)를 수집하여 한 곳에서 문제를 처리하십시오. 그렇게하면 다른 패키지를 만들 때마다 ~ 40 바이트의 오버 헤드가 절약됩니다.

즉, 적은 양의 데이터를 전송하는 것이 좋으며 더 큰 객체를 만들면 도움이됩니다. 물론 보내는 "UID:10|1|2|3것보다 보내는 것이 더 작습니다 UID:10;x:1UID:10;y:2UID:10;z:3. 사실,이 시점에서도 휠을 재발 명해서는 안되며 protobuf 와 같은 라이브러리를 사용하여 데이터를 10 바이트 문자열 이하로 줄일 수 있습니다.

Flush스트림에 데이터 추가를 중단하자마자 스트림을 전송하기 전에 무한정 기다릴 수 있으므로 관련 위치에서 스트림에 명령 을 삽입하는 것만 잊지 마십시오 . 클라이언트가 해당 데이터를 기다리는 동안 문제가 발생하고 클라이언트가 다음 명령을 보낼 때까지 서버가 새로운 것을 보내지 않습니다.

패키지 손실은 여기서 약간 영향을 줄 수 있습니다. 전송하는 모든 바이트가 손상 될 수 있으며 TCP는 자동으로 재전송을 요청합니다. 패키지가 작을수록 모든 단일 패키지가 손상 될 가능성이 낮아 지지만 오버 헤드가 증가하기 때문에 더 많은 바이트를 보내므로 패키지 손실 가능성이 훨씬 높아집니다. 패키지가 손실되면 TCP는 누락 된 패키지를 다시 보내고받을 때까지 모든 후속 데이터를 버퍼링합니다. 이로 인해 큰 지연 (핑)이 발생합니다. 패키지 손실로 인한 총 대역폭 손실은 무시할 수 있지만 게임에서는 높은 핑이 바람직하지 않습니다.

결론 : 가능한 한 적은 양의 데이터를 보내고, 큰 패키지를 보내며, 자신의 저수준 방법을 쓰지 말고 bufferstream무거운 리프팅을 처리하기 위해 잘 알려진 라이브러리 와 protobuf 와 같은 방법 을 사용하십시오.


실제로 이와 같은 간단한 것들을 위해 자신의 롤을 쉽게 만듭니다. 다른 사람의 라이브러리를 사용하기 위해 50 페이지의 문서를 살펴 보는 것보다 훨씬 쉽고, 그 후에도 여전히 버그와 문제를 처리해야합니다.
Pacerier

사실, 자신의 글을 작성하는 bufferstream것은 사소한 일이므로 제가 이것을 메소드라고 부릅니다. 여전히 한 곳에서 처리하고 버퍼 로직을 메시지 코드와 통합하지 마십시오. Object Serialization에 관해서는 다른 사람들이 거기에 넣은 수천 시간의 노동 시간보다 더 나은 것을 얻지 못할 것이라고 생각합니다.
Dorus

2

네트워크 프로그래밍의 신생 자이지만 몇 가지 요점을 추가하여 제한된 경험을 공유하고 싶습니다.

  • TCP는 오버 헤드를 의미합니다. 관련 통계를 측정해야합니다.
  • UDP는 네트워크 게임 시나리오를위한 사실상의 솔루션이지만 그에 의존하는 모든 구현에는 패킷 손실 또는 순서가 잘못된 패킷을 처리하는 추가 CPU 측 알고리즘이 있습니다.

측정과 관련하여 고려해야 할 측정 항목은 다음과 같습니다.

언급했듯이, 의미가 제한되지 않고 UDP를 사용할 수 있다는 것을 알게되면 그렇게하십시오. 일부 UDP 기반 구현이 있으므로 수년간의 경험과 입증 된 경험을 바탕으로 휠을 다시 만들거나 작업 할 필요가 없습니다. 언급 할 가치가있는 구현은 다음과 같습니다.

결론 : UDP 구현은 TCP보다 성능이 3 배나 높을 수 있으므로 시나리오가 UDP 친화적 인 것으로 확인되면이를 고려하는 것이 좋습니다. 경고 받다! UDP 위에 전체 TCP 스택을 구현하는 것은 항상 나쁜 생각입니다.


나는 UDP를 사용하고 있었다. 방금 TCP로 전환했습니다. UDP의 패킷 손실은 클라이언트가 필요로하는 중요한 데이터로는 받아 들일 수 없었습니다. UDP를 통해 이동 데이터를 보낼 수 있습니다.
joehot200

따라서 최선의 방법은 다음과 같습니다. 실제로 중요한 작업에만 TCP를 사용하거나 UDP 기반 소프트웨어 프로토콜 구현 (Enet은 단순하고 UDT는 잘 테스트 됨)을 사용하십시오. 그러나 먼저 손실을 측정하고 UDT가 우위를 점할 것인지 결정하십시오.
teodron
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.