2016-09-11 3 views
0

ここでは、ソケットプログラムの作成中に通常どおりにstruct sockaddr *を入力してstruct sockaddr_in *またはstruct sockaddr_in6 *をキャストする方法を示す簡単なプログラムを示します。有効なCコードを使用してstruct sockaddr *をstruct sockaddr_in6 *に変換する正しい方法は何ですか?

#include <stdio.h> 
#include <stdlib.h> 
#include <inttypes.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netdb.h> 

int main() 
{ 
    struct addrinfo *ai; 

    printf("sizeof (struct sockaddr): %zu\n", sizeof (struct sockaddr)); 
    printf("sizeof (struct sockaddr_in): %zu\n", sizeof (struct sockaddr_in)); 
    printf("sizeof (struct sockaddr_in6): %zu\n", sizeof (struct sockaddr_in6)); 

    if (getaddrinfo("localhost", "http", NULL, &ai) != 0) { 
     printf("error\n"); 
     return EXIT_FAILURE; 
    } 

    if (ai->ai_family == AF_INET) { 
     struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr; 
     printf("IPv4 port: %d\n", addr->sin_port); 
    } else if (ai->ai_family == AF_INET6) { 
     struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr; 
     printf("IPv6 port: %d\n", addr->sin6_port); 
    } 

    return 0; 
} 

Beej's Guide to Network Programmingもsockaddr構造体に対処するには10ページ

でこれを推奨しています、プログラマは並列構造作成します:struct sockaddr_in構造体を(「中」「インターネット」用)はIPv4で使用します。

これは重要なビットです。struct sockaddr_inへのポインタは、struct sockaddrへのポインタにキャストすることができます。逆もまた同様です。だからconnect()はstruct sockaddr *を必要としていますが、struct sockaddr_inを使用して直前にキャストすることができます!

しかし、another questionでの議論から、これはちょうどハックであり、C標準の有効なCコードではないようです。特に

、言及AnT's answer、sockaddr構造体の*、構造体のsockaddr_in *と構造体のsockaddr_in6間のキャストとの一般的な技術については

を参照してください* - これらは、C言語とは何の関係もないただのハックです。彼らは実際には動作しますが、C言語に関する限り、その技術は無効です。

したがって、私たちがソケットプログラミングを行うために使用する手法(およびその書籍で推奨されるもの)が無効である場合、それは有効なCコードであるように上記のコードを書き換える有効な方法Cの標準?

+0

* Beej's Guide to Network Programming *は、ネットワークプログラミングのテクニックでは一般的に堅実です。含まれていたものが間違っていたのは稀である。 –

+0

あなたの引用に記載されている方法は、インタフェースによって意図されているものです。 30年以上続いています。他には何も必要ありません。 – EJP

答えて

3

私たちがソケットプログラミングを行う方法(そして書籍によっても推奨される方法)がハックだとすれば、上記のコードを書き換えて正しいCの標準?

TL;あなたの例であなたが提示していることを続けてください。

あなたが提示したコードは構文的に正しいと思われます。状況によっては、未定義の動作が発生する場合もあれば、発生しない場合もあります。それが動作するかどうかは、getaddrinfo()の動作に依存します。

Cでこれを行う方法はありません。すべての機能要件を満たしています。また、あなたが提示した標準的なテクニックよりも未定義の動作に対しても優れています。だからそれが標準的なテクニックです。ここでの問題は、関数が定義されていない型を含め、考えられるすべてのアドレス型をサポートしなければならないことです。これは、ソケットアドレスポインタをキャストする必要のないvoid *と宣言できますが、指定されたプログラムが未定義の動作を示すかどうかは実際には変更されません。

結果に期待されるキャストを使用して不正行為を可能にした場合、それはその問題であるので、その部分については、getaddrinfo()は、心の中で正確な使用に設計されています。さらに、getaddrinfo()はC標準ライブラリの一部ではありません。POSIXによって標準化されており、C標準も組み込まれています。したがって、Cだけでその機能を分析すると、不適切なハイパーフォーカスが実証されます。キャストはCだけでは不安が増しますが、getaddrinfo()と他のPOSIXネットワーク機能のコンテキストでは、struct sockaddr *を使用すると、正しい特定のアドレスタイプにキャストし、参照されたオブジェクトにアクセスすると信頼できる結果が得られるはずです。

また、AnTのあなたの他の質問に対する回答は、単純化され過度に否定的だと思います。私は対照的な答えを書くかどうか検討しています。

+0

以下のコメントはAnTの答えを正確にサポートしています。すべての 'struct sockaddr *'型は、私が引用した透明な共用体を介して解決されます。明らかに誤解を招いていることを考えれば、あなたは対照的な答えを書く価値があるかもしれません。 (私は正義を行うのに十分理解していると主張していません) –

+0

