TFTP简介及通信过程
TFTP简单文件传送协议
最初用于引导无盘系统,被设计用来传输小文件
特点
1、基于UDP协议实现
2、不进行用户有效性认证
数据传输模式
1、octet:二进制模式
2、netascii:文本模式
3、mail:已经不再支持
TFTP通信过程总结
TFTP协议分析
tftp ERROR错误码
TFTP客户端
使用TFTP协议,下载server上的文件到本地
实现思路
1、构造请求报文,送至服务器69号端口
2、等待服务器回应
3、分析服务器回应
4、接收数据,直到接收的数据包小于规定的数据长度
TFTP流程图
1、开始
2、创建UDP套接字
3、构造读请求数据包并发送到69端口
4、创建下载文件(最好在接收到服务端应答后再创建文件)
5、接收数据
6、判断数据的操作码
- CMD = 3(数据包) => 保存数据到文件 => 构造ACK包并发送到对方临时端口 => 数据长度小于512则关闭文件
- CMD = 5(错误包) => 删除文件
- CMD = 6(Oack相关包) => 发送ACK确认包
7、结束
tftp客户端下载文件举例
1、服务端使用tftp软件
2、客户端下载文件代码示例
#include <stdio.h> // printf
#include <sys/types.h> //
#include <sys/socket.h> // socket
#include<stdlib.h> // exit
#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指令并发送给服务器 sprintf指的是字符串格式化命令 例如: 01test.txt0octet0
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)) < 0)
{
perror("fail to recvfrom");
exit(1);
}
// 数据包:操作码(2Bytes) + 块编号(2Bytes) + 数据(512Bytes Data)
printf("操作码:%d, 块编号:%u\n", text[1], ntohs(*(unsigned short *)(text+2)));
// printf("数据:%s\n", text+4);
// 判断操作码执行相应的处理
if(text[1] == 5)
{
// printf("error: %s\n", text+4);
printf("差错码是:%d ,差错信息error: %s\n", ntohs(*(unsigned short *)(text+2)), 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("文件下载完毕\n");
return ;
}
}
}
}
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; // 协议族,AF_INET:ipv4网络协议
serveraddr.sin_addr.s_addr = inet_addr(argv[1]); // tftp服务端的ip地址
serveraddr.sin_port = htons(69); // 端口号
do_download(sockfd, serveraddr); // 下载操作
return 0;
}
UDP广播
广播:由一台主机向该主机所在的子网内的所有主机发送数据的方式
广播只能用UDP或原始IP实现,不能用TCP
广播的用途
单个服务与多个客户主机通信时减少分组流通
以下几个协议都用到广播
1、地址解析协议ARP
2、动态主机配置协议DHCP
3、网络时间协议NTP
UDP广播的特点
1、处于同一子网的所有主机都必须处理数据
2、UDP数据包会沿协议栈向上一直到UDP层
3、运行音视频等较高速率工作的应用,会带来大负荷
4、局限于局域网内使用
广播地址
广播与单播
广播流程
-
发送者:
- 第一步:创建套接字socket()
- 第二步:设置为允许发送广播权限setsockopt()
- 第三步:向广播地址发送sendto()
-
接收者:
- 第一步:创建套接字socket()
- 第二步:将套接字与广播的信息结构体绑定bind()
- 第三步:接收数据recvfrom()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
setsockopt函数=>允许发送广播数据
setsockopt
广播发送者
广播发送者
#include <stdio.h> // printf
#include <sys/types.h> //
#include <sys/socket.h> // socket
#include<stdlib.h> // exit
#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>
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %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; // 协议族,AF_INET:ipv4网络协议
broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); // 173.0.255.255或255.255.255.255
broadcataddr.sin_port = htons(atoi(argv[2])); // 设置端口
// 发送数据
char buf[128] ="";
while (1)
{
fgets(buf, 128, stdin);
buf[strlen(buf) - 1] = '\0'; // 把buf字符串中的\n转化为\0
if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&broadcataddr, addrlen) == -1)
{
perror("fail to sendto");
exit(1);
}
}
return 0;
}
广播接收者
广播接收者
#include <stdio.h> // printf
#include <sys/types.h> //
#include <sys/socket.h> // socket
#include<stdlib.h> // exit
#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>
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %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; // 协议族,AF_INET:ipv4网络协议
broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); // 173.0.255.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;
// socklen_t addrlen = sizeof(struct sockaddr_in);
while (1)
{
if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
{
perror("fail to recvfrom");
exit(1);
}
// 打印接收到的数据 打印客户端的ip地址和port
printf("[%s - %d]: %s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), text);
}
return 0;
}
执行结果
多播概述
多播:数据的收发仅仅在同一分组中进行
多播的特点
1、多播地址标示一组接口
2、多播可以用于广域网使用
3、在IPv4中,多播是可选的
比起广播,多播具有可控性
只有加入多播组的接收者才可以接收到数据,否则接收不到
多播工作过程
多播流程
- 发送者
- 第一步:创建套接字socket()
- 第二步:向多播地址发送数据sendto()
- 接收者
- 第一步:创建套接字socket()
- 第二步:设置为加入多播组setsockopt()
- 第三步:将套接字与多播信息结构体绑定bind()
- 第四步:接收数据
多播地址结构体
在IPv4因特网(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示
setsockopt函数=>设置加入多播组
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
加入多播组示例
多播发送者
多播发送者
#include <stdio.h> // printf
#include <sys/types.h> //
#include <sys/socket.h> // socket
#include<stdlib.h> // exit
#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>
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %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; // 协议族,AF_INET:ipv4网络协议
groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); // 224.x.x.x - 239.x.x.x
groupcastaddr.sin_port = htons(atoi(argv[2])); // 设置端口
// 进行通信
char buf[128] ="";
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0'; // 把buf字符串中的\n转化为\0
if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
{
perror("fail to sendto");
exit(1);
}
}
return 0;
}
多播接收者
多播接收者
#include <stdio.h> // printf
#include <sys/types.h> //
#include <sys/socket.h> // socket
#include<stdlib.h> // exit
#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>
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %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; // 协议族,AF_INET:ipv4网络协议
groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); // 224.x.x.x - 239.x.x.x
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;
// socklen_t addrlen = sizeof(struct sockaddr_in);
while (1)
{
if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
{
perror("fail to recvfrom");
exit(1);
}
// 打印接收到的数据 打印客户端的ip地址和port
printf("[%s - %d]: %s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), text);
}
return 0;
}