UDP プログラミング - TFTP、ブロードキャスト、マルチキャスト

1.1 TFTP の概要、通信プロセス
1.1.1 TFTPの概要

TFTP: Trivial Text Transfer Protocol;
元々はディスクレス システムの起動に使用され、小さなファイルを転送するために設計されました

特徴:
UDPプロトコルに基づいて実現
ユーザー正当性認証なし

データ転送モード:
オクテット: バイナリ モード
netascii: テキスト モード
メール: サポートされなくなりました

1.1.2 TFTP通信処理

TFTP通信の概要

  1. サーバーはポート 69 でクライアントのリクエストを待ちます。
  2. サーバーがリクエストを承認すると、一時ポートを使用してクライアントと通信します。
  3. 各パケットの番号が変わります(1から始まります)
  4. すべてのデータ パケットは ACK によって確認される必要がある タイムアウトが発生した場合は、最後のパケット (データまたは ACK) を再送信する必要がある
  5. データの長さは512Byteで送信されます
  6. データが 512 バイト未満の場合は転送の終了を意味します
1.1.3 TFTPプロトコルの解析

注:
上記の 0 は「\0」を表します。
異なるエラー コードは異なるエラー メッセージに対応します。

エラー コード:
0: 未定義、エラー メッセージを参照
1: ファイルが見つかりません
2: アクセス違反
3: ディスクがいっぱいまたは割り当てを超えています
4: 不正な TFTP 操作
5: 不明な転送 ID
6: ファイルはすでに
存在します7:そのようなユーザーはいません
8: サポートされていないオプションが要求されました

1.1.4 演習 - TFTP クライアント

演習要件:
TFTP プロトコルを使用してサーバーからローカルにファイルをダウンロードする

実装のアイデア:
1. 要求メッセージを作成し、サーバー (ポート 69) に送信します;
2. サーバーの応答を待ちます;
3. サーバーの応答を分析します;
4. 受信したデータ パケットが指定されたパケットより小さくなるまでデータを受信しますデータ長

サーバー側はあらかじめ用意されたソフトウェアによって利用されます

サーバーを設定する

クライアントがコードを書く

#include <stdio.h>		//printf
#include <stdlib.h>		//exit
#include <sys/types.h>  
#include <sys/socket.h>	//socket
#include <netinet/in.h>	//sockaddr_in
#include <arpa/inet.h>	//htons	inet_addr
#include <unistd.h>		//close
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

void do_download(int sockfd, struct sockaddr_in serveraddr){
    
    
	char filename[128] = "";
	printf("请输入要下载的文件名:");
	scanf("%s",filename);
	
	//给服务器发送信息,告知服务器执行下载操作
	unsigned char text[1024] = "";
	int text_len;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	int fd;
	int flags = 0;
	int num = 0;
	ssize_t bytes;
	
	//构建给服务器发送的tftp指令并发送给服务器,例如01test.txtoctet0
	text_len = sprintf(text,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
	if(sendto(sockfd,text,text_len,0,(struct sockaddr *)&serveraddr,addrlen) < 0){
    
    
		perror("fail to sendto");
		exit(1);
	}
	
	while(1){
    
    
		//接收服务器发送过来的数据并处理
		if((bytes = recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&serveraddr,*addrlen)) == -1){
    
    
			perror("fail to recvfrom");
			exit(1);
		}
		
		//判断操作码执行相应的处理
		if(text[1] == 5){
    
    
			printf("error: %s\n",text + 4); //打印差错信息
			return;
		}
		else if(text[1] == 3){
    
    
			if(flags == 0){
    
    
				//创建文件,只创建一次
				if((fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0){
    
    
					perror("fail to open");
					exit(1);
				}
				flags = 1;
			}
			
			//对比编号和接收的数据大小并将文件内容写入文件
			if((num + 1 == ntohs(*(unsigned short *)(text + 2))) && (bytes == 516)){
    
    
				num = ntohs(*(unsigned short *)(text + 2));
				if(write(fd,text+4,bytes - 4) < 0){
    
    
					perror("fail to write");
					exit(1);
				}
			
				//当文件写入完毕后,给服务器发送ACK
				text[1] = 4;
				if(sendto(sockfd, text, 4, 0, (struct sockaddr *)&serveraddr,addrlen) < 0 ){
    
    
					perror("fail to sendto");
					exit(1);
				}
			//当最后一个数据接收完毕后,写入文件后退出函数
			else if(num + 1 == ntohs(*(unsigned short *)(text + 2)) && (bytes < 516)){
    
    
				if(write(fd,text + 4, bytes - 4) < 0){
    
    
					perror("fail to write");
					exit(1);
				}
			
				text[1] = 4;
				if(sendto(sockfd,text,4,0,(struct sockaddr *)&serveraddr, addrlen)< 0){
    
    
					perror("fail to sendto");
					exit(1);
				}	
				printf("文件下载完毕")}
		}
	}
}

int main(int argc, char **argv)
{
    
    
	if(argc < 2){
    
    
		fprintf(stderr,"Usage: %s <server_ip>\n",argv[0]);
		exit(1);
	}
	
	int sockfd;
	struct sockaddr_in serveraddr;
	
	//创建套接字
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
    
    
		perror("fail to socket");
		exit(1);
	}
	
	//填充服务器网络信息结构体
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(agrv[1]);	//tftp服务器的ip地址,192.168.3.78
	serveraddr.sin_port = htons(69);	//tftp服务器的端口默认是69
	
	do_download(sockfd,serveraddr);		//下载操作
	
	return 0;
}
1.2 UDP - ブロードキャスト
1.2.1 放送の概念

ブロードキャスト: ホストが、そのホストが存在するサブネット内のすべてのホストにデータを送信する方法。たとえば、
192.168.3.103 のホストがブロードキャスト情報を送信すると、192.168.3.1 ~ 192.168.3.254 のすべてのホストがデータを受信できます。

ブロードキャストは、TCP ではなく、UDP または raw IP でのみ実装できます。

ブロードキャストの目的:
単一サーバーが複数のクライアント ホストと通信するときのパケット フローを削減します。
次のプロトコルはすべてブロードキャストを使用します。
1. アドレス解決プロトコル (ARP)
2. 動的ホスト構成プロトコル (DHCP)
3. ネットワーク タイム プロトコル (NTP) )

