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
- The server waits for the client's request on port 69
- If the server approves the request, it will use the ephemeral port to communicate with the client
- The number of each packet changes (starts from 1)
- Every data packet needs to be confirmed by ACK If there is a timeout, the last packet (data or ACK) needs to be resent
- The length of the data is transmitted in 512Bytes
- 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的数据(1和0)
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;
}