백엔드 개발 C#.Net 튜토리얼 C#의 TCP 고정 패킷 문제 해결 예

C#의 TCP 고정 패킷 문제 해결 예

Jul 17, 2017 am 11:06 AM
.net

이 글은 주로 C#의 TCP 끈적한 패킷 문제에 대한 해결책을 자세히 소개하며, 관심 있는 친구들은 이를 참고할 수 있습니다.

1 TCP 끈적한 패킷의 원리

1. 이는 송신자가 보낸 여러 데이터 패킷이 수신자가 수신할 때 하나의 패킷에 결합됨을 의미합니다. 수신 버퍼에서 다음 데이터 패킷의 헤드는 이전 데이터 패킷의 테일 바로 뒤에 옵니다. 패킷이 끈적이는 현상에는 여러 가지 이유가 있습니다. 이는 보낸 사람이나 받는 사람에 의해 발생할 수 있습니다.

2. 송신자에 의해 발생하는 끈적한 패킷은 TCP 프로토콜 자체로 인해 발생합니다. TCP는 전송 효율성을 높이기 위해 종종 데이터 패킷을 보내기 전에 송신자에게 충분한 데이터를 수집하도록 요구합니다. 연속해서 여러 번 전송된 데이터가 매우 작은 경우 일반적으로 TCP는 최적화 알고리즘에 따라 데이터를 하나의 패킷으로 결합하여 한 번에 전송하므로 수신자는 고정 패킷 데이터를 수신합니다. 수신측에서 발생하는 끈적끈적한 패킷은 수신측의 사용자 프로세스가 제때에 데이터를 수신하지 못하기 때문에 발생하며, 이로 인해 끈적끈적한 패킷 현상이 발생하게 됩니다.

3. 수신자가 먼저 수신한 데이터를 시스템 수신 버퍼에 넣고, 다음 패킷 시 사용자 프로세스가 이전 데이터 패킷을 가져가지 않은 경우 사용자 프로세스가 버퍼에서 데이터를 가져오기 때문입니다. 데이터가 도착하면 다음 데이터 패킷이 시스템 수신 버퍼에 배치되면 이전 데이터 패킷 이후에 수신되며 사용자 프로세스는 미리 설정된 버퍼 크기에 따라 시스템 수신 버퍼에서 데이터를 가져옵니다. 한 번에 여러 개의 데이터 패킷을 처리합니다. ,

2. 솔루션 원리 및 코드 구현

1. 패킷 헤더(전송 시 동적으로 획득되는 패킷 본문의 길이를 포함하는 고정 길이) + 패킷 본문의 전송 메커니즘을 사용합니다. 그림과 같이

HeaderSize는 패킷 본문의 길이를 저장하며 HeaderSize 자체는 4바이트의 고정 길이입니다.

전체 데이터 패킷(L) = HeaderSize+BodySize

2. 알고리즘

 기본 아이디어는 먼저 수신된 처리 대상 데이터 스트림, 즉 시스템 버퍼 데이터(길이가 M으로 설정됨)를 미리 정해진 구조화된 데이터 형식으로 강제 변환하고 구조화된 데이터 길이 필드 L을 꺼내는 것입니다. 그런 다음 패킷 헤더를 기반으로 첫 번째 패킷 데이터 길이를 계산합니다.

      M=시스템 버퍼 크기; L=사용자가 전송한 데이터 패킷=HeaderSize+BodySize;

1) L

2) L=M이면 데이터 스트림의 내용이 데이터의 완전한 구조(즉, 사용자 정의 버퍼가 시스템 수신 버퍼 크기와 동일함)임을 의미하며, 임시 버퍼에 직접 저장할 수 있습니다.

3) L>M이면 데이터 스트림의 내용이 완전한 구조화된 데이터를 형성하기에 충분하지 않으며 처리하기 전에 다음 데이터 패킷과 병합되어야 함을 의미합니다.

4) 코드 구현은 다음과 같습니다. (데이터 수신은 HP-SOCKET 프레임워크의 서버측을 사용합니다.)

int headSize = 4;//包头长度 固定4
  byte[] surplusBuffer = null;//不完整的数据包,即用户自定义缓冲区
  /// <summary>
  /// 接收客户端发来的数据
  /// </summary>
  /// <param name="connId">每个客户的会话ID</param>
  /// <param name="bytes">缓冲区数据</param>
  /// <returns></returns>
  private HandleResult OnReceive(IntPtr connId, byte[] bytes) 
  {
   //bytes 为系统缓冲区数据
   //bytesRead为系统缓冲区长度
   int bytesRead = bytes.Length;
   if (bytesRead > 0)
   {
    if (surplusBuffer == null)//判断是不是第一次接收,为空说是第一次
     surplusBuffer = bytes;//把系统缓冲区数据放在自定义缓冲区里面
    else
     surplusBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
    //已经完成读取每个数据包长度
    int haveRead = 0;
    //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
    int totalLen = surplusBuffer.Length;
    while (haveRead <= totalLen)
    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够
     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
     if (totalLen - haveRead < headSize)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      //把剩下不够一个完整的数据包存起来
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      surplusBuffer = byteSub;
      totalLen = 0;
      break;
     }
     //如果够了一个完整包,则读取包头的数据
     byte[] headByte = new byte[headSize];
     Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
     int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度

     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
     if (haveRead + headSize + bodySize > totalLen)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      surplusBuffer = byteSub;
      break;
     }
     else
     {
      //挨个分解每个包,解析成实际文字
      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
      //AddMsg(string.Format(" > [OnReceive] -> {0}", strc));
      //依次累加当前的数据包的长度
      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
      {
       surplusBuffer = null;//设置空 回到原始状态
       totalLen = 0;//清0
      }
     }
    }
   }
   return HandleResult.Ok;
  }
