UDP编程——TFTP、广播、多播

1.1 TFTP简介、通信过程
1.1.1 TFTP概述

TFTP:简单文本传送协议;
​ 最初用于引导无盘系统,被设计用来传输小文件

特点:
​ 基于UDP协议实现
​ 不进行用户有效性认证

数据传输模式:
​ octet: 二进制模式
​ netascii: 文本模式
​ mail: 已经不再支持

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: 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 练习—TFTP客户端

练习要求:
​ 使用TFTP协议,下载server上的文件到本地

实现思路:
​ 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所有主机都可以接收到数据

广播只能用UDP或原始IP实现,不能用TCP

广播的用途:
​ 单个服务器与多个客户主机通信时减少分组流通
以下几个协议都用到广播
​ 1.地址解析协议(ARP)
​ 2.动态主机配置协议(DHCP)
​ 3.网络时间协议(NTP)

广播的特点:
​ 1.处于同一子网的所有主机都必须处理数据
​ 2.UDP数据包会沿协议栈向上一直到UDP层
​ 3.运行音视频等较高速率工作的应用,会带来大负
​ 4.局限于局域网内使用

广播地址
{网络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
今日推荐