ブロードキャストの特徴:
1. 同じサブネット内のすべてのホストがデータを処理する必要がある
2. UDP データ パケットはプロトコル スタックを上り、UDP レイヤに到達します。
3. オーディオ、ビデオ、その他の高速アプリケーションを実行すると、大きなマイナスが生じます
4。 LAN内での使用に限定

ブロードキャスト アドレス
{ネットワーク ID、ホスト ID}
ネットワーク ID は、サブネット マスクの 1 でカバーされる連続ビットを表します。
ホスト ID は、サブネット マスクの 0 でカバーされる連続ビットを表します

定向广播地址:主机ID全1
​ 1、例:对192.168.220.0/24,其定向广播地址为192.168.220.255
​ 2、通常路由器不转发该广播

受限广播地址:255.255.255.255
​ 路由器从不转发该广播

1.2.2 广播和单播的对比

单播:

广播:

1.2.3 广播流程

发送者:
​ 第一步:创建套接字socket()
​ 第二步:设置为允许发送广播权限setsocket()
​ 第三步:向广播地址发送数据sendto()

接收者:
​ 第一步:创建套接字socket()
​ 第二步:将套接字与广播的信息结构体绑定bind()
​ 第三步:接收数据recvfrom()

套接字选项

int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
参数:
	sockfd:		文件描述符
	level:		协议层次		
				SOL_SOCKET		套接字层次
				IPPROTO_TCP		tcp层次
				IPPROTO_IP		IP层次
	optname:	选项的名称
				SO_BROADCAST	允许发送广播数据包
			    SO_RCVBUF		接收缓冲区大小
			    SO_SNDBUF		发送缓冲区大小
	optval:		设置的选项的值		
				int类型的值,存储的是bool的数据(10)
                  0			不允许
                  1			允许
    option_len:		option_value的长度
返回值
	成功执行返回0,否则返回-1
1.2.4 广播示例

发送者

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[]){
    
    
	if(argc < 3){
    
    
		fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
		exit(1)
	}
	
	int sockfd;
	struct sockaddr_in broadcataddr;	//服务器网络信息结构体
	socklen_t addrlen = sizeof(broadcataddr);
	
	//第一步:创建套接字
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0))< 0){
    
    
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:设置为允许发送广播权限
	int on = 1;
	if(setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on)) < 0){
    
    
		perror("fail to setsockopt");
		exit(1);
	}
	
	//第三步:填充广播信息结构体
	broadcataddr.sin_family = AF_INET;
	broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.255 或者255.255.255.255
	broadcataddr.sin_port = htons(atoi(argv[2]));
	
	//第四步:进行通信
	char buf[128] = "";
	while(1){
    
    
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';
		
		if(sendto(sockfd,buf,0,(struct sockaddr *)&broadcataddr,addrlen)){
    
    
			perror("fail to sendto");
			exit(1);
		}
	}

	return 0;
}

接收

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[]){
    
    
	if(argc < 3){
    
    
		fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
		exit(1)
	}
	
	int sockfd;
	struct sockaddr_in broadcataddr;	//服务器网络信息结构体
	socklen_t addrlen = sizeof(broadcataddr);
	
	//第一步:创建套接字
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0))< 0){
    
    
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:填充广播信息结构体
	broadcataddr.sin_family = AF_INET;
	broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.255 或者255.255.255.255
	broadcataddr.sin_port = htons(atoi(argv[2]));
	
	第三步:将套接字与广播信息结构体绑定
	if(bind(sockfd,(struct sockaddr *)&broadcataddr,addrlen) < 0){
    
    
		perror("fail to bind");
		exit(1);
	}
	
	//第四步:进行通信
	char text[32] = "";
	struct sockaddr_in sendaddr;
	while(1){
    
    
		if(recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&sendaddr,&addrlen)){
    
    
			perror("fail to recvfrom");
			exit(1);
		}
		
		printf("[%s - %d]: %s\n",inet_ntoa(sendaddr.sin_addr),ntohs(sendaddr.sin_port),text);
	}

	return 0;
	
}
1.3 UDP—多播
1.3.1 多播概述

