web-dev-qa-db-ja.com

Cのソケットを介して構造体を渡す

クライアントからサーバーへ、またはその逆に構造全体を渡そうとしています。次のように私の構造を仮定しましょう

struct temp {
  int a;
  char b;
}

sendtoを使用し、構造変数のアドレスを送信し、recvfrom関数を使用して反対側でそれを受信して​​います。しかし、受信側で送信された元のデータを取得することはできません。 sendto関数では、受け取ったデータをstruct temp型の変数に保存しています。

n = sendto(sock, &pkt, sizeof(struct temp), 0, &server, length);
n = recvfrom(sock, &pkt, sizeof(struct temp), 0, (struct sockaddr *)&from,&fromlen);

ここで、pktはstruct temp型の変数です。

Eventhough私は8バイトのデータを受信して​​いますが、印刷しようとすると、単にゴミの値が表示されます。修正に関するヘルプはありますか?

注:サードパーティのライブラリを使用する必要はありません。

EDIT1:このシリアライゼーションの概念は本当に新しい..しかし、シリアライゼーションをせずにソケット経由で構造体を送信できませんか?

EDIT2:-を使用して文字列または整数変数を送信しようとするとsendtoおよびrecvfrom機能受信側でデータを適切に受信しています。構造の場合はなぜですか?シリアル化機能を使用する必要がない場合、構造体のすべてのメンバーを個別に送信する必要がありますか?これは実際には適切なソリューションではありません。メンバーが「n」個ある場合、データを送信または受信するためだけに「n」行のコードが追加されるからです。

47
codingfreak

これは非常に悪い考えです。バイナリデータは常に次の方法で送信する必要があります。

構造体全体を、ファイルやソケットではなく、バイナリ形式で記述しないでください。

常に各フィールドを個別に書き込み、同じ方法で読み取ります。

次のような機能が必要です

_unsigned char * serialize_int(unsigned char *buffer, int value)
{
  /* Write big-endian int value into buffer; assumes 32-bit int and 8-bit char. */
  buffer[0] = value >> 24;
  buffer[1] = value >> 16;
  buffer[2] = value >> 8;
  buffer[3] = value;
  return buffer + 4;
}

unsigned char * serialize_char(unsigned char *buffer, char value)
{
  buffer[0] = value;
  return buffer + 1;
}

unsigned char * serialize_temp(unsigned char *buffer, struct temp *value)
{
  buffer = serialize_int(buffer, value->a);
  buffer = serialize_char(buffer, value->b);
  return buffer;
}

unsigned char * deserialize_int(unsigned char *buffer, int *value);
_

または同等の方法で、バッファ管理などに関してこれを設定する方法がいくつかあります。次に、構造体全体をシリアライズ/デシリアライズする高レベルの関数を実行する必要があります。

これは、シリアル化がバッファとの間で行われることを前提としているため、最終的な宛先がファイルかソケットかをシリアル化する必要はありません。また、メモリのオーバーヘッドをいくらか支払うことを意味しますが、一般的にパフォーマンス上の理由から適切な設計です(各値をソケットにwrite()したくない)。

上記を取得したら、構造インスタンスをシリアル化して送信する方法を次に示します。

_int send_temp(int socket, const struct sockaddr *dest, socklen_t dlen,
              const struct temp *temp)
{
  unsigned char buffer[32], *ptr;

  ptr = serialize_temp(buffer, temp);
  return sendto(socket, buffer, ptr - buffer, 0, dest, dlen) == ptr - buffer;
}
_

上記について注意すべきいくつかの点:

  • 送信する構造体は、最初にフィールドごとにbufferにシリアル化されます。
  • シリアル化ルーチンは、バッファー内の次の空きバイトへのポインターを返します。このポインターを使用して、シリアル化されたバイト数を計算します
  • 明らかに、私の例のシリアライゼーションルーチンは、バッファオーバーフローから保護しません。
  • sendto()呼び出しが成功した場合の戻り値は1であり、そうでない場合は0になります。
69
unwind

「プラグマ」パックオプションを使用すると問題は解決しましたが、依存関係があるかどうかわかりません。

#pragma pack(1)   // this helps to pack the struct to 5-bytes
struct packet {
int i;
char j;
};
#pragma pack(0)   // turn packing off

その後、次のコード行は問題なく正常に機能しました

n = sendto(sock,&pkt,sizeof(struct packet),0,&server,length);

n = recvfrom(sock, &pkt, sizeof(struct packet), 0, (struct sockaddr *)&from, &fromlen);
10
codingfreak

shortおよびlong整数型用の独自のシリアル化ルーチンを作成する必要はありません-htons()/htonl() POSIX関数を使用してください。

7
qrdl

シリアル化コードを自分で記述したくない場合は、適切なシリアル化フレームワークを見つけて使用してください。

たぶんGoogleの プロトコルバッファ は可能でしょうか?

6
Douglas Leeder

シリアル化は良いアイデアです。 Wireshark を使用して、トラフィックを監視し、実際にパケットで渡されるものを理解することもできます。

1
eyalm

シリアル化する代わりに、サードパーティのライブラリに依存するため、タグ、長さ、および値を使用してプリミティブプロトコルを簡単に作成できます。

Tag: 32 bit value identifying the field
Length: 32 bit value specifying the length in bytes of the field
Value: the field

必要に応じて連結します。タグに列挙を使用します。そして、ネットワークバイトオーダーを使用して...

エンコード、デコードが簡単です。

また、TCPを使用する場合、データはstreamであるため、たとえば3パケットを送信する場合、必ずしも3パケットを受信する必要はありません。とりわけnodelay/nagelアルゴリズムに依存するストリームであり、すべてを1回の受信で取得できます。たとえば、RFC1006を使用してデータを区切る必要があります。

UDPの方が簡単です。送信されたパケットごとに個別のパケットを受け取りますが、安全性ははるかに低くなります。

0
hplbsh

転送するデータの形式が非常に単純な場合、ANSI文字列との間の変換は単純で移植性があります。

0
the_mandrill