記事ディレクトリ
1. 文字列情報をネットワーク バイト オーダーの整数型に変換します。
1.1 inet_addr 関数
sockaddr_in
住所情報を保存する会員は32 3232ビット整数型。したがって、IP アドレスを割り当てるには、32 3232ビット整数データ。しかし、IPアドレスの表現については、整数データの表現ではなく、ドット10進表記(Dotted Decimal Notation)に慣れています。幸いなことに、inet_addr
この関数は、文字列形式の IP アドレスを32 3232ビット整数データ。inet_addr
この関数は、型を変換しながらネットワーク バイト オーダー変換を実行します。
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
// 成功时返回32位大端序整数型值,失败时返回INADDR_NONE
// 返回值类型in_addr_t在内部声明为32位整数型
以下は、サンプル コード inet_addr.c によるこの関数の呼び出しプロセスを示しています。
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char *addr1 = "1.2.3.4";
// 1个字节能表示的最大整数为255,也就是说,它是错误的IP地址。
// 利用该错误地址验证inet_addr函数的错误检测能力。
char *addr2 = "1.2.3.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 occured!\n");
else
printf("Network ordered integer addr: %#lx\n", conv_addr);
return 0;
}
コンパイルして実行します。
gcc inet_addr.c -o addr
./addr
出力結果:
Network ordered integer addr: 0x4030201
Error occured!
実行結果からわかるように、inet_addr
関数は IP アドレスを32 32に変換するだけではありません。32ビット整数型で、無効な IP アドレスを検出できます。さらに、実際にネットワーク バイト オーダーに変換されていることが出力から確認できます。
1.2 inet_aton 関数
inet_aton
inet_addr
関数は関数とまったく同じで、IP アドレスを文字列形式で32 32に変換します。32ビットのネットワーク バイト順整数と戻り値。この関数がin_addr
構造体
#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
// 成功时返回1(true),失败时返回0(false)
// string:含有需转换的IP地址信息的字符串地址值
// addr:将保存转换结果的in_addr结构体变量的地址值
実際のプログラミングでは、inet_addr
関数、sockaddr_in
構造体で宣言したin_addr
構造体に、変換したIPアドレス情報を代入する必要があります。inet_aton
関数はこのプロセスを必要としません。その理由は、in_addr
構造体、関数が自動的に結果を構造体変数に入力するためです。
以下では、inet_aton
関数。
#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;
// 转换后的IP地址信息需保存到sockaddr_in的in_addr型变量才有意义。
// 因此,inet_aton函数的第二个参数要求得到in_addr型的变量地址值。这就省去了手动保存IP地址信息的过程。
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);
}
コンパイルして実行します。
gcc inet_aton.c -o aton
./aton
操作結果:
Network ordered integer addr: 0x4f7ce87f
1.3 inet_ntoa 関数
inet_aton
関数とは逆に、inet_ntoa
関数はネットワーク バイト オーダー整数型の IP アドレスを、使い慣れた文字列形式に変換できます。
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
// 成功时返回转换的字符串地址值,失败时返回-1
inet_ntoa
この関数は、パラメーターを介して渡された整数の IP アドレスを文字列形式に変換して返します。ただし、呼び出すときは注意してください。戻り値の型は char ポインターです。文字列のアドレスを返すということは、文字列がメモリ空間に保存されたことを意味しますが、この関数はプログラマにメモリの割り当てを要求するのではなく、内部的にメモリを適用して文字列を保存します。つまり、この関数を呼び出した後、文字列情報をすぐに他のメモリ空間にコピーする必要があります。inet_ntoa
関数が再度呼び出されると、以前に保存された文字列情報が上書きされる可能性があるためです。つまり、inet_ntoa
関数を有効です。長期保存が必要な場合は、文字列を他のメモリ空間にコピーする必要があります。
以下は、サンプル コード inet_ntoa.c によるこの関数の呼び出しプロセスを示しています。
#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);
// 向inet_ntoa函数传递结构体变量addr1中的IP地址信息并调用该函数,返回字符串形式的IP地址
str_ptr = inet_ntoa(addr1.sin_addr);
// 浏览并复制第15行中返回的IP地址信息
strcpy(str_arr, str_ptr);
printf("Dotted-Decimal notation1: %s\n", str_ptr);
// 再次调用inet_ntoa函数。由此得出,第15行中返回的地址已覆盖了新的IP地址字符串,可通过第23行的输出结果进行验证。
inet_ntoa(addr2.sin_addr);
printf("Dotted-Decimal notation2: %s\n", str_ptr);
// 第18行中复制了字符串,因此可以正确输出第15行中返回的IP地址字符串
printf("Dotted-Decimal notation3: %s\n", str_arr);
return 0;
}
コンパイルして実行します。
gcc inet_ntoa.c -o ntoa
./ntoa
出力結果:
Dotted-Decimal notation1: 1.2.3.4
Dotted-Decimal notation2: 1.1.1.1
Dotted-Decimal notation3: 1.2.3.4
2. ネットワークアドレスの初期化
これまでに学んだことを組み合わせて、ソケット作成プロセスでの一般的なネットワーク アドレス情報の初期化方法を紹介します。
struct sockaddr_in addr;
char *serv_ip = "211.217.168.13"; // 声明IP地址字符串
char *serv_port = "9190"; // 声明端口号字符串
memset(&addr, 0, sizeof(addr)); // 结构体变量addr的所有成员初始化为0
addr.sin_family = AF_INET; // 指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); // 基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); // 基于字符串的端口号初始化
上記のコードでは、memset
関数は各バイトを同じ値に初期化します。
- 最初のパラメーターは、構造体変数 addr のアドレス値です。つまり、初期化オブジェクトは addr です。
- 2 番目のパラメーターは0 0です0であるため、0 00;
- addr の長さは最後のパラメーターで渡されるため、addr のすべてのバイトが0 0に初期化されます。0 . これは、構造体のメンバーを0 0
sockaddr_in
ために行われますsin_zero
0。
さらに、コードの最後の行で呼び出されるatoi
関数は、文字列値を整数に変換します。要約すると、上記のコードは、文字列形式の IP アドレスとポート番号でsockaddr_in
構造。
上記のネットワーク アドレス情報の初期化プロセスは、クライアント側ではなく、主にサーバー側を対象としています。ソケットにIPアドレスとポート番号を割り振るのは、主に「IP 211.217.168.13、ポート9190に入るデータを送ってください!」という準備です。
これに対し、クライアント側の接続要求は「IP 211.217.168.13、ポート 9190 に接続してください!」です。
要求メソッドが異なれば、呼び出す関数も異なります。サーバー側の準備作業はbind関数で行い、クライアント側はconnect関数で行います。そのため、関数呼び出し前に用意しておく必要があるアドレス値の種類も異なります。
- サーバー側は、sockaddr_in 構造体変数を宣言し、サーバー側の IP とソケットに割り当てられたポート番号に初期化してから、bind 関数を呼び出します。
- クライアントは、sockaddr_in 構造体を宣言し、接続するサーバー ソケットの IP とポート番号で初期化してから、connect 関数を呼び出します。
3.INADDR_ANY
サーバー側ソケットを作成するたびにIPアドレスを入力するのは面倒なので、以下のようにしてアドレス情報を初期化することができます。
struct sockaddr_in addr;
char *serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));
以前の方法との最大の違いは、定数 INADDR_ANY を使用してサーバーの IP アドレスを割り当てることです。この方法を使用すると、サーバーを実行しているコンピュータの IP アドレスを手動で入力することなく、自動的に取得できます。また、同じコンピュータに複数のIPアドレスが割り当てられている場合(マルチホームコンピュータ、一般的なルーターがこの範疇に属します)、ポート番号が一致していれば、異なるIPアドレスからデータを受信することができます。したがって、このメソッドはサーバー側で優先されます。また、クライアントにサーバー側の機能がない限り、使用されません。
Q: サーバー側ソケットを作成するときに IP アドレスが必要なのはなぜですか?
回答: 同じコンピュータに複数の IP アドレスを割り当てることができます。実際の IP アドレスの数は、コンピュータにインストールされている NIC の数と同じです。サーバー側のソケットでさえ、どの IP から (どの NIC から) データを受信するかを決定する必要があります。したがって、サーバー側のソケットの初期化中に IP アドレス情報が必要になります。さらに、1 1だけの場合1 NIC、直接使用しますINADDR_ANY
。
4. ソケットにネットワークアドレスを割り当てます (バインド機能)
sockaddr_in
構造体の初期化方法が説明されたので、初期化のためのアドレス情報がソケットに割り当てられ、バインド関数がこの操作を担当します。
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
// 成功时返回0,失败时返回-1
// sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符
// myaddr:存有地址信息的结构体变量地址值
// addrlen:第二个结构体变量的长度
この関数呼び出しが成功すると、第 2 パラメーターで指定されたアドレス情報が、第 1 パラメーターの対応するソケットに割り当てられます。
サーバー側での一般的なソケットの初期化プロセスを以下に示します。
int serv_sock;
struct sockaddr_in serv_addr;
char *serv_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));
// ......
サーバー側のコード構造はデフォルトで上記のようになっていますが、もちろんここには示していない例外処理コードもあります。