TCP IP ネットワークプログラミング (3) アドレスファミリーとデータシーケンス

ソケットに割り当てられたIPアドレスとポート番号

ウェブサイトアドレス

IP アドレスは、次の 2 つのカテゴリに分類されます。

  • IPv4 4バイトアドレスファミリー
  • IPv6 16 バイトのアドレス ファミリ

IPv4 と IPv6 の違いは主に IP アドレスで使用されるバイト数であり、現在一般的なアドレス ファミリは IPv4 ですが、IPv6 は IP アドレス枯渇の問題に対処するために提案された規格であり、現在は IPv4 が主に使用されています。

IPv4規格の4バイトのIPアドレスは、ネットワークアドレスとホストアドレスに分かれ、A、B、C、Dなどの種類に分かれます。

A类		网络ID 主机ID 主机ID 主机ID
B类		网络ID 网络ID 主机ID 主机ID
C类		网络ID 网络ID 网络ID 主机ID
D类		网络ID 网络ID 网络ID 网络ID (多播IP地址)

ネットワークアドレスの分類とホストアドレスの境界

ネットワーク アドレスが占めるバイト数は、IP アドレスの最初のバイトによって決まります。

  • クラス A アドレスの最初のバイト範囲: 0 ~ 127
  • クラス B アドレスの最初のバイト範囲: 128 ~ 191
  • クラス C アドレスの最初のバイト範囲: 191 ~ 223

別の表現方法もあるよ

  • クラス A アドレスは 0 で始まります
  • クラス B アドレスは 10 で始まります
  • クラス C アドレスは 110 で始まります

住所情報の表現

IPv4アドレスを表す構造体

struct sockaddr_in
{
    sa_family_t     sin_family;  //地址族
    uint16_t        sin_port;    //16位TCP/UDP端口号
    struct in_addr  sin_addr;    //32位IP地址
    char            sin_zero[8]; //不使用
};

この構造体は、次のように定義された別の構造体 in_addr について言及しています。

struct in_addr
{
    in_addr_t        s_addr;     //32位IPv4地址
}

上記 2 つの構造体にはいくつかのデータ型が含まれていますが、uint16_t、in_addr_t およびその他の型については、POSIX を参照してください。POSIX は UNIX システムを動作させるための標準です

POSIXで定義されているデータ型

データ型名 データ型の説明 宣言ヘッダーファイル
int8_t 符号付き 8 ビット整数 sys/types.h
uint8_t 符号なし 8 ビット整数 sys/types.h
int16_t 符号付き 16 ビット int sys/types.h
uint16_t 符号なし 16 ビット int sys/types.h
int32_t 符号付き 32 ビット int sys/types.h
uint32_t 符号なし 32 ビット int sys/types.h
sa_family_t アドレスファミリー sys/socket.h
ソックレン length(構造体の長さ) sys/socket.h
in_addr_t IPアドレス、uint32_t ネットネット/in.h
in_port_t ポート番号、uint16_t ネットネット/in.h

構造体 sockaddr_in のメンバー分析

メンバー sin_family

各プロトコル ファミリは異なるプロトコル ファミリに適用できます。たとえば、IPv4 は 4 バイトのアドレス ファミリを使用し、IPv6 は 16 バイトのアドレス ファミリを使用します。

アドレスファミリー

アドレスファミリー 意味
OF_INET IPv4 ネットワーク プロトコルで使用されるアドレス ファミリ
AF_INET6 IPv6 ネットワーク プロトコルで使用されるアドレス ファミリ
AF_LOCAL ローカル通信で使用される UNIX プロトコルのアドレス ファミリ

AF_LOCAL は、複数のアドレス ファミリがあることを示すために追加されました。

メンバー sin_port

このメンバーには 16 ビットのポート番号が格納されますが、重要なのは、ネットワーク バイト オーダーで格納されていることです。

メンバー sin_addr

このメンバーは、32 ビットの IP アドレス情報をネットワーク バイト オーダーで保存し、構造体 in_addr を管理します。

メンバー sin_zero

特に意味はありません。構造体 sockaddr_in のサイズを sockaddr 構造体の記憶域と一致させるために挿入されるメンバーです。必ず 0 で埋めてください。

ネットワークのバイトオーダーとアドレス変換

バイトオーダーとネットワークバイトオーダー

