UDP programming - TFTP, broadcast, multicast

1.1 Introduction to TFTP, communication process
1.1.1 Overview of TFTP

TFTP: Trivial Text Transfer Protocol;
​ originally used to boot diskless systems, designed to transfer small files

Features:
Realized based on UDP protocol
No user validity authentication

Data transfer mode:
octet: binary mode
netascii: text mode
mail: no longer supported

1.1.2 TFTP communication process

Summary of TFTP communication

  1. The server waits for the client's request on port 69
  2. If the server approves the request, it will use the ephemeral port to communicate with the client
  3. The number of each packet changes (starts from 1)
  4. Every data packet needs to be confirmed by ACK If there is a timeout, the last packet (data or ACK) needs to be resent
  5. The length of the data is transmitted in 512Bytes
  6. Data less than 512Bytes means the end of transmission
1.1.3 TFTP protocol analysis

Note:
The above 0 represents '\0'
Different error codes correspond to different error messages

Error code:
​ 0: Undefined, see error message
​ 1: File not found
​ 2: Access violation
​ 3: Disk full or allocation exceeded
​ 4: illegal TFTP operation
​ 5: Unknown transfer ID
​ 6: File already
exists 7: No such user
​ 8: Unsupported option(s) requested

1.1.4 Exercise—TFTP client

Exercise requirements:
​ Use TFTP protocol to download files from the server to the local

Implementation ideas:
1. Construct a request message and send it to the server (port 69);
2. Wait for the server to respond;
3. Analyze the server’s response;
4. Receive data until the received data packet is less than the specified data length

The server side is used in advance through prepared software

set server

client write code

#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—Broadcast
1.2.1 The concept of broadcasting

Broadcast: A way for a host to send data to all hosts in the subnet where the host is located.
For example, if the host at 192.168.3.103 sends broadcast information, all hosts at 192.168.3.1~192.168.3.254 can receive the data

Broadcasting can only be implemented with UDP or raw IP, not TCP

The purpose of broadcasting:
​ Reduce packet flow when a single server communicates with multiple client hosts.
The following protocols all use broadcasting
​ 1. Address Resolution Protocol (ARP)
2. Dynamic Host Configuration Protocol (DHCP)
3. Network Time Protocol (NTP)

The characteristics of broadcasting:
1. All hosts in the same subnet must process data
2. UDP data packets will go up the protocol stack to the UDP layer
3. Running audio and video and other high-speed applications will bring Big negative
​ 4. Limited to use within the LAN

Broadcast Address
{Network ID, Host ID}
​ Network ID represents the consecutive bits covered by 1 in the subnet mask
​ Host ID represents the consecutive bits covered by 0 in the subnet mask

定向广播地址:主机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;
}

Guess you like

Origin blog.csdn.net/AAAA202012/article/details/127286981