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. 데이터 길이는 512Bytes로 전송
  6. 512Bytes 미만의 데이터는 전송 종료를 의미합니다.
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(Address Resolution Protocol)
2. DHCP(Dynamic Host Configuration Protocol)
3. NTP(Network Time Protocol) )

브로드캐스팅의 특성:
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