記事ディレクトリ
基礎知識
UDPは、レイヤードTCP / IPプロトコルのトランスポート層をエクスポートします。
UDPは信頼性の低い通信プロトコルであり、再送信や確認応答、順序制御、輻輳制御はありません。
UDPは、パケットの効果的な配信やパケットの順序を保証するものではありません。つまり、UDPを使用する場合、パケットの損失、再送信、およびメッセージの組み立てを適切に行う必要があります。
UDPは比較的単純で、適切なシナリオが多数あります。一般的なDNSサービスとSNMPサービスは、UDPプロトコルに基づいています。これらのシナリオは、遅延やパケット損失に特に敏感ではありません。さらに、チャットルーム、マルチプレイヤーゲームなどのマルチパーソンコミュニケーションシナリオもUDPプロトコルを使用します。
メッセージフォーマット
UDPヘッダーは8バイト長で、送信元ポート、宛先ポート、UDPパケット長、およびチェックサムです。
UDPメッセージの長さは2バイトでのみ記録されるため、ヘッダー長を含むメッセージの最大長は65535バイトです。
UDPプロトコルで送信する場合、sendto関数で送信できるデータの最大長は、65535-IPヘッダー(20)-UDPヘッダー(8)= 65507バイトです。sendto関数を使用してデータを送信するときに、送信されたデータの長さがこの値より大きい場合、関数はエラーを返します。
IPには最大MTUがあるため
、UDPパケットのサイズは1500-IPヘッダー(20)-UDPヘッダー(8)= 1472(バイト)である必要があります
UDPプログラミング
サーバー:
1.ソケットを作成します。
2.監視するIPとポートをバインドします。
3.ループ:
3.1。recvfromを呼び出して受信したメッセージを読み取り、メッセージがない場合はブロックします。
3.2。メッセージを受信した後、sendtoを呼び出し、それに応じてクライアントに送信します。
クライアント:
1.ソケットを作成します。
2.ループ:
2.1。sendtoを呼び出してリクエストを送信します。
2.2。対応するものを受け取るには、recvfromを呼び出します。
#include <sys/socket.h>
// 返回值:收到数据的字节数
// 参数:
// sockfd:socket描述符
// buff:本地缓存
// nbytes:缓存最大接收字节数
// flags:I/O 相关的参数,一般使用 0 即可
// from:发送端的 ip 和 port 等信息
// addrlen:from 的大小
ssize_t
recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
// 返回值:发送了多少字节
// 参数:和上面的 recvfrom 类似
ssize_t
sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
コードは私の以前の記事を参照しています:UDPエコープログラムhttps://blog.csdn.net/Hanoi_ahoj/article/details/105358383
UDPパケットの「コネクションレス」機能を使用すると、UDPサーバーの再起動後もパケットを送信し続けることができます。これは、UDPパケットの「コンテキストなし」の最良の説明です。
接続されたUDP
上記により、UDPでは、TCPプログラミングで接続するような接続を確立する必要はありません。
実際、UDPは「接続」することもできます。
プログラムでテストしてみましょう:
クライアント:ソケットが作成された後、サーバーのIPとポートに接続およびバインドされることに注意してください。
// UDP connect 测试客户端
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9090);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
perror("connect");
return -1;
}
while (1) {
char buf[1024] = {0};
printf("input>");
scanf("%s", buf);
ssize_t n = sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (n < 0) {
perror("sendto");
continue;
}
printf("%zd bytes sent to [%s:%d]\n", n, inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
bzero(buf, sizeof(buf));
n = recvfrom(socket_fd, buf, sizeof(buf), 0, NULL, NULL);
if (n < 0) {
perror("recvfrom");
return -1;
}
printf("resp: %s\n", buf);
}
close(socket_fd);
return 0;
}
サーバー:通常のサーバーであり、リクエストは変更されずに返されます。
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
server_addr.sin_port = htons(9090);
int ret = bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
perror("bind");
return -1;
}
// 一般服务器不进行 connect 操作
while (1) {
char buf[1024] = {0};
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
ssize_t n = recvfrom(socket_fd, buf, sizeof(buf) - 1, 0,
(struct sockaddr*)&client_addr, &client_addr_len);
if (n < 0) {
perror("recvfrom");
continue;
}
buf[n] = '\0';
printf("req->[%s:%d] %s\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port), buf);
n = sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&client_addr, client_addr_len);
if (n < 0) {
perror("sendto");
continue;
}
}
close(socket_fd);
return 0;
}
gcc client.c -o client
gcc server.c -o server
テスト:
1.サーバーは実行せず、クライアントのみを実行します。
ご覧のように、sendtoが呼び出されたときに送信されましたが、recvfromに移動するときに接続が拒否されたというエラーがありました。
2.サーバーとクライアントを実行します。
通常どおりリクエストを受信して処理します。
UDP接続の役割
接続しない場合は、サーバーを起動せず、クライアントを実行してください。プログラムはrecvfromでブロックされます。サーバーが再起動するかタイムアウトするまで。
通常、UDPサーバーはクライアントに接続する必要があるため、接続する必要はありません。
接続の役割は、プログラムがエラーメッセージをできるだけ早く受信して返すようにすることです。
UDPソケットで接続操作を実行することにより、UDPソケットの「コンテキスト」が確立され、サーバー側のアドレスとポートとの接続が確立されます。このバインディング関係により、オペレーティングシステムのカーネルに必要な情報が提供されます、オペレーティングシステムカーネルが受信した情報を対応するソケットに関連付けることができます。
sendto関数またはsend操作関数が呼び出されると、アプリケーションメッセージが送信され、アプリケーションが返され、オペレーティングシステムカーネルがメッセージを引き継ぎ、オペレーティングシステムは対応するアドレスとポートへの送信を試みます。到達すると、ICMPメッセージがオペレーティングシステムのカーネルに返されます。ICMPメッセージには、宛先アドレスやポートなどの情報が含まれています。
オペレーティングシステムカーネルが(UDPソケット宛先アドレス+ポート)間のマッピング関係を確立するのを助けるために、接続操作が実行されました。ICMP到達不能メッセージが受信されると、オペレーティングシステムカーネルはマッピングテーブルからそれを見つけることができますどのUDPソケットに宛先アドレスとポートがあるか、ソケットがオペレーティングシステム内でグローバルに一意であることを忘れないでください。ソケットでrecvfromまたはrecvメソッドを再度呼び出すと、操作を受信できます。システムカーネルから返された「接続拒否」メッセージ。
UDPに接続した後、送信および受信機能の使用に関する多くの本が推奨
されます。sendtoを使用して送信するには送信または書き込み機能を使用し、関連するアドレス情報をゼロに設定する必要があります。
受信するには、recvまたは読み取り機能を使用しますrecvfromを使用するには、対応するfromアドレス情報をゼロに設定する必要があります。
実際、UNIXの実装によって動作が異なります。
効率係数:
connectメソッドを使用しない場合、メッセージを送信するたびに、
ソケットの接続→メッセージの送信→ソケットの切断→ソケットの接続→メッセージの送信→ソケットの切断→......のプロセスが必要になるためです。…
connectメソッドを使用すると、
ソケット接続→メッセージ送信→メッセージ送信→...→最後にソケットを切断します。
ソケットを接続するには、ルーティングテーブル情報を検索する必要があるなど、ある程度のオーバーヘッドが必要であることはわかっています。したがって、UDPクライアントプログラムは、接続によって特定のパフォーマンスの向上を得ることができます。
参照:Geek Time-Practical Network Programming(https://time.geekbang.org/column/article/129807)
EOF