記事ディレクトリ
ソケットに割り当てられた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更新中~~~