2009-06-19 4 views
3

원시 이진 메시지 (매우 간단합니다. 첫 번째 바이트는 메시지 유형이고 나머지는 페이로드입니다)를 사용하는 응용 프로그램에서 작업하고 있습니다. 내가 달성하고자하는 일은 네트워킹 서비스가 나머지 응용 프로그램에서 추상화되어 프로토콜을 지금 수정 한 다음 나머지 응용 프로그램에 너무 많은 영향을주지 않도록 허용하는 것입니다. 응용 프로그램의 컨텍스트는 매우 간단한 클라이언트 - 서버 게임입니다. 지금 클라이언트 작업을하고 있습니다.네트워크 프로토콜 캡쳐

저는 지금 다소 고심하고 있습니다. 예쁜 객체를 반환하는 일종의 번역기/어댑터 서비스에 연결을 던지는 우아한 방법을 찾아야합니다 (필자는 생각합니다). 이러한 객체는 나머지 앱에서 소비를 기다리는 대기열에 던져 질 것입니다. 내가 직면하고있어 문제는 더 많거나 적은이 구조 (의사 코드) :

는의 각 메시지는 20 바이트 가정 해 보자, 그래서 각 20 바이트이 함수 호출을 처리 할 수 ​​있습니다

public Message GetMessage(byte[] buffer) 
{ 
    switch(buffer[0]) 
    { 
    case 1: 
     return Message1(...); 
    case 2: 
     return Message2(...); 
    ..... 

    case n: 
     return MessageN(...); 
    } 
} 

을 당연히 케이스에 상수 나 상수를 사용 하겠지만 그것이 나를 괴롭히는 것은 아닙니다. 여기 있습니다. 약 50 가지의 메시지 유형이 있다고 생각합니다. 이는 50 가지의 사례로 switch 문을 사용할 수 있음을 의미합니다. 나는 이것을 작은 조각으로 잘라내는 적절한 방법을 생각할 수 없다. 이것은 거대한 오류가 발생하기 쉬운 방법이 될 것이다. 내가 이것을 찾을 수 없기 때문에 이것을 더 쉽게 만들 수있는 패턴이 있는지 궁금합니다.

미리 감사드립니다.

답변

1

음, 분명히 많은 방법이 있습니다. 표준 함수는 사전에 함수를 저장하는 것입니다. 기능적 언어로 다음과 같이 쓸 수 있습니다.

import MyProtocol 

handler = { mListFirmware : listFirmwareVersions,     
      mLoadFirmware : startLoadingFirmwareVersion, 
      mLoadFirmwareBl: loadFirmwareVersionBlock, 
      ... 
} 

... 
try { 
    handler[message[0]](message[1:]) 
} catch (NotInTheDictionary e) { 
    # complain ... 
} 

C/C++/C#의 버전이 무엇인지 알 수 없습니다. 함수를 거기에 넣을 수 없다면 함수에 포인터를 넣으십시오. 당신의 기능 중 일부는 매우 작은 경우, 어떤 언어로 당신은 lambda와 바로 다음 넣을 수 있습니다 :

... 
      mLoadFirmware : (lambda (m): start_load(m[1:3]); do_threads()), 
... 

내가 할 것이 더 최적화가 있습니다. 모든 메시지에 대해 상수와 함수 이름이 있습니다.

Messages = new Array() 

def listFirmwareVersions(m): 
     ... 
    Messages.add(Name_Of_This_Method(), This_Method) 
    # it's possible to get name of current function in Python or C# 

... # how to send 
send_message(Messages.lookup(listFirmwareVersions), ...) 