@ DavidC.Rankin、[新しい回答](http://stackoverflow.com/a/39433214/2402272)にもう1つの質問が追加されました。 –

1

POSIX標準では、どんな種類のソケットへのポインタもstruct sockaddr*にキャストできることが保証されています。したがって、bind()またはconnect()で使用するには、struct sockaddr*に任意の種類のソケットへのポインタをキャストできます。ライブラリはチェックするビットを知っています。ソケットのsa_familyフィールドをチェックして、ソケットが有効なデータを含んでいると仮定して、実際の内容を確認してから、適切なポインタ型にキャストすることもできます。あらゆる種類のソケットを安全に保管するために十分大きなメモリブロックを割り当てる必要がある場合は、sockaddr_storageを使用してください。 sockaddr_storage*から他のソケットポインタへのキャストは正しく整列することが保証され、ソケットファミリを含むフィールドは動作することが保証されます。

sockaddr_inからIPv6ソケットを取得するには、IPv4アドレスをIPv6表記に変換してgetaddrinfo()を使用します。しかし、現代のルックアップ機能はおそらくIPv4とIPv6の両方のソケットを含むリンクされたリストを提供します。

0

答えはman getaddrinfosys/socket.hです。 man getaddrinfoは共通struct sockaddrを使用しての背後にある合理的な提供しています。一つだけstruct sockaddrあり

Given node and service, which identify an Internet host and a service, 
getaddrinfo() returns one or more addrinfo structures, each of which 
contains an Internet address that can be specified in a call to bind(2) 
or connect(2). The getaddrinfo() function combines the functionality 
provided by the gethostbyname(3) and getservbyname(3) functions into a 
single interface, but unlike the latter functions, getaddrinfo() is 
reentrant and allows programs to eliminate IPv4-versus-IPv6 dependencies. 

。様々なタイプはすべて、透過的な組合の中で単純に使用され、struct sockaddr_Xが必要になると思われます。例:

/* This is the type we use for generic socket address arguments. 

    With GCC 2.7 and later, the funky union causes redeclarations or 
    uses with any of the listed types to be allowed without complaint. 
    G++ 2.7 does not support transparent unions so there we want the 
    old-style declaration, too. */ 
#if defined __cplusplus || !__GNUC_PREREQ (2, 7) || !defined __USE_GNU 
# define __SOCKADDR_ARG   struct sockaddr *__restrict 
# define __CONST_SOCKADDR_ARG const struct sockaddr * 
#else 
/* Add more `struct sockaddr_AF' types here as necessary. 
    These are all the ones I found on NetBSD and Linux. */ 
# define __SOCKADDR_ALLTYPES \ 
    __SOCKADDR_ONETYPE (sockaddr) \ 
    __SOCKADDR_ONETYPE (sockaddr_at) \ 
    __SOCKADDR_ONETYPE (sockaddr_ax25) \ 
    __SOCKADDR_ONETYPE (sockaddr_dl) \ 
    __SOCKADDR_ONETYPE (sockaddr_eon) \ 
    __SOCKADDR_ONETYPE (sockaddr_in) \ 
    __SOCKADDR_ONETYPE (sockaddr_in6) \ 
    __SOCKADDR_ONETYPE (sockaddr_inarp) \ 
    __SOCKADDR_ONETYPE (sockaddr_ipx) \ 
    __SOCKADDR_ONETYPE (sockaddr_iso) \ 
    __SOCKADDR_ONETYPE (sockaddr_ns) \ 
    __SOCKADDR_ONETYPE (sockaddr_un) \ 
    __SOCKADDR_ONETYPE (sockaddr_x25) 

# define __SOCKADDR_ONETYPE(type) struct type *__restrict __##type##__; 
typedef union { __SOCKADDR_ALLTYPES 
      } __SOCKADDR_ARG __attribute__ ((__transparent_union__)); 
# undef __SOCKADDR_ONETYPE 
# define __SOCKADDR_ONETYPE(type) const struct type *__restrict __##type##__; 
typedef union { __SOCKADDR_ALLTYPES 
      } __CONST_SOCKADDR_ARG __attribute__ ((__transparent_union__)); 
# undef __SOCKADDR_ONETYPE 
#endif 

私はすべてのマクロスープを飲んだわけではありませんが、どちらのタイプでも安全です。

-2

これと他のリンクにも表示されていますIs it legal to type-cast pointers of different struct types (e.g. struct sockaddr * to struct sockaddr_in6 *)?。 これは厳密にはハッキングではありません。あなたがベースに派生からキャストするとき、あなたは派生重複の最初のnバイトと確信している、このように

struct base 
{ 
    int a; 
    char b; 
    double *n; 
} 
struct derived 
{ 
    struct base b; //(no pointer, but the whole struct) 
    int c; 
    int d; 
} 

:右が理解している場合、私はのような何かをするだろう、あなたがやりたい 正確にベース。コードは機能し、完全に移植可能です。 異なる問題の解決策は異なります。実際に私の経験では、ベースには派生したものが含まれていて、その代わりに派生したものは含まれていませんしたがって、 "多形"の構造を持つこと。しかし、1)それが動作する場合、2)人々がコードを理解しようとしていることを読むだろう3)あなたは有用な感じ...なぜですか?すべてあなた次第。おそらくC++はこのように正確にhinerhitanceを実装します!誰がそれを言うことができる?
正しいタイプのインデックスを作成するために、それらの配列に注意してください。最初の場所に置くように注意してください。 (しかし、C++は多態的なオブジェクトの配列に問題がありますが、それらの古いポインタでも可能です)

+0

これは私の質問にどのように関連しているのか分かりません。 'sockaddr_in6'は最初のメンバーとして' sockaddr'オブジェクトを含んでいません。 'sockaddr_in'もありません。 –

+0

と動作しますか?私が知る限り、同じデータ型と重複するだけではありません。すべての必要条件のために、構造は含まれているデータ型よりも大きくなる可能性があります。例えば、2つのフィールドを持つ構造体がある場合、sizeof()は2ではなく4を返します。構造体の先頭を別の構造体に書き換えると、別の2つのchar型が返されます。他とちょうど重なり合う...しかし、...それは変だ。私の主なダブツは、あなたがそれらの配列を宣言することができないということです(これもC++では、これが多相objの基本クラスであると言われています) – jurhas

+0

基本クラスのすべての変数と関数を宣言する必要があります。それらの配列)とそれは直感的ではない、言葉では頑丈に見えない... – jurhas

関連する問題