0

かなり一般的なネットワークコードを書き込もうとしています。いくつかの種類のパケットがあり、それぞれが異なる構造体で表されています。すべての送信が発生する関数は次のようになります。

- (void)sendUpdatePacket:(MyPacketType)packet{ 
    for(NSNetService *service in _services) 
        for(NSData *address in [service addresses]) 
            sendto(_socket, &packet, sizeof(packet), 0, [address bytes], [address length]); 
}

MyPacketTypeパケットだけでなく、あらゆる種類のパケットをこの関数に送信できるようにしたいと考えています。

関数defが次のようになっているのではないかと思いました。

- (void)sendUpdatePacket:(void*)packetRef

パケットへのあらゆる種類のポインタを渡すことができます。しかし、パケットの種類を知らなければ、ポインタを逆参照することはできません。

あらゆる種類のプリミティブ/構造体を引数として受け入れる関数を作成するにはどうすればよいですか?

4

2 に答える 2

4

あなたが達成しようとしているのは、OOの概念であるポリモーフィズムです。

したがって、これはC ++(または他のオブジェクト指向言語)で実装するのは非常に簡単ですが、Cでは少し難しいです。

回避できる1つの方法は、次のような一般的な「パケット」構造を作成することです。

typedef struct {
    void* messageHandler;
    int   messageLength;
    int*  messageData;
} packet;

messageHandlerメンバーがメッセージタイプを処理できるコールバックルーチンへの関数ポインタであり、messageLengthおよびmessageDataメンバーがかなり自明である場合。

に渡すメソッドは、 Tell、Do n't AskpacketStructの原則を使用して、を介して特定のメッセージハンドラーポインタを呼び出し、それを解釈せずに渡します。messageHandlermessageLengthmessageData

ディスパッチ関数(が指すmessageHandler)はメッセージ固有であり、を適切な意味のあるタイプにキャストできるようになりますmessageData。次に、意味のあるフィールドをそこから抽出して処理することができます。

もちろん、これはすべて、継承や仮想メソッドなどを備えたC++でははるかに簡単でエレガントです。


編集:

コメントへの応答:

「messageDataを適切な意味のあるタイプにキャストして、意味のあるフィールドをそこから抽出して処理できるようにするなど」については、少しわかりません。達成されるでしょう。

特定のメッセージタイプのハンドラーを実装し、messageHandlerメンバーをこのハンドラーへの関数ポインターに設定します。例えば:

void messageAlphaHandler(int messageLength, int* messageData)
{
    MessageAlpha* myMessage = (MessageAlpha*)messageData;

    // Can now use MessageAlpha members...
    int messageField = myMessage->field1;
    // etc...
}

messageAlphaHandler()どのクラスでも関数ポインタを簡単に取得できるように定義します。これは、アプリケーションの起動時に行うことができるため、メッセージハンドラーは最初から登録されます。

このシステムが機能するには、すべてのメッセージハンドラーが同じ関数シグネチャ(つまり、戻りタイプとパラメーター)を共有する必要があることに注意してください。

または、そのことについては、そもそも私の構造からmessageDataがどのように作成されるのか。

パケットデータをどのように取得していますか?手動で作成し、ソケットから読み取りますか?いずれにせよ、それをバイトの文字列としてどこかにエンコードする必要があります。int*メンバー( )は、エンコードされたデータの先頭へのmessageData単なるポインターです。メンバーは、このmessageLengthエンコードされたデータの長さです。

メッセージハンドラのコールバックでは、データを生のバイナリ/ 16進データとして操作し続けたくない場合がありますが、代わりにメッセージタイプに従って意味のある方法で情報を解釈します。

それを構造体にキャストすると、基本的に、生のバイナリ情報が、処理しているメッセージのプロトコルに一致する意味のある属性のセットにマップされます。

于 2009-11-19T04:17:05.517 に答える
3

重要なのは、コンピューター内のすべてが単なるバイトの配列(または、ワード、またはダブルワード)であることを理解する必要があるということです。

ZEN MASTER MustARDは机に座って、一見ランダムなキャラクターの複雑なパターンを見つめているモニターを見つめています。学生が近づいています。

学生:マスター?中断してもいいですか?

禅マスターマスタード:あなたはあなた自身の質問に答えました、私の息子。

S:なに?

ZMM:私を邪魔することについてあなたの質問をすることによって、あなたは私を邪魔しました。

S:ああ、ごめんなさい。さまざまなサイズの構造物を場所ごとに移動することについて質問があります。

ZMM:それが本当なら、あなたはそのようなことで優れているマスターに相談するべきです。レーダーの追跡などの大きな金属構造物を場所から場所へ移動するのに優れた知識を持っているMasterDotPuftを訪問することをお勧めします。マスターDotPuftは、羽の重さのひずみゲージのわずかな要素を鳩の息の力で動かすこともできます。右に曲がり、ハイベイのドアに着いたら左に曲がります。そこにはマスターDotPuftが住んでいます。

S:いいえ、私はコンピュータのメモリ内の場所から場所へとさまざまなサイズの大きな構造物を移動することを意味します。

ZMM:必要に応じて、その取り組みを支援する場合があります。問題を説明してください。

S:具体的には、いくつかの異なるタイプの構造体を受け入れたいAC関数があります(それらは異なるタイプのパケットを表します)。したがって、構造体パケットはvoid*として関数に渡されます。しかし、タイプを知らなければ、私はそれらをキャストすることはできませんし、実際には何もできません。socket.hのsento()がまさにそれを行うので、これが解決可能な問題であることを私は知っています:

    ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr,socklen_t dest_len);