CPU がデータを保存する方法は 2 つあります。これは、CPU がデータを解析する方法も 2 つあることを意味します。

  • ビッグエンディアン: 上位バイトが下位アドレスに格納されます。
  • リトルエンディアン: 上位バイトは上位アドレスに格納されます。

ネットワーク上でデータを送信する際の統一方式を取り決めること、これをネットワークバイトオーダーと呼び、ビッグエンディアンオーダーに統一します。

要約すると、データ配列をネットワーク経由で送信する前にビッグ エンディアン形式に転送します。データを受信するとき、すべてのコンピュータはデータがネットワーク バイト オーダー形式であることを認識する必要があります。データをリトル エンディアン システムで送信する場合は、ビッグエンディアン配列に変換する必要があります。

バイトオーダー変換

sockadr_in 構造体を埋める前にデータをネットワーク バイト オーダーに変換し、バイト オーダーの変換を支援する関数を導入します。

unsigned short htons(unsigned short)
unsigned short ntohs(unsigned short)
unsigned long htonl(unsigned short)
unsined long ntohl(unsigned long)

関数名の意味

  • htons の h はホストのバイトオーダーを表します
  • hton の n はネットワークのバイトオーダーを表します
  • htons の s は短いことを意味します
  • htons の l は、long を表します (Linux の long 型は 4 バイトを占めます)。
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	unsigned short host_port=0x1234;
	unsigned short net_port;
	unsigned long host_addr=0x12345678;
	unsigned long net_addr;
	
	net_port=htons(host_port);
	net_addr=htonl(host_addr);
	
	printf("Host ordered port: %#x \n", host_port);
	printf("Network ordered port: %#x \n", net_port);
	printf("Host ordered address: %#lx \n", host_addr);
	printf("Network ordered address: %#lx \n", net_addr);
	return 0;
}

以下はリトルエンディアン CPU で実行した結果です。ビッグエンディアン CPU で実行している場合、変数値は変更されません。Intel と AMD シリーズの CPU は両方ともリトル エンディアン標準を採用しているため、ほとんどの友人は同様の実行結果を得るでしょう。

gcc endian_conv.c -o conv
./conv
输出:
Host ordered port : 0x1234
Network ordered port : 0x3412
Hostordered address : 0x12345678
Network ordered address : 0x78563412

ネットワークアドレスの初期化と割り当て

文字列情報をネットワークバイトオーダー整数型に変換する

IP アドレスの表現では、整数データ表現ではなく、ドット付き 10 進表記がよく知られています。幸いなことに、文字列形式の IP アドレスを 32 ビット整数データに変換し、型を変換しながらネットワーク バイト オーダー変換を実行するのに役立つ関数があります。

#include<arpa/inet.h>
in_addr_t inet_addr(const char * string);
		//成功时返回32位大端序整数型值,失败时返回INADDR_NONE。

この関数の呼び出し処理

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	char *addr1="127.212.124.78";
	char *addr2="127.212.124.256";

	unsigned long conv_addr=inet_addr(addr1);
	if(conv_addr==INADDR_NONE)
		printf("Error occured! \n");
	else
		printf("Network ordered integer addr: %#lx \n", conv_addr);
	
	conv_addr=inet_addr(addr2);
	if(conv_addr==INADDR_NONE)
		printf("Error occureded \n");
	else
		printf("Network ordered integer addr: %#lx \n\n", conv_addr);
	return 0;
}

この結果から、inet_addr 関数は IP アドレスを 32 ビット整数型に変換できるだけでなく、無効な IP アドレスを検出できることがわかります。

inet_aton 関数は、inet_addr 関数と機能的に同一であり、文字列 IP アドレスを 32 ビット ネットワーク バイト オーダーの整数に変換して返します。違いは、この関数は in_addr 構造体を使用し、より頻繁に使用されることです。

#include<arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
		成功时返回1,失败时返回0
		参数1:string,含有需转换的IP地址信息的字符串地址值。
		参数2:addr,将保存转换结果的in_addr结构体变量的地址值。

この関数の呼び出し処理

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
	char *addr="127.232.124.79";
	struct sockaddr_in addr_inet;
	
	if(!inet_aton(addr, &addr_inet.sin_addr))
		error_handling("Conversion error");
	else
		printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

