TFTP 简介、通信过程
TFTP 概述
TFTP : 简单文件传送协议
最初用于引导无盘系统,用来传输小文件
特点:
基于UDP协议实现
不进行用户有效性认证
数据传输模式:
octet:二进制模式
netascii:文本模式
mail:已经不再支持
TFTP 通信过程
TFTP 通信过程总结
- 服务器在69端口等待客户端的请求
- 服务器同意,就使用临时端口与客户端通信
- 每个数据包的编号都会变化(从1开始)
- 每个数据包都要ACK的确认,如果超时,就会重新发送最后的包
- 数据的长度以512byte传输
- 小于512byte的数据意味着传输结束
TFTP 协议分析
注意:
以上的 0 代表的是'\0'
不同的差错码对应不同的错误信息
错误码:
- 0 Undefined 未定义
- 1 File not found 找不到文件
- 2 access violation 访问冲突
- 3 disk full or allocation exceeded 磁盘已满或超过分配
- 4 illegal TFTP operation 非法的TFTP操作
- 5 unknown transfer ID 未知的传输ID
- 6 file already exists 文件已经存在
- 7 no such user 没有这样的用户
- 8 unsupported option requested 不支持的选项请求
TFTP 带选项
读写请求中修改了选项
如果发送带选项的读写请求
tsize 选项
当读操作,tsize 选项参数必须为 0 ,服务器会返回待读取的文件大小
当写操作,tsize 选项参数为待写入的文件大小,服务器会显示该参数
blksize 选项
修改传输文件时使用的数据块大小(范围:8 ~ 65464)
timeout 选项
修改默认的数据传输超时时间(单位:秒)
TFTP通信过程总结(带选项)
- 可以通过发送 带选项的读/写请求 发送给server
- 如果server同意修改选项,就发送选项修改确认包
- server发送的数据,选项修改确认包都是临时port
- server 通过timeout来对丢失数据包的重新发送
练习—TFTP 客户端
要求:
使用TFTP协议,下载server上的文件到本地
实现思路:
- 构造请求报文,送到服务器(69端口)
- 等待服务器回应
- 分析服务器回应
- 接受数据,直到接收到的数据包小于规定数据包大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
static int sockfd;
void sig_dispose(int sig)
{
if(SIGINT == sig)
{
close(sockfd);
puts("\n close!");
exit(0);
}
}
/*
*function:
* tftp客户端程序,从tftp服务器下载程序
*/
int main(int argc, char *argv[])
{
unsigned short p_num = 0;
unsigned char cmd = 0;
char cmd_buf[512] = "";
char recv_buf[516] = "";
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
struct sockaddr_in dest_addr;
int len;
signal(SIGINT, sig_dispose);
if(argc < 3)
{
/* 参数个数小于3,认为输入命令错误 */
printf("cmd example: ./tftpc 192.168.x.x hello.text \n");
return 0;
}
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket error");
exit(-1);
}
bzero(&dest_addr, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(69);
inet_pton(AF_INET, argv[1], &dest_addr.sin_addr);
/* 构造下载请求, argv[2]为文件名 */
len = sprintf(cmd_buf,
"%c%c%s%c%s%c",
0,
1,
argv[2],
0,
"octet",
0);
/* 发送读数据包请求 */
sendto(sockfd,
cmd_buf,
len,
0,
(struct sockaddr *)&dest_addr,
sizeof(dest_addr));
int fd = open(argv[2], O_WRONLY | O_CREAT, 0666);
if(fd < 0)
{
perror("open error");
close(sockfd);
exit(-1);
}
do
{
len =recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&cliaddr_len);
cmd = recv_buf[1];
/* 是否为数据包 */
if(cmd == 3)
{
//接收的包编号是否为上次包的编号+1
if((unsigned short)(p_num + 1) == ntohs(*(unsigned short *)(recv_buf + 2)))
{
write(fd, recv_buf + 4, len -4);
p_num = ntohs(*(unsigned short *)(recv_buf + 2));
printf("recv:%d\n",p_num);
}
recv_buf[1] = 4;
sendto(sockfd,
recv_buf,
4,
0,
(struct sockaddr *)&client_addr,
sizeof(client_addr));
}
else if(cmd == 5)
{
close(sockfd);
close(fd);
unlink(argv[2]); //删除文件
printf("error:%s \n",recv_buf + 4);
exit(-1);
}
}while(len == 516);
//如果收到的数据小于516,则出错
close(fd);
return 0;
}
#include <stdio.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
static int sockfd;
static struct sockaddr_in client_addr;
static void sig_dispose(int sig)
{
if(SIGINT == sig)
{
close(sockfd);
puts("\n nclose");
exit(0);
}
}
int main(int argc, char *argv[])
{
struct sockaddr_in dest_addr;
unsigned char send_buf[1024];
int len;
FILE *fp;
char recv_buf[2048];
char client_ip[INET_ADDRSTRLEN];
unsigned short pack_num;
socklen_t client_addr_len = sizeof(client_addr);
signal(SIGINT, sig_dispose);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
exit(-1);
}
bzero(&dest_addr, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
inet_pton(AF_INET,"192.168.43.76", &dest_addr.sin_addr.s_addr);
bzero(send_buf, sizeof(send_buf));
len = sprintf(send_buf,"%c%c%s%c%s%c%s%c%s%c%s%c%d%c",0,1,"01.jpg",0,
"octet",0,
"tsize",0,
"0",0,
"blksize",0,600,0);
sendto(sockfd, send_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&client_addr_len);
if(recv_buf[1] != 6)
{
perror("fopen:");
close(sockfd);
return 0;
}
len = sprintf(send_buf, "%c%c%c%c", 0, 4, 0, 0);
sendto(sockfd, send_buf, len, 0, (struct sockaddr *)&client_addr,sizeof(client_addr));
fp = fopen("01.jpg","wb");
if(fp == NULL)
{
perror("fopen");
close(sockfd);
return 0;
}
printf("IP:%s port:%d: \n",inet_ntop(AF_INET,
&client_addr.sin_addr,
client_ip,
INET_ADDRSTRLEN),
ntohs(client_addr.sin_port));
len = 604;
while(len == 604)
{
len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&client_addr_len);
pack_num = *(unsigned short *)(recv_buf + 2);
printf("pack_num = %d\n",ntohs(pack_num));
if(recv_buf[1] == 3)
{
send_buf[0] = 0;
send_buf[1] = 4;
send_buf[2] = recv_buf[2];
send_buf[3] = recv_buf[3];
sendto(sockfd,
send_buf,
4,
0,
(struct sockaddr *)&client_addr,
sizeof(client_addr));
fwrite(recv_buf + 4, 1, len -4, fp);
}
else if(recv_buf[1] == 5)
{
puts(recv_buf + 4);
break;
}
}
close(sockfd);
fclose(fp);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <termios.h>
#define GREEN "\e[32m" //打印显示绿色
#define RED "\e[31m" //打印显示红色
#define PRINT(X,Y) { write(1,Y,5);\
printf(X);\
fflush(stdout);\
write(1, "\e[0m", 4);\
}
static int sockfd;
static struct sockaddr_in dest_addr;
void sig_dispose(int sig);
void tftp_down(char *argv);
void tftp_upload(char *argv);
void help_fun(int argc, char *argv[]);
char mygetch();
int main(int argc, char *argv[])
{
char cmd_line[100];
signal(SIGINT, sig_dispose);
bzero(&dest_addr, sizeof(dest_addr));
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket error");
exit(-1);
}
while(1)
{
int cmd;
help_fun(argc, argv);
PRINT("send>", GREEN);
cmd = mygetch();
if(cmd == '1')
{
puts("input file name:");
fgets(cmd_line, sizeof(cmd_line),stdin);
*(strchr(cmd_line,'\n')) = '\0';
tftp_down(cmd_line);
}
else if(cmd == '2')
{
puts("input file name:");
fgets(cmd_line, sizeof(cmd_line), stdin);
*(strchr(cmd_line, '\n')) = '\0';
tftp_upload(cmd_line);
}
else if(cmd == '3')
{
close(sockfd);
system("stty sane"); //回显
exit(0);
}
}
close(sockfd);
return 0;
}
void sig_dispose(int sig)
{
if(SIGINT == sig)
{
close(sockfd);
puts("\nclose");
system("stty sane"); //回显
exit(0);
}
}
void tftp_upload(char *argv)
{
int fd, read_len;
unsigned short p_num = 0;
unsigned char cmd = 0;
char cmd_buf[512] = "";
char recv_buf[516] = "";
struct sockaddr_in client_addr;
int len;
socklen_t cliaddr_len = sizeof(client_addr);
if(dest_addr.sin_port == 0)
{
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(69);
puts("send to IP:");
fgets(recv_buf, sizeof(recv_buf),stdin);
*(strchr(recv_buf, '\n')) = '\0';
inet_pton(AF_INET, recv_buf, &dest_addr.sin_addr);
}
/* 构造下载请求,argv为文件名
* 发送读数据包请求
*/
len = sprintf(cmd_buf, "%c%c%s%c%s%c", 0, 2, argv, 0, "octet", 0);
sendto(sockfd, cmd_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
fd = open(argv, O_RDONLY);
if(fd < 0)
{
perror("open error");
close(sockfd);
exit(-1);
}
do
{
len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&cliaddr_len);
cmd = recv_buf[1];
if(cmd == 4) //是否为ACK
{
p_num = ntohs(*(unsigned short *)(recv_buf + 2));
read_len = read(fd, recv_buf + 4, 512);
printf("recv:%d\n",p_num);
//十进制方式打印包消息
recv_buf[1] = 3; //构造数据包
(*(unsigned short *)(recv_buf + 2)) = htons(p_num + 1);
printf("%s\n", recv_buf+3);
sendto(sockfd,
recv_buf,
read_len + 4,
0,
(struct sockaddr *)&client_addr,
sizeof(client_addr));
}
else if(cmd == 5) //是否为错误应答
{
close(sockfd);
close(fd);
printf("error:%s\n",recv_buf + 4);
exit(-1);
}
}while(read_len == 512); //读取数据小于512认为结束
len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&cliaddr_len);
//接收最后一个ACK确认包
close(fd);
PRINT("Download file is successful \n", RED);
}
void tftp_down(char *argv)
{
int fd;
unsigned short p_num = 0;
unsigned char cmd = 0;
char cmd_buf[512] = "";
char recv_buf[516] = "";
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
int len;
if(dest_addr.sin_port == 0)
{
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(69);
puts("send to IP:");
fgets(recv_buf, sizeof(recv_buf),stdin);
*(strchr(recv_buf, '\n')) = '\0';
inet_pton(AF_INET, recv_buf, &dest_addr.sin_addr);
}
//构造下载请求,argv为文件名
len = sprintf(cmd_buf, "%c%c%s%c%s%c", 0, 1, argv, 0, "octet", 0);
//发送读数据包请求
sendto(sockfd, cmd_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
fd = open(argv, O_WRONLY | O_CREAT, 0666);
if(fd < 0)
{
perror("open error");
close(sockfd);
exit(-1);
}
do
{
//接收服务器发送的内容
len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&cliaddr_len);
cmd = recv_buf[1];
if(cmd == 3) //是否为数据包
{
//接收的包编号是否为上次包编号+1
if((unsigned short)(p_num + 1) == ntohs(*(unsigned short *)(recv_buf + 2)))
{
write(fd, recv_buf + 4, len - 4);
p_num = ntohs(*(unsigned short *)(recv_buf + 2));
//十进制方式打印包编号
printf("recv:%d\n",p_num);
}
recv_buf[1] = 4;
sendto(sockfd,
recv_buf,
4,
0,
(struct sockaddr *)&client_addr,
sizeof(client_addr));
}
else if(cmd == 5) //是否错误应答
{
close(sockfd);
close(fd);
unlink(argv); //删除文件
printf("error:%s\n",recv_buf + 4);
exit(-1);
}
}while((len == 516) || (cmd == 6));
//如果收到的数据小于516则认为出错
close(fd);
PRINT("download file is successful\n",RED);
}
char mygetch()
{
struct termios oldt, newt;
char ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
void help_fun(int argc, char *argv[])
{
printf("1 down\n");
printf("2 upload\n");
printf("3 exit\n");
return;
}
UDP 广播
广播:由一台主机向该主机所在子网内所有主机发送数据方式
广播只能用于UDP或原始IP实现,不能用TCP
广播的用途
单个服务器与多个客户主机通信时减少分组流通
以下协议用到广播
- 地址解析协议(ARP)
- 动态主机配置协议(DHCP)
- 网络时间协议(NTP)
UDP广播的特点
- 处在同一子网的所有主机都必须处理数据
- UDP数据包会沿协议栈上一直到UPD层
- 运行音视频等高速率工作的应用,会带来大负
- 局限于局域网内使用
UDP广播地址
{网络ID,主机ID}
网络ID 表示 由子网掩码中 1 覆盖的连续位
主机ID 表示 由子网掩码中 0 覆盖的连续位
定向广播地址:主机ID全 1
- 192.168.220.0/24,其定向广播地址为 192.168.220.255
- 通常路由器不转发该广播
受限广播地址:255.255.255.255
路由器从不转发该广播
广播与单播的对比
单播
广播
套接口选项
/*
*成功: 执行 0
*失败: -1
*/
int setsockopt(int sockfd, int level, int optname, const viod *optval, socklen_t optlen);
广播示例
int main(int argc, char *argc[])
{
int sock_fd = 0;
char buff[1024] = "";
unsigned short port = 8000;
struct sockaddr_in send_addr;
bzero(&send_addr, sizeof(send_addr));
send_addr.sin_family = AF_INET;
send_addr.sin_port = htons(port);
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket failed");
close(sock_fd);
exit(1);
}
if(argc > 1)
{
send_addr.sin_addr.s_addr = inet_addr(argv[1]);
}
else
{
printf("not have a server IP");
exit(1);
}
int yes = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
strcpy(buf, "boardcast sucess");
int len = sendto(sock_fd,
buff,
strlen(buff),
0,
(struct sockaddr *)&send_addr,
sizeof(send_addr));
if(len < 0)
{
printf("send error \n");
close(sock_fd);
exit(1);
}
return 0;
}
UDP 多播
多播概述
多播:
数据的收发仅仅在同一个分组中进行
多播的特点:
- 多播地址标识一组接口
- 多播可以用于广域网使用
- 在ipv4中,多播是可选的
多播地址
IPv4 的 D 类地址是多播地址
十进制:224.0.0.1 239.255.255.254
十六进制:E0.00.00.01 EF.FF.FF.FE
多播地址向以太网 MAC 地址的映射
UDP 多播工作过程
多播地址结构体
在 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
};
多播套接口选项
/*
*成功: 执行 0
*失败: -1
*/
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
加入多播组示例
char group[INET_ADDRSTRLEN] = "244.0.1.1";
struct ip_mreq mreq; //定义一个多播组地址
mreq.imr_multiaddr.s_addr = inet_addr(group); //添加一个多播组IP
mreq.imr_interface.s_addr = htonl(INADDR_ANY); //添加一个将要添加到多播组的IP
setsockopt(sockfd, IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));