로그인 후 복사

이제 언패킹 및 텍스트 파싱 작업이 완료됩니다. 하지만 실제로는 아직 완료되지 않았습니다. 클라이언트가 서버에서 데이터를 수신하기 위한 코드라면 문제가 없습니다.

각 연결의 IntPtr connId 세션 ID를 주의 깊게 살펴보세요

private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
}
로그인 후 복사

그러나 서버 측도 각 데이터 패킷이 생성되는 세션을 구별해야 합니다. 서버 측이 다중 스레드, 다중 사용자 모드이기 때문에 첫 번째 데이터 두 번째 것은 다른 세션의 데이터일 수 있으므로 위의 코드는 단일 세션 모드에서만 작동합니다.

이 문제는 아래에서 해결하겠습니다.

c#safeConcurrentDictionary,

최신 코드

//线程安全的字典
  ConcurrentDictionary<IntPtr, byte[]> dic = new ConcurrentDictionary<IntPtr, byte[]>();
  int headSize = 4;//包头长度 固定4
  /// <summary>
  /// 接收客户端发来的数据
  /// </summary>
  /// <param name="connId">每个客户的会话ID</param>
  /// <param name="bytes">缓冲区数据</param>
  /// <returns></returns>
  private HandleResult OnReceive(IntPtr connId, byte[] bytes) 
  {
   //bytes 为系统缓冲区数据
   //bytesRead为系统缓冲区长度
   int bytesRead = bytes.Length;
   if (bytesRead > 0)
   {
    byte[] surplusBuffer = null;
    if (dic.TryGetValue(connId, out surplusBuffer))
    {
     byte[] curBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
     //更新会话ID 的最新字节
     dic.TryUpdate(connId, curBuffer, surplusBuffer);
     surplusBuffer = curBuffer;//同步
    }
    else
    {
     //添加会话ID的bytes
     dic.TryAdd(connId, bytes);
     surplusBuffer = bytes;//同步
    }

    //已经完成读取每个数据包长度
    int haveRead = 0;
    //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
    int totalLen = surplusBuffer.Length;
    while (haveRead <= totalLen)
    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够
     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
     if (totalLen - haveRead < headSize)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      //把剩下不够一个完整的数据包存起来
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      dic.TryUpdate(connId, byteSub, surplusBuffer);
      surplusBuffer = byteSub;
      totalLen = 0;
      break;
     }
     //如果够了一个完整包,则读取包头的数据
     byte[] headByte = new byte[headSize];
     Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
     int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度

     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
     if (haveRead + headSize + bodySize > totalLen)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      dic.TryUpdate(connId, byteSub, surplusBuffer);
      surplusBuffer = byteSub;
      break;
     }
     else
     {
      //挨个分解每个包,解析成实际文字
      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
      AddMsg(string.Format(" > {0}[OnReceive] -> {1}", connId, strc));
      //依次累加当前的数据包的长度
      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
      {
       byte[] xbtye=null;
       dic.TryRemove(connId, out xbtye);
       surplusBuffer = null;//设置空 回到原始状态
       totalLen = 0;//清0
      }
     }
    }
   }
   return HandleResult.Ok;
  }
로그인 후 복사

를 사용하면 여러 클라이언트 세션으로 인한 수신 혼란을 해결할 수 있습니다. 이 시점에서 모든 작업이 완료되었습니다. 위의 코드는 실제로 그러한 문제를 겪고 싶지 않은 경우 참조 및 학습을 위한 것입니다. 끈적끈적한 패킷 문제를 자동으로 해결하는 HP-SOCKET 통신 프레임워크의 PACK 모델을 직접 사용할 수 있습니다.

위 내용은 C#의 TCP 고정 패킷 문제 해결 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Python 함수 소개: exec 함수 소개 및 예 Python 함수 소개: exec 함수 소개 및 예 Nov 03, 2023 pm 02:09 PM

Python 함수 소개: exec 함수 소개 및 예 소개: Python에서 exec는 문자열이나 파일에 저장된 Python 코드를 실행하는 데 사용되는 내장 함수입니다. exec 함수는 코드를 동적으로 실행하는 방법을 제공하여 프로그램이 런타임 중에 필요에 따라 코드를 생성, 수정 및 실행할 수 있도록 합니다. 이 기사에서는 exec 함수를 사용하는 방법을 소개하고 몇 가지 실용적인 코드 예제를 제공합니다. exec 함수 사용 방법: exec 함수의 기본 구문은 다음과 같습니다. exec