多播:数据的收发仅仅在同一分组中进行,所以多播又称之为组播。

多播的特点:
​ 1、多播地址标示一组接口
​ 2、多播可以用于广域网使用
​ 3、在IPV4中,多播是可选的

1.3.2 多播地址

IPV4的D类地址是多播地址
十进制:224.0.0.1 ~ 239.255.255.254
十六进制:E0.00.00.01 EF.FF.FF.FE

多播地址向以太网MAC地址的映射

1.3.3 UDP多播工作过程

比起广播,多播具有可控性,只有加入多播组的接收者才可以接收数据,否则接收不到

1.3.4 多播流程

发送者:
​ 第一步:创建套接字 socket()
​ 第二步:向多播地址发送数据 sendto()

接收者:
​ 第一步:创建套接字socket()
​ 第二步:设置加入多播组setsockopt()
​ 第三步:将套接字与多播信息结构体绑定bind()
​ 第四步:接收数据recvfrom()

1.3.5 多播地址结构体

在IPV4因特网络(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示

struct in_addr
{
    
    
	in_addr_t s_addr;	
}
struct ip_mreq
{
    
    
	struct in_addr imr_multiaddr;	//多播组IP
	struct in_addr imr_interface;	//将要添加到多播组的IP
}
1.3.6 多播套接口选项
int setsockopt(int sockfd, int level, int optname, const void *optval_value, socklen_t optlen);
功能:设置一个套接字的选项(属性)
参数:
	socket:	文件描述符
	level:	协议层次
		IPPROTO_IP		IP层次
	option_name:	选项名称
		IP_ADD_MEMBERSHIP	加入多播组
		IP_DROP_MEMBERSHIP	离开多播组
	option_value:	设置的选项的值
		struct ip_mreq
		{
    
    
			struct in_addr imr_multiaddr;	//多播组IP
			struct in_addr imr_interface;	//将要添加到多播组的IP
				//INADDR_ANY	任意主机地址(自动获取你的主机地址)
		}
	option_len:	option_value的长度
返回值:
	成功:0
	失败:-1
1.3.7 加入多播组示例

发送者:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[]){
    
    
	if(argc < 3){
    
    
		fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
		exit(1)
	}
	
    int sockfd;
    struct sockaddr_in groupcastaddr; // 服务器网络信息结构体
    socklen_t addrlen = sizeof(groupcastaddr);
    
    // 第一步: 创建套接字
    if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
    
    
    	perror("fail to socket");
    	exit(1);
    }
    
    // 第二步: 填充组播信息结构体
    groupcastaddr.sin_family = AF_INET;
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]);  //224.x.x.x - 239.255.255.254
    groupcastaddr.sin_port = htons(atoi(argv[2]));
    
    // 第三步:	进行通信
    char buf[128] = "";
    while(1){
    
    
    	fgets(buf,sizeof(buf),stdin);
    	buf[strlen(buf) - 1] = '\0';
    	if(sendto(sockfd,buf,sizeof(buf),(struct sockaddr *)&groupcastaddr,socklen_t) < 0){
    
    
    		perror("fail to sendto");
    		exit(1);
    	}
    }
    
    return 0;
}

接收者:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[]){
    
    
	if(argc < 3){
    
    
		fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
		exit(1)
	}
	
    int sockfd;
    struct sockaddr_in groupcastaddr; // 服务器网络信息结构体
    socklen_t addrlen = sizeof(groupcastaddr);
    
    // 第一步: 创建套接字
    if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
    
    
    	perror("fail to socket");
    	exit(1);
    }
    
    // 第二步:	设置为加入多播组
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
    mreq.imr_interface.s_addr = INADDR_ANY;
    if(setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0){
    
    
    	perror("fail to setsockopt");
    	exit(1);
    }
    
    
    // 第三步: 填充组播信息结构体
    groupcastaddr.sin_family = AF_INET;
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]);  //224.x.x.x - 239.255.255.254
    groupcastaddr.sin_port = htons(atoi(argv[2]));
    
    //第四步:	将套接字与广播信息结构体绑定
    if(bind(sockfd,(struct sockaddr *)&groupcastaddr, addrlen) < 0){
    
    
    	perror("fail to bind");
    	exit(1);
    }
    
    
    // 第五步:	进行通信
    char text[32] = "";
    struct sockaddr_in sendaddr
    while(1){
    
    
    	if(recvfrom(sockfd,text,sizeof(text),(struct sockaddr *)&sendaddr,&socklen_t) < 0){
    
    
    		perror("fail to recvfrom");
			exit(1);
		}
		
		printf("[%s - %d]: %s\n",inet_ntoa(sendaddr.sin_addr),ntohs(sendaddr.sin_port),text);
    }
    
    return 0;
}

おすすめ

転載: blog.csdn.net/AAAA202012/article/details/127286981