sendtoは次のように呼び出されます。

    sendto(socketAddress, &myPacket, sizeof(myPacket), Other args....);

ZMM:Zen Master MANTARにあなたの問題を説明しましたか??

S:ええ、彼は「それはただのポインターです。Cのすべてがポインターです」と言いました。私が彼に説明を求めたとき、彼は「ボク、ボク、私のオフィスから地獄を出して」と言った。

ZMM:本当に、あなたはマスターと話しました。これはあなたを助けませんでしたか?

S:ええと、えー、いや。それから私は禅マスターマックスに尋ねました。

ZMM:賢いのは彼です。あなたへの彼のアドバイスは役に立ちましたか?

S:いいえ。sendto()について聞いたところ、彼は拳を空中で渦巻かせていました。単なるバイトの配列です。」

ZMM:確かに、ZenMasterMaxにはタウがあります。

S:ええ、彼はタウを持っていますが、void *型の関数の引数をどのように処理しますか?

ZMM:学ぶには、まず学習をやめる必要があります。重要なのは、コンピューター内のすべてが単なるバイトの配列(または、ワード、またはダブルワード)であることを理解する必要があるということです。バッファの先頭とバッファの長さへのポインタがわかれば、バッファに配置されているデータの種類を知らなくても、どこにでも送信できます。

S:わかりました。

ZMM:人間が読めるテキストの文字列について考えてみましょう。「あなたは雲を突き刺す塔を計画していますか?最初に謙虚さの基礎を築いてください。」長さは82バイトです。または、おそらく、邪悪なUnicodeが使用されている場合は164です。Unicodeの嘘から身を守ってください!このテキストをsendto()に送信するには、次のように、文字列を含むバッファーの先頭へのポインターと、バッファーの長さを指定します。

char characterBuffer[300];      // 300 bytes
strcpy(characterBuffer, "You plan a tower that will pierce the clouds? Lay first the foundation of humility.");
// note that sizeof(characterBuffer) evaluates to 300 bytes.
sendto(socketAddress, &characterBuffer, sizeof(characterBuffer));

ZMM:文字バッファのバイト数はコンパイラによって自動的に計算されることに注意してください。変数タイプが占めるバイト数は、「size_t」と呼ばれるタイプです。longタイプ「 」または「 」と同等である可能性unsinged intがありますが、コンパイラに依存します。

S:ええと、構造体を送信したい場合はどうなりますか?

ZMM:では、構造体を送信しましょう。

struct
{
     int integerField;          // 4 bytes
     char characterField[300];  // 300 bytes
     float floatField;          // 4 bytes
} myStruct;

myStruct.integerField = 8765309;
strcpy(myStruct.characterField, "Jenny, I got your number.");
myStruct.floatField = 876.5309;

// sizeof(myStruct) evaluates to 4 + 300 + 4 = 308 bytes
sendto(socketAddress, &myStruct, sizeof(myStruct);

S:ええ、それはTCP/IPソケットを介して物事を送信するのに優れています。しかし、受信機能が悪いのはどうですか?文字配列と構造体のどちらを送信しているかをどのように判断できますか?

ZMM:1つの方法は、送信される可能性のあるさまざまなタイプのデータを列挙してから、そのタイプのデータをデータと一緒に送信することです。Zen Mastersは、これを「メタデータ」、つまり「データに関するデータ」と呼んでいます。受信関数は、メタデータを調べて送信されているデータの種類(struct、float、character array)を判別し、この情報を使用してデータを元のタイプにキャストし直す必要があります。まず、送信機能について考えてみましょう。

enum
{
    INTEGER_IN_THE_PACKET =0 ,
    STRING_IN_THE_PACKET =1,  
    STRUCT_IN_THE_PACKET=2
} typeBeingSent;

struct
{
     typeBeingSent dataType;
     char data[4096];
} Packet_struct;

Packet_struct myPacket;

myPacket.dataType = STRING_IN_THE_PACKET;
strcpy(myPacket.data, "Nothing great is ever achieved without much enduring.");
sendto(socketAddress, myPacket, sizeof(Packet_struct);

myPacket.dataType = STRUCT_IN_THE_PACKET;
memcpy(myPacket.data, (void*)&myStruct, sizeof(myStruct);
sendto(socketAddress, myPacket, sizeof(Packet_struct);

S:大丈夫です。

ZMM:では、受信機能と一緒に歩きます。送信されたデータのタイプを照会し、そのタイプで宣言された変数にデータをコピーする必要があります。許してください、でもそのrecvfrom()機能の正確さを忘れてしまいました。

   char[300] receivedString;
   struct myStruct receivedStruct;  

   recvfrom(socketDescriptor, myPacket, sizeof(myPacket);

   switch(myPacket.dataType)
   {
       case STRING_IN_THE_PACKET:
            // note the cast of the void* data into type "character pointer"
            &receivedString[0] = (char*)&myPacket.data; 
            printf("The string in the packet was \"%s\".\n", receivedString); 
            break;

       case STRUCT_IN_THE_PACKET:
            // note the case of the void* into type "pointer to myStruct"
            memcpy(receivedStruct, (struct myStruct *)&myPacket.data, sizeof(receivedStruct));
            break;
    }

ZMM:あなたは悟りを達成しましたか?まず、送信するデータのサイズ(バイト数)をコンパイラーに要求しますsendto()。元のデータのタイプも一緒に送信されます。次に、レシーバーは元のデータのタイプを照会し、それを使用して、「ポインターからボイド」(一般的なポインター)から元のデータのタイプ(int、char []、構造体、等。)

S:まあ、やってみます。

ZMM:安心してください。

于 2009-11-25T01:41:21.470 に答える