inet_addr 関数を呼び出して、変換された IP アドレス情報を返し、それを sockaddr_in 構造体で宣言された in_addr 構造体変数に保存します。inet_aton 関数では、結果が構造体変数に自動的に格納されるため、このプロセスは必要ありません。

inet_aton() の逆に、ネットワークのバイトオーダーの整数 IP アドレスを使い慣れた文字列形式に変換できる関数もあります。

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
	成功返回转换的字符串地址值,失败返回-1
  • この関数は、パラメータを通じて渡された整数の IP アドレスを文字列形式に変換して返します。

  • ただし、戻り値は char ポインタであり、文字列アドレスが返されるということは、文字列がメモリ空間に保存されたことを意味しますが、この関数はプログラマにメモリの割り当てを要求するのではなく、内部的にメモリを適用して保存することに注意してください。文字列。つまり、この関数が呼び出されると、情報はすぐに他のメモリ空間にコピーされなければなりません。再度 inet_ntoa 関数を呼び出すと、以前に保存した文字列情報が上書きされる可能性があるためです。

  • つまり、inet_ntoa 関数を再度呼び出す前に返された文字列アドレスは有効です。長期保存が必要な場合は、文字列を他のメモリ空間にコピーする必要があります。

この関数を呼び出す例を示します

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	struct sockaddr_in addr1, addr2;
	char *str_ptr;
	char str_arr[20];
   
	addr1.sin_addr.s_addr=htonl(0x1020304);
	addr2.sin_addr.s_addr=htonl(0x1010101);
	
	str_ptr=inet_ntoa(addr1.sin_addr);
	strcpy(str_arr, str_ptr);
	printf("Dotted-Decimal notation1: %s \n", str_ptr);
	
	inet_ntoa(addr2.sin_addr);
	printf("Dotted-Decimal notation2: %s \n", str_ptr);
	printf("Dotted-Decimal notation3: %s \n", str_arr);
	return 0;
}

ネットワークアドレスの初期化

サーバー側のソケット作成時の一般的なネットワーク アドレス情報の初期化方法は次のとおりです。

struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";          // 声明 IP 地址字符串
char *serv_port = "9190";                  // 声明端口号字符串
memset(&addr, 0, sizeof(addr));            // 结构体变量 addr 的所有成员初始化为 0,主要是为了将 sockaddr_in 的成员 sin_zero 初始化为 0。
addr.sin_family = AF_INET;                 // 指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); // 基于字符串的 IP 地址初始化
addr.sin_port = htons(atoi(serv_port));    // 基于字符串的端口号初始化

INADDR_ANY

アドレス情報の初期化

struct sockaddr_in addr;
char * serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
add.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

前の方法との最大の違いは、INADDR_ANY を使用してサーバー側 IP アドレスを割り当てると、サーバーを実行しているコンピューターの IP アドレスを自分で入力することなく自動的に取得できることです。また、同じコンピュータに複数のIPアドレスが割り当てられている場合、ポート番号が一致していれば、異なるIPアドレスからデータを受信することができます。

ソケットにネットワークアドレスを割り当てる

sockaddr_in 構造体の初期化方法については前に学習し、初期化されたアドレス情報をソケットに割り当てます。この操作はバインド関数が担当します

#include<sys/socket.h>
int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);
	成功时返回0,失败时返回-1。
	参数1:sockfd,要分配地址信息(IP地址和端口号)的套接字文件描述符
	参数2:myaddr,存有地址信息的结构体变量地址值。
	参数3:addrlen,第二个结构体变量的长度。

この関数呼び出しが成功すると、2 番目のパラメータで指定されたアドレス情報が、最初のパラメータの対応するソケットに割り当てられます。

int serv_sock;
struct sockaddr_in serv_addr;
char * srev_port = "9190";/* 创建服务器端套接字(监听套接字)*/
serv_sock = socket(PF_INET, SOCK_STREAM, 0);/* 地址信息初始化 */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(serv_port));/* 分配地址信息 */
bind(serv_sock, (struct sockaddr * )&serv_addr, sizeof(serv_addr));

サーバー側のコード構造は上記の通りです

要約する

「TCP/IP ネットワーク プログラミング」コラムの 3 回目の記事です。読者の皆様もぜひご購読ください。

詳細については、「 GitHub 」をクリックしてください。

⭐学術交流グループQ 754410389更新中~~~

おすすめ

転載: blog.csdn.net/m0_63743577/article/details/132651152