(1) ホストバイトオーダーとネットワークバイトオーダー
バイトオーダーとは、複数のバイトタイプのデータがメモリに格納される順序を指します。
1 つは、開始アドレスに下位バイトを格納する方法で、これはリトルエンディアン バイト オーダーと呼ばれます。
1 つは、上位バイトを開始アドレスに格納する方法で、これはビッグエンディアン バイト オーダーと呼ばれます。
どちらのエンディアンにも標準はなく、両方の形式がシステムで使用されます。
メモリ上には1バイト以上のデータのみが順番に格納され、1バイトのデータには順序はありません。
ホストのバイト順序 (ホスト-バイト) : マシンごとにホストのバイト順序は異なります。これは CPU の設計に関連しており、オペレーティング システムとは関係ありません。
同じデータでもマシン間で格納順序が異なるため、異なるアーキテクチャ間で通信することはできず、通信時に取り決められたバイトオーダー、つまりネットワークバイトオーダーに変換する必要があります。
ネットワーク バイト オーダー (network-byte) : ネットワーク バイト オーダーは、TCP/IP で規定されているデータ表現形式であり、ビッグ エンディアン バイト オーダーを採用しています。CPUの種類やOSなどは問いません。
ネットワークバイトオーダーとホストバイトオーダー間の変換関数:
1) htons() と ntohs() は、16 ビット (短い) 符号なし数値の相互変換を完了します。
2) htonl() と ntohl() は、32 ビット (長い) 符号なし数値の相互変換を完了します。
そのため、ソケット通信時にipアドレス(32ビット)と通信ポート(16ビット)を指定すると、それに合わせて変換されます。
serveraddr->sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址
serveraddr->sin_port = htons(atoi(argv[1])); // 指定通信端口
(2) sockaddr_in 構造体
元の構造定義は次のとおりです。
struct sockaddr {
unsigned short sa_family; // 地址类型 AF_XXX
char sa_data[14]; // 14字节存放端口和地址
};
この構造ではアドレスやポート番号を保存するのが不便なので、
構造は次のように定義されます。
struct sockaddr_in {
short int sin_family; // 地址类型 AF_INET(ipv4)
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // 地址
unsigned char sin_zero[8]; // 为了保持与 struct sockaddr 一样的长度
};
struct in_addr { // 简化定义 详细定义可百度
unsigned long s_addr; // 地址
};
struct sockaddr と同じ長さを保つために、sin_zero[8] は 2 つの構造体を変換可能にします。
ポート番号 sin_port: 0 未使用
1) [1, 1023]: ウェルノウン ポート。IANA は、これらのポート番号を TCP/IP システムの最も重要なアプリケーション プロトコルのいくつかに割り当てます。
2) [1024, 49151]: 既知のポート番号を持たないアプリケーションによって使用されます。これらのポート番号を使用する場合は、重複を防ぐために所定の手順に従ってIANAに登録する必要があります。
3) [49152、65535]: 一時的に使用するかどうかはクライアント プロセスに任せます。
ポート番号はローカルな意味のみを持ち、コンピューターのアプリケーション層で各プロセスを識別するためにのみ使用されます。
(3)gethostbyname()とinet_addr()
1) in_addr_t inet_addr(const char* cp): 文字列形式の IP アドレスをネットワーク バイト オーダーの整数値に変換します。関数が成功すると 0 以外の値が返され、関数が正しくない場合は 0 が返されます。
2) char* inet_ntoa(struct in_addr in): ネットワークバイト列のIPアドレスを文字列のIPアドレスに変換します。
3) gethostbyname() は次のように定義されます。
struct hostent *gethostbyname(const char *name);
パラメータ: name (「192.168.1.3」、「www.baidu.com」など) はドメイン名またはホスト名で、ipv4 のみをサポートします。
戻り値: 成功した場合は hostent 構造体ポインタを返し、失敗した場合は NULL を返します。
ホスト構造は次のように定義されます。
struct hosttent {
char* h_name; // 主机规范名
char** h_aliases; // 主机的别名
int h_addrtype; // 表示的是主机ip地址的类型。AF_INET/AF_INET6
int h_length; // 主机ip地址的长度
char** h_addr_list; // 主机的ip地址,网络字节序存储
};
# define h_addr h_addr_list[0]
取得した IP アドレスを sockaddr_in にコピーします。
memcpy(&servaddr->sin_addr, h->h_addr, h->h_length);
(3)ソケット()
ソケット関数は、新しいソケットを作成する、つまりシステムにソケット リソースを申請するために使用されます。
関数宣言:
int socket(int domain, int type, int protocol);
ドメイン: プロトコル ドメイン。プロトコル ファミリ (ファミリー) とも呼ばれます。一般的に使用されるプロトコル ファミリは、AF_INET、AF_INET6、AF_LOCAL (または AF_UNIX、Unix ドメイン ソケット)、AF_ROUTE などです。プロトコル ファミリによってソケットのアドレス タイプが決定され、対応するアドレスが通信で使用される必要があります。たとえば、AF_INET は ipv4 アドレス (32 ビット) とポート番号 (16 ビット) の組み合わせを使用することを決定し、AF_UNIX は絶対パス名をアドレスとして使用します。
type: ソケットのタイプを指定します。一般的に使用されるソケット タイプは、SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET などです。ストリーミング ソケット (SOCK_STREAM) は、コネクション指向の TCP サービス アプリケーション用のコネクション指向のソケットです。データグラム ソケット (SOCK_DGRAM) は、コネクションレス型 UDP サービス アプリケーションに対応するコネクションレス型ソケットです。
プロトコル: プロトコルを指定します。一般的に使用されるプロトコルには、TCP 伝送プロトコル、UDP 伝送プロトコル、STCP 伝送プロトコル、TICP 伝送プロトコルに対応する IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC などが含まれます。
一般的に使用されるパラメータは ipv4 と TCP 接続であるため、パラメータは AF_INET、SOCK_STREAM、IPPROTO_TCP (0) です。
戻り値: 成功した場合はソケット(int)が返され、失敗した場合は -1 が返されます。エラーの原因は errno に格納されます。
(4)バインド()
バインド関数は、通信に使用されるアドレスとポートをソケットにバインドするためにサーバーによって使用されます。
関数宣言:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
ソケット: バインドする必要があるソケット
addr: サーバーが通信に使用するアドレスとポートを格納します。型は struct sockaddr * であるため、sockaddr_in 構造体を使用して通信アドレスとポートを定義する場合は、必須の型変換が必要です
bind(listenfd, (struct sockaddr *) serveraddr, sizeof(*serveraddr))
addrlen: addr構造体のサイズを示します。
戻り値: 成功の場合は 0、失敗の場合は -1 を返し、エラーの原因は errno に格納されます。
バインドされたアドレスが間違っている場合、またはポートがすでに占有されている場合、バインド関数は確実にエラーを報告しますが、それ以外の場合は通常、エラーは返されません。
サーバー プログラムのポートが解放された後、ポートは TIME_WAIT 状態になる可能性があり、再度使用できるようになるまで 2 分間待つ必要があります。SO_REUSEADDR 属性を設定すると、ポートが解放された直後に再度使用できるようになります。解放されます。
// 设置SO_REUSEADDR选项
int opt = 1;
unsigned int len = sizeof(opt);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, len);
(5)聞く()
listen 関数は、アクティブな接続ソケットをパッシブな接続ソケットに変更します。これにより、このソケットは他のソケットからの接続要求を受け入れることができ、サーバー ソケットになります。
関数宣言:
int listen(int socket, int backlog);
ソケット: バインドされたソケット。Socket() 関数は、アクティブに接続されているソケットを返します。サーバー側プログラミングでは、プログラマは、このソケットが外部接続要求を受け入れることができること、つまり、ソケットがクライアントの接続を待機していることを期待します。システムはデフォルトでソケットがアクティブに接続されていると見なすため、何らかの方法でシステムに通知する必要があります。つまり、listen() 関数を使用する必要があります。
backlog: このパラメータにはネットワークの詳細が含まれます。通常は 5 または 10 で問題なく、通常は 30 を超えないようにしてください。
戻り値: 成功の場合は 0、失敗の場合は -1、エラーの原因は errno に格納されます。
listen を呼び出した後、サーバー側のソケットは accept を呼び出して、クライアントからの接続要求を受け入れることができます。
(6)受け入れる()
accept 関数は準備完了接続キューからリクエストを取得します。キューが空の場合、accept 関数はブロックして待機します。
関数宣言:
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
ソケット: リッスンされたソケット。
addr:socketaddr構造体で表現されるクライアントのアドレス情報を格納するために使用され、クライアントのアドレスが不要な場合は0を入力できます。
addrlen: addr パラメータの長さを格納するために使用されます。addr が 0 の場合、addrlen にも 0 が入ります。
戻り値: 新しいソケットを返します。サーバーはこの新しいソケットを使用してクライアントとメッセージを送受信します。
accept の待機中に中断またはその他の理由があった場合、関数は失敗を示す -1 を返し、エラーの原因は errno に格納されるため、再度受け入れることができます。
(7)接続()
connect 関数は、サーバーへの接続要求を開始するためにクライアントによって使用されます。
関数宣言:
int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);
sockfd: サーバーへの接続に使用されるソケット。
serv_addr: 接続サーバーのアドレスとポートを保存します。
addrlen: 構造体 sockaddr の長さ。
戻り値: 成功の場合は 0、失敗の場合は -1、エラーの原因は errno に格納されます。
サーバーアドレスが間違っている、ポートが間違っている、またはサーバーが起動していない場合、接続は確実に失敗します。
まとめ:
サーバー: ソケット -> バインド -> リッスン -> 受け入れ
クライアント: ソケット -> 接続
1) サーバーが listen() を呼び出す前に、クライアントはサーバーへの接続要求を開始できません。
2) サーバーが listen() を呼び出した後、サーバー上のソケットはクライアントの接続の待機を開始します。
3) クライアントは connect() 関数を呼び出して、サーバーへの接続要求を開始します。
4) TCP の最下層では、クライアントとサーバーがハンドシェイクした後に通信チャネルが確立され、複数のクライアント要求がある場合、サーバー上に準備完了の接続のキューが形成されます。
5) サーバーは、accept() 関数を呼び出してキューから準備完了の接続を取得します。この関数は、クライアントとの通信に使用される新しいソケットを返します。リッスン ソケットは、クライアントの接続リクエストをリッスンすることのみを担当します。クライアント。