... # how to receive 
try { 
    Messages[message[0]](message[1:]) 
... 

을하지만 철학적으로 정확하고 싶다면, 당신은 핸들러에 대해 별도의 클래스를 가질 수 있습니다 : 당신은하지만, 반복 할 필요가 없습니다 이것은 단순히

class MessageHandler: 
     static int message_handlers = [] 
     int msg 
     Array data 
     void handler 
     Message(a_handler):    
      msg = message_handlers.add(this) 
      handler = a_handler 
     write(Stream s): 
      s.write(msg, data) 

    listFirmwareVersions = new MessageHandler(do_firmware) 
    startLoadingFirmwareVersion = new MessageHandler(load_firmware) 
    ... 

... # how to send 
listFirmwareVersions.send(...) 

... # how to receive 
try { 
     message_handlers[message[0]](message[1:]) 
... 
+0

이것은 별도의 핸들러 클래스를 포함하여 내가 선택한 옵션입니다. 각 개별 메시지를 처리하는 로직 비트를 분리하는 가장 좋은 방법 인 것 같습니다. –

+0

네드 플랑드르 (Ned Flanders)가 말했듯이, 당신은 책으로 모든 것을하고 있습니다. –

1

첫 번째 바이트의 값을 사용하여 색인을 생성하는 50 개의 함수 포인터 (예 : C# 델리게이트) 배열 또는 첫 번째 바이트 값이 키인 델리게이트 사전을 가질 수 있습니다. 이것은 switch 문을 작성하는 또 다른 방법 일뿐입니다.

새 메시지 유형 (새 소스 파일에서 새 클래스 일 수 있음)을 만들 때 소스 코드를 편집하여 큰 switch 문에 새 사례를 추가하는 대신, 기존 메서드를 호출하여 대리자의 정적 컬렉션에 새 대리자를 추가 할 수 있습니다.

2

이 작업을 수행하는 Java 코드가 있습니다. C#으로 쉽게 변환 할 수 있기를 바랍니다. 이것은 자신의 해당 Message 클래스에 메시지 ID에서지도가

private final Map<Byte, Class<? extends Message>> messageMap; 

: 기본적으로, 나는 messageMap 컬렉션이 있습니다. 을 만들 반사를 사용 그때 messageMap에서 인스턴스화하는 데 필요한 Message 클래스를 검색하고, 메시지가 나는 와이어 오프 메시지 ID를 읽을 도착 그런

public void addMessage(int id, Class<? extends Message> messageClass) { 
    messageMap.put((byte) id, messageClass); 
} 

: 나는 각기 다른 메시지 유형에 대해 한 번 addMessage 전화 그 클래스의 인스턴스.

Class<? extends Message> messageClass = messageMap.get(id); 
Message     message  = messageClass.newInstance(); 

여기서 newInstance()은 기본 생성자를 호출합니다.

나는이 메시지 처리 코드를 메시지가 다른 여러 응용 프로그램에서 사용했습니다.

// Messages that we can send to the client. 
addOutgoingMessage(0, HeartbeatMessage.class); 
addOutgoingMessage(1, BeginMessage .class); 
addOutgoingMessage(2, CancelMessage .class); 

// Messages that the client can send. 
addIgnoredMessage (0, HeartbeatMessage.class); 
addIncomingMessage(1, StatusMessage .class, statusMessageHandler); 
addIncomingMessage(2, ProgressMessage .class, progressMessageHandler); 
addIncomingMessage(3, OutputMessage .class, outputMessageHandler); 
addIncomingMessage(4, FinishedMessage .class, finishedMessageHandler); 
addIncomingMessage(5, CancelledMessage.class, cancelledMessageHandler); 
addIncomingMessage(6, ErrorMessage .class, errorMessageHandler); 
1

정확히 C#을 해결하는 동안, 나는 최근에 비슷한 상황 처리 한 : 각각은과 같이 서로 다른 메시지를 등록하는 코드의 좋은, 간단한 블록을 가지고있다. 내 솔루션은 F #을 사용하여 훨씬 쉽게 만들 수있었습니다.

예를 들어, 내 코드는 완벽한 해결책이 아니다이

member private this.processDefaultGroupMessage(m : Message) = 
     try 
      match m.Intro.MessageType with 
      | (1us) -> this.listFirmwareVersions(m)        //ListFirmwareVersions    0 
      | (2us) -> this.startLoadingFirmwareVersion(m)      //StartLoadingFirmwareVersion  1 
      | (3us) -> this.loadFirmwareVersionBlock(m)       //LoadFirmwareVersionBlock   2 
      | (4us) -> this.removeFirmwareVersion(m)        //RemoveFirmwareVersion    3 
      | (5us) -> this.activateFirmwareVersion(m)       //ActivateFirmwareVersion   3   
      | (12us) -> this.startLoadingBitmapLibrary(m)       //StartLoadingBitmapLibrary   2 
      | (13us) -> this.loadBitmapBlock(m)         //LoadBitmapLibraryBlock   2   
      | (21us) -> this.listFonts(m)           //ListFonts       0 
      | (22us) -> this.loadFont(m)           //LoadFont       4 
      | (23us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFont      3 
      | (24us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDefaultFont     3   
      | (31us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListParameterSets     0 
      | (32us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadParameterSets     4 
      | (33us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveParameterSet    3 
      | (34us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateParameterSet    3 
      | (35us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetParameterSet     3   
      | (41us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //StartSelfTest      0 
      | (42us) -> this.ackResponse(m)          //GetStatus (reply with ACK)  0 
      | (43us) -> this.getStatusDetail(m)         //GetStatusDetail     0 
      | (44us) -> this.resetStatus(m)          //ResetStatus      5 
      | (45us) -> this.setDateTime(m)          //SetDateTime      6 
      | (46us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetDateTime      0 
      | (71us) -> this.clearConfiguration(m)        //ClearConfiguration    0 
      | (72us) -> this.defineTextFields(m)         //DefineTextFields     11 
      | (74us) -> this.defineClockFields(m)         //DefineClockFields     13 
      | (80us) -> this.deleteFieldDefinitions(m)       //DeleteFieldDefinitions   14 
      | (91us) -> this.preloadTextFields(m)         //PreloadTextFields     15 
      | (94us) -> this.clearFields(m)          //ClearFields      17 
      | (95us) -> this.activate(m)           //Activate       0 
      | _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED) 
     with 
      | _ -> this.nakResponse(m, VPL_INVALID) 

처럼 보이지만 C#에서 switch 문보다 훨씬 더 나은 보인다. 우리의 전체 응용 프로그램은 csharp로 작성되었지만 메시지 파서는 fsharp로 작성되었습니다.

는 참고 : 우리는 몇 가지 인터페이스를 가지고있다 - RS232 또는 TCP/IP

IDataProcessor를 통해 데이터를 수신 책임 -

IDataTransportServer 이진 데이터를 분석하고, 메시지 클래스의 인스턴스에 돌려 책임을

IMessageProcessor - 메시지 처리 책임자 (이것은 fsharp 모듈 임)

이것이 유용 할 지 모르겠지만, 당신은 이런 종류의 문제를 우리가 어떻게 대처 하는지를 알고 있습니다.

+0

를 작성 덜 자세한 방법을 스위치, 맞지? 그래도 좋은 옵션! –

+0

이봐 요,이 경우는 그렇습니다.하지만 실제로는 훨씬 강력합니다. 그리고 차별 노동 조합을 사용하여 더 나은 노동 조합을 만들 수 있다고 들었습니다. 어쨌든 내가 예제를 찾을 수 있는지 보자. – TimothyP

+0

내가 가진 예제는 pastebin에서 제거되었지만 기본적으로 스위치보다 훨씬 더 강력한 동시에 여러 변수를 "전환"할 수있다. 심지어 제한이있을 수 있습니다. 좋아요 | m> 10 && m < 20 ->() – TimothyP