바이트 스트림에서 가변 길이 설명자를 구문 분석하고 유형에 따라 행동합니다.

StackOverflow https://stackoverflow.com/questions/1612918

문제

나는 코드에서 다양한 structs/classes로 표현하는 일련의 변수 길이 설명자가 포함 된 바이트 스트림에서 읽고 있습니다. 각 디스크립터에는 다른 모든 디스크립터와 공통된 고정 된 길이 헤더가 있으며, 이는 유형을 식별하는 데 사용됩니다.

각 디스크립터를 가장 잘 구문 분석하고 표현하는 데 사용할 수있는 적절한 모델 또는 패턴이 있습니까? 그런 다음 유형에 따라 적절한 조치를 수행합니까?

도움이 되었습니까?

해결책

나는 이러한 유형의 파서를 많이 썼습니다.

고정 길이 헤더를 읽은 다음 간단한 스위치 케이스를 사용하여 구조물로 올바른 생성자로 파견하여 고정 헤더와 스트림을 해당 생성자로 전달하여 스트림의 가변 부분을 소비 할 수 있습니다.

다른 팁

이것은 파일 구문 분석에서 일반적인 문제입니다. 일반적으로, 당신은 읽습니다 모두 다 아는 디스크립터의 일부 (운 좋게도이 경우 고정 길이이지만 항상 그런 것은 아닙니다). 일반적으로 나는 a를 사용한다 전략 패턴 여기서는 일반적으로 시스템이 광범위하게 유연 할 것으로 기대하지만 직선 스위치 나 공장도 작동 할 수 있습니다.

다른 질문은 : 당신은 다운 스트림 코드를 제어하고 신뢰합니까? 의미 : 공장 / 전략 구현? 그렇게한다면, 당신은 그들에게 스트림과 소비 할 것으로 예상되는 바이트 수를 줄 수 있습니다 (아마도 디버그 어설 션을 제자리에두고 그들을 확인하십시오. 하다 정확히 올바른 금액을 읽으십시오).

만약 너라면 캔트 공장/전략 구현을 신뢰합니다 (아마도 사용자 코드가 사용자 정의 사막화제를 사용할 수 있습니다). 그러면 스트림 위에 래퍼를 구성합니다 (예시: SubStream Protobuf-Net에서), 이로 인해 예상 바이트 수를 소비 할 수 있으며 (나중에보고),이 블록 이외의 외부 작업을 허용하지 않습니다. 또한 충분한 데이터가 소비되었음을 런타임 확인 (릴리스 빌드에서도)을 가지고있을 것입니다. 그러나이 경우에는 읽지 않은 데이터를 지나서 읽을 것입니다. 즉, 다운 스트림 코드가 20 바이트를 소비 할 것으로 예상되지만 12 만 읽습니다. 그런 다음 다음 8을 건너 뛰고 다음 설명자를 읽으십시오.

그것에 대한 확장; 여기서 하나의 전략 디자인은 다음과 같은 것일 수 있습니다.

interface ISerializer {
    object Deserialize(Stream source, int bytes);
    void Serialize(Stream destination, object value);
}

예상 마커 당 해당 시리얼 라이저의 사전 (또는 숫자가 작은 경우 목록)을 작성하고 직렬화를 해결 한 다음 호출 할 수 있습니다. Deserialize 방법. 마커를 인식하지 못하면 (중 하나) :

  • 주어진 수의 바이트를 건너 뜁니다
  • 오류를 던지십시오
  • 여분의 바이트를 버퍼에 어딘가에 저장하십시오 (예기치 않은 데이터의 왕복 허용).

위의 내용에 대한 측면 노트로 -이 접근법 (전략)은 시스템을 반사 또는 런타임 DSL (ETC)을 통해 런타임에 결정하는 경우 유용합니다. 시스템이 있다면 전적으로 컴파일 타임에 예측 가능 (변경되지 않거나 코드 생성을 사용하고 있기 때문에) switch 접근 방식이 더 적절할 수 있습니다. 적절한 코드를 직접 주입 할 수 있으므로 추가 인터페이스가 필요하지 않을 수 있습니다.

스트림에서 읽고 유효한 헤더/메시지를 감지하지 않는다면 다시 시도하기 전에 첫 바이트 만 버리십시오. 여러 번 나는 전체 패킷이나 메시지가 대신 버려지는 것을 보았으므로 유효한 데이터가 손실 될 수 있습니다.

이것은 그것이 일자리 일 것 같다. 공장 방법 아니면 아마도 초록 공장. 헤더를 기준으로 호출 할 공장 방법을 선택하고 관련 유형의 객체를 반환합니다.

스위치 문에 생성자를 추가하는 것보다 이것이 더 나은지 여부는 생성하는 객체의 복잡성과 균일성에 따라 다릅니다.

내가 제안 할게:

fifo = Fifo.new

while(fd is readable) {
  read everything off the fd and stick it into fifo
  if (the front of the fifo is has a valid header and 
      the fifo is big enough for payload) {

      dispatch constructor, remove bytes from fifo
  }
}

이 방법으로 :

  • 나쁜 페이로드를 확인하는 오류를 수행하고 잠재적으로 나쁜 데이터를 버릴 수 있습니다.
  • FD의 읽기 버퍼에서 데이터가 기다리고 있지 않습니다 (큰 페이로드의 문제가 될 수 있음)

좋은 oo가 되려면 객체 계층에서 방문자 패턴을 사용할 수 있습니다. 내가 어떻게했는지는 이와 같았습니다 (네트워크에서 캡처 한 패킷을 식별하기 위해 필요한 것과 거의 똑같은 것) :

  • 하나의 부모 클래스와 함께 거대한 객체 계층

  • 각 클래스는 부모와 함께 등록하는 정적 반대자가 있으므로 부모는 직접 어린이에 대해 알고 있습니다 (이것은 C ++입니다.이 단계는 반사 지원이 좋은 언어에서 필요하지 않다고 생각합니다).

  • 각 클래스에는 바이 테스트의 나머지 부분을 얻은 정적 생성자 방법이 있었으며 그에 따라 해당 데이터를 처리 할 책임이 있는지 여부를 결정했습니다.

  • 패킷이 들어 왔을 때, 나는 단순히 주 부모 클래스 (패킷이라고 함)의 정적 생성자 메소드로 전달했는데, 그 패킷을 처리해야 할 책임이 있는지 모든 어린이들을 확인했으며, 이것은 하나가 될 때까지 재귀 적으로 진행되었습니다. 계층 구조의 맨 아래에있는 클래스는 인스턴스화 된 클래스를 되돌 렸습니다.

  • 각 정적 "생성자"메소드는 자체 헤더를 바탕부터 자체 헤더에서 자르고 탑재량 만 어린이에게 전달했습니다.

이 접근법의 상승점은 객체 계층의 어느 곳에서나 새로운 유형을 추가 할 수 있다는 것입니다. 없이 보고/변경해야합니다 어느 다른 수업. 그것은 패킷에 놀랍도록 훌륭하고 잘 작동했습니다. 이렇게 갔다 :

  • 패킷
  • 이더넷 패킷
  • ippacket
  • UDPPACKET, TCPPACKET, ICMPPACKET
  • ...

나는 당신이 아이디어를 볼 수 있기를 바랍니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top