Go 언어 들여쓰기 사양 및 예 Go 언어 들여쓰기 사양 및 예 Mar 22, 2024 pm 09:33 PM

Go 언어의 들여쓰기 사양 및 예 Go 언어는 간결하고 명확한 구문으로 알려져 있으며, 들여쓰기 사양은 코드의 가독성과 아름다움에 중요한 역할을 합니다. 이번 글에서는 Go 언어의 들여쓰기 사양을 소개하고, 구체적인 코드 예시를 통해 자세히 설명하겠습니다. 들여쓰기 사양 Go 언어에서는 들여쓰기에 공백 대신 탭이 사용됩니다. 각 들여쓰기 수준은 하나의 탭이며 일반적으로 4칸의 너비로 설정됩니다. 이러한 사양은 코딩 스타일을 통합하고 팀이 함께 작업하여 컴파일할 수 있도록 합니다.

Oracle DECODE 기능 상세 설명 및 사용 예시 Oracle DECODE 기능 상세 설명 및 사용 예시 Mar 08, 2024 pm 03:51 PM

Oracle의 DECODE 함수는 쿼리 문의 다양한 조건에 따라 다양한 결과를 반환하는 데 자주 사용되는 조건식입니다. 이 기사에서는 DECODE 함수의 구문, 사용법 및 샘플 코드를 자세히 소개합니다. 1. DECODE 함수 구문 DECODE(expr,search1,result1[,search2,result2,...,default]) expr: 비교할 표현식 또는 필드입니다. 검색1,

여러 .NET 오픈 소스 AI 및 LLM 관련 프로젝트 프레임워크 공유 여러 .NET 오픈 소스 AI 및 LLM 관련 프로젝트 프레임워크 공유 May 06, 2024 pm 04:43 PM

오늘날 인공지능(AI) 기술 개발은 본격화되고 있으며, 다양한 분야에서 큰 잠재력과 영향력을 보여주고 있습니다. 오늘 Dayao는 여러분에게 몇 가지 참고 자료를 제공하고자 4개의 .NET 오픈 소스 AI 모델 LLM 관련 프로젝트 프레임워크를 공유할 것입니다. https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.mdSemanticKernelSemanticKernel은 OpenAI, Azure와 같은 대규모 언어 모델(LLM)을 통합하도록 설계된 오픈 소스 소프트웨어 개발 키트(SDK)입니다.

Python 함수 소개: abs 함수의 사용법 및 예 Python 함수 소개: abs 함수의 사용법 및 예 Nov 03, 2023 pm 12:05 PM

Python 함수 소개: abs 함수 사용법 및 예 1. abs 함수 사용법 소개 Python에서 abs 함수는 주어진 값의 절대값을 계산하는 데 사용되는 내장 함수입니다. 숫자 인수를 허용하고 해당 숫자의 절대값을 반환할 수 있습니다. abs 함수의 기본 구문은 다음과 같습니다: abs(x) 여기서 x는 정수 또는 부동 소수점 숫자일 수 있는 절대값을 계산하기 위한 숫자 매개변수입니다. 2. abs 함수의 예 아래에서는 몇 가지 구체적인 예를 통해 abs 함수의 사용법을 보여줍니다. 예 1: 계산

C#의 취업 전망은 어떻습니까? C#의 취업 전망은 어떻습니까? Oct 19, 2023 am 11:02 AM

초보자이든 숙련된 전문가이든 C#을 마스터하면 경력을 쌓는 길이 열릴 것입니다.

Python 함수 소개: eval 함수의 함수 및 예 Python 함수 소개: eval 함수의 함수 및 예 Nov 04, 2023 pm 12:24 PM

Python 함수 소개: eval 함수의 함수 및 예 Python 프로그래밍에서 eval 함수는 매우 유용한 함수입니다. eval 함수는 문자열을 프로그램 코드로 실행할 수 있으며 그 기능은 매우 강력합니다. 이 기사에서는 eval 함수의 세부 기능과 몇 가지 사용 예를 소개합니다. 1. eval 함수의 기능 eval 함수의 기능은 매우 간단합니다. 문자열을 Python 코드로 실행할 수 있습니다. 이는 문자열을 변환할 수 있음을 의미합니다.

Python 함수 소개: isinstance 함수의 사용법 및 예 Python 함수 소개: isinstance 함수의 사용법 및 예 Nov 04, 2023 pm 03:15 PM

Python 함수 소개: isinstance 함수의 사용법 및 예 Python은 프로그래밍을 보다 편리하고 효율적으로 만들기 위해 많은 내장 함수를 제공하는 강력한 프로그래밍 언어입니다. 매우 유용한 내장 함수 중 하나는 isinstance() 함수입니다. 이 기사에서는 isinstance 함수의 사용법과 예를 소개하고 구체적인 코드 예를 제공합니다. isinstance() 함수는 객체가 지정된 클래스나 유형의 인스턴스인지 여부를 확인하는 데 사용됩니다. 이 함수의 구문은 다음과 같습니다

See all articles