例: IPv6 クライアントと IPv4 クライアントの両方から接続を受け入れる
このプログラム例では、IPv4 (AF_INET アドレス・ファミリーを使用するソケット・アプリケーション) および IPv6 (AF_INET6 アドレス・ファミリーを使用するアプリケーション) の両方からの要求を受け入れるサーバー/クライアント・モデルを作成する方法を示します。
現在のところ、 ソケット・アプリケーションは、TCP およびユーザー・データグラム・プロトコル (UDP) を考慮に入れた AF_INET アドレス・ファミリーのみを使用できます。 しかし、これは IPv6 アドレスの使用の増加に伴って、変更される可能性があります。このサンプル・プログラムを使用して、 両方のアドレス・ファミリーに対応するアプリケーションを作成することができます。
以下の図は、このプログラム例がどのように機能するかを示しています。
ソケットのイベントのフロー: IPv4 クライアントと IPv6 クライアントの両方からの要求を受け入れるサーバー・アプリケーション
このフローは、IPv4 クライアントと IPv6 クライアントの両方からの要求を受け入れるソケット・アプリケーションに、 どのような API 呼び出しが含まれており、それぞれが何を行うかを説明したものです。
- socket() API が、 端点を作成するソケット記述子を指定します。さらに、IPv6 をサポートする AF_INET6 アドレス・ファミリーも指定します。 このソケットには、TCP トランスポート (SOCK_STREAM) が使用されます。
- setsockopt() API により、 必要な待ち時間が満了する前にサーバーが再始動した場合に、 アプリケーションはローカル・アドレスを再利用できるようになります。
- bind() API が、
ソケットの固有名を指定します。この例では、プログラマーはアドレスを in6addr_any に設定します。これにより、デフォルトで
ポート 3005 を指定するあらゆる IPv4 クライアントまたは IPv6 クライアントが接続を確立できるようになります (つまり、IPv4 ポート・スペースと IPv6 ポート・スペースの両方にバインドされます)。注: サーバーが IPv6 クライアントのみを処理できればよい場合は、IPV6_ONLY ソケット・オプションを使用できます。
- listen() API により、 サーバーが着信クライアント接続を受け入れられるようになります。この例では、プログラマーはバックログを 10 に設定します。 これは、待ち行列に入れられた接続要求が 10 個に達すると、 システムが着信要求を拒否するようになるということです。
- サーバーは、 着信接続要求を受け入れるために accept() API を使用します。 accept() 呼び出しは、IPv4 クライアントまたは IPv6 クライアントからの着信接続を待機して、 無期限にブロックします。
- getpeername() API が、 クライアントのアドレスをアプリケーションに戻します。IPv4 クライアントの場合、アドレスは IPv4 がマップした IPv6 アドレスとして表示されます。
- recv() API が、 クライアントから 250 バイトのデータを受信します。この例では、クライアントは 250 バイトのデータを送信します。そのため、 プログラマーは SO_RCVLOWAT ソケット・オプションを使用し、250 バイトのデータがすべて到着するまで recv() API がウェイクアップしないように指定します。
- send() API が、クライアントにデータを送り返します。
- close() API が、 オープンしているソケット記述子をすべてクローズします。
ソケットのイベントのフロー: IPv4 クライアントまたは IPv6 クライアントからの要求
注: このクライアントの例は、IPv4 ノードまたは IPv6 ノードの要求を受け入れる、
他のサーバー・アプリケーション設計と一緒に使用できます。
このクライアント例では、他のサーバー設計も使用することができます。
- inet_pton() 呼び出しが、 テキスト形式のアドレスをバイナリー形式に変換します。この例では、以下の 2 つの呼び出しを発行します。 最初の呼び出しは、サーバーが有効な AF_INET アドレスかどうかを判別します。 2 番目の inet_pton() 呼び出しは、 サーバーが AF_INET6 アドレスを持っているかを判別します。アドレスが数値の場合、getaddrinfo() がネーム・レゾリューションをしないようにする必要があります。そうしなければ、getaddrinfo() 呼び出しが発行されるときに、 提供されたホスト名を解決することが必要になります。
- getaddrinfo() 呼び出しが、 後続の socket() 呼び出しと connect() 呼び出しに必要なアドレス情報を検索します。
- socket() API が、端点を表すソケット記述子を戻します。さらにステートメントは、getaddrinfo() API 呼び出しから戻される情報を使用して、 アドレス・ファミリー、ソケット・タイプ、およびプロトコルも識別します。
- サーバーが IPv4 か IPv6 であるかにかかわらず、connect() API がサーバーへの接続を確立します。
- send() API が、 サーバーにデータ要求を送信します。
- recv() API が、 サーバー・アプリケーションからデータを受信します。
- close() API が、 オープンしているソケット記述子をすべてクローズします。
以下のサンプル・コードは、このシナリオのサーバー・アプリケーションです。
注: この例の使用をもって、コードに関するライセンス情報および特記事項の条件に同意したものとします。
/**************************************************************************/
/* Header files needed for this sample program */
/**************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/**************************************************************************/
/* Constants used by this program */
/**************************************************************************/
#define SERVER_PORT 3005
#define BUFFER_LENGTH 250
#define FALSE 0
void main()
{
/***********************************************************************/
/* Variable and structure definitions. */
/***********************************************************************/
int sd=-1, sdconn=-1;
int rc, on=1, rcdsize=BUFFER_LENGTH;
char buffer[BUFFER_LENGTH];
struct sockaddr_in6 serveraddr, clientaddr;
int addrlen=sizeof(clientaddr);
char str[INET6_ADDRSTRLEN];
/***********************************************************************/
/* A do/while(FALSE) loop is used to make error cleanup easier. The */
/* close() of each of the socket descriptors is only done once at the */
/* very end of the program. */
/***********************************************************************/
do
{
/********************************************************************/
/* The socket() function returns a socket descriptor, which */
/* represents an endpoint. Get a socket for address family */
/* AF_INET6 to prepare to accept incoming connections on. */
/********************************************************************/
if ((sd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
{
perror("socket() failed");
break;
}
/********************************************************************/
/* The setsockopt() function is used to allow the local address to */
/* be reused when the server is restarted before the required wait */
/* time expires. */
/********************************************************************/
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,
(char *)&on,sizeof(on)) < 0)
{
perror("setsockopt(SO_REUSEADDR) failed");
break;
}
/********************************************************************/
/* After the socket descriptor is created, a bind() function gets a */
/* unique name for the socket. In this example, the user sets the */
/* address to in6addr_any, which (by default) allows connections to */
/* be established from any IPv4 or IPv6 client that specifies port */
/* 3005. (that is, the bind is done to both the IPv4 and IPv6 */
/* TCP/IP stacks). This behavior can be modified using the */
/* IPPROTO_IPV6 level socket option IPV6_V6ONLY if required. */
/********************************************************************/
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin6_family = AF_INET6;
serveraddr.sin6_port = htons(SERVER_PORT);
/********************************************************************/
/* Note: applications use in6addr_any similarly to the way they use */
/* INADDR_ANY in IPv4. A symbolic constant IN6ADDR_ANY_INIT also */
/* exists but can only be used to initialize an in6_addr structure */
/* at declaration time (not during an assignment). */
/********************************************************************/
serveraddr.sin6_addr = in6addr_any;
/********************************************************************/
/* Note: the remaining fields in the sockaddr_in6 are currently not */
/* supported and should be set to 0 to ensure upward compatibility. */
/********************************************************************/
if (bind(sd,
(struct sockaddr *)&serveraddr,
sizeof(serveraddr)) < 0)
{
perror("bind() failed");
break;
}
/********************************************************************/
/* The listen() function allows the server to accept incoming */
/* client connections. In this example, the backlog is set to 10. */
/* This means that the system will queue 10 incoming connection */
/* requests before the system starts rejecting the incoming */
/* requests. */
/********************************************************************/
if (listen(sd, 10) < 0)
{
perror("listen() failed");
break;
}
printf("Ready for client connect().\n");
/********************************************************************/
/* The server uses the accept() function to accept an incoming */
/* connection request. The accept() call will block indefinitely */
/* waiting for the incoming connection to arrive from an IPv4 or */
/* IPv6 client. */
/********************************************************************/
if ((sdconn = accept(sd, NULL, NULL)) < 0)
{
perror("accept() failed");
break;
}
else
{
/*****************************************************************/
/* Display the client address. Note that if the client is */
/* an IPv4 client, the address will be shown as an IPv4 Mapped */
/* IPv6 address. */
/*****************************************************************/
getpeername(sdconn, (struct sockaddr *)&clientaddr, &addrlen);
if(inet_ntop(AF_INET6, &clientaddr.sin6_addr, str, sizeof(str))) {
printf("Client address is %s\n", str);
printf("Client port is %d\n", ntohs(clientaddr.sin6_port));
}
}
/********************************************************************/
/* In this example we know that the client will send 250 bytes of */
/* data over. Knowing this, we can use the SO_RCVLOWAT socket */
/* option and specify that we don't want our recv() to wake up */
/* until all 250 bytes of data have arrived. */
/********************************************************************/
if (setsockopt(sdconn, SOL_SOCKET, SO_RCVLOWAT,
(char *)&rcdsize,sizeof(rcdsize)) < 0)
{
perror("setsockopt(SO_RCVLOWAT) failed");
break;
}
/********************************************************************/
/* Receive that 250 bytes of data from the client */
/********************************************************************/
rc = recv(sdconn, buffer, sizeof(buffer), 0);
if (rc < 0)
{
perror("recv() failed");
break;
}
printf("%d bytes of data were received\n", rc);
if (rc == 0 ||
rc < sizeof(buffer))
{
printf("The client closed the connection before all of the\n");
printf("data was sent\n");
break;
}
/********************************************************************/
/* Echo the data back to the client */
/********************************************************************/
rc = send(sdconn, buffer, sizeof(buffer), 0);
if (rc < 0)
{
perror("send() failed");
break;
}
/********************************************************************/
/* Program complete */
/********************************************************************/
} while (FALSE);
/***********************************************************************/
/* Close down any open socket descriptors */
/***********************************************************************/
if (sd != -1)
close(sd);
if (sdconn != -1)
close(sdconn);
}