Linux&C语言简单实现客户端使用TFTP协议文件下载-网络编程-应用层

编写客户端,使用TFTP协议,完成文件下载的功能。

在这里插入图片描述

  • 基于C语言实现,TFTP练习——用recvfrom( )/ sendto( )
  • 命令行输入指定IP、端口

1.安装tftpd32

教程链接

2.TFTP协议

  1. 数据传输模式:

octet:二进制模式

  1. TFTP通信过程
    在这里插入图片描述
  2. TFTP通信过程

1、服务器在69号端口等待客户端的请求
2、服务器若批准此请求,则使用临时端口与客户端进行通信
3、每个数据包的编号都有变化(从1开始)
4、每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的包(数据或ACK)
5、数据的长度以512Byte传输
6、小于512Byte的数据意味着传输结束

  1. TFTP协议分析
    在这里插入图片描述

3.打开服务器tftpd32

 选择IP 日志查看

4.客户端

代码实现

//-------编写客户端, 使用TFTP协议,完成文件下载的功能----------
#include <stdio.h>
/*exit*/
#include <stdlib.h>
/*socket*/
#include <sys/types.h>
#include <sys/socket.h>
/*sockaddr_in结构体*/
#include <netinet/in.h>
#include <netinet/ip.h>
/*manset*/
#include <string.h>
/*socket*/
#include <arpa/inet.h>
/*write-close*/
#include <unistd.h>
/*open*/
#include <sys/stat.h>
#include <fcntl.h>

#define ERRLOG(errmsg)                                       \
    do                                                       \
    {                                                        \
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                      \
        exit(-1);                                            \
    } while (0)
int main(int argc, char const *argv[])
{
    //检测命令行3个参数
    if (3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }
    // 1.创建套接字
    // socket返回的文件描述符 //IPV4 //UDP
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
        ERRLOG("socket error");

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr)); //清空

    server_addr.sin_family = AF_INET; // IPV4
LOOP://注意 goto的入口,因为即使是出错,erver_addr中的 69 也被临时端口覆盖了
    //端口号  填69
    //将无符号2字节整型  主机-->网络//atoi输入字符串转换为一个整数
    server_addr.sin_port = htons(atoi(argv[2]));
    // windows 的ip地址
    //将所指的字符串转换成32位的网络字节序二进制值
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    //-----------------------------------------------
    //数据包--数据的长度以512Byte传输
    unsigned char buff[600] = {0};
    //返回的ACK
    unsigned char _ack[4];
    unsigned short code = 0; //操作码
    unsigned short num = 0;  //块编号 或者 错误码
    //数据的长度以512Byte传输
    char text[512] = {0}; //文件内容 或 错误信息
    //校验收到的块编号
    int N = 0;
    //返回的文件描述符
    int fd;
    int ret = 0;

    //输入文件名
    char filename[32] = {0};
    printf("下载文件名: ");
    scanf("%s", filename);

    //使用 sprintf 组包
    //返回值:成功格式化字符的个数           //-读-  //文件名 //0 //二进制模式//0
    ret = sprintf(buff, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0);

    //首次发送请求-----想要发送的数据的字节数-阻塞
    if (-1 == sendto(sockfd, buff, ret, 0, (struct sockaddr *)&server_addr, server_addr_len))
        ERRLOG("recvfrom error");

    //循环接收服务器发来的数据包
    while (1)
    {
        //接收--需要保存服务器的网络信息结构体  因为里面有临时端口
        if (-1 == (ret = recvfrom(sockfd, buff, 600, 0, (struct sockaddr *)&server_addr, &server_addr_len)))
            ERRLOG("recvfrom error");

        //解析数据包中的内容
        //将无符号2字节整型  网络-->主机
        //解析操作码
        code = ntohs(*(unsigned short *)buff);
        //解析块编号 或者 错误码
        num = ntohs(*(unsigned short *)(buff + 2));
        //解析文件内容 或 错误信息
        strncpy(text, buff + 4, 512);
        if (3 == code && num == N + 1)
        {
            //校验块编号+1
            N++;
            //要接收的数据包
            //如果是第一次接到数据包 要创建文件
            if (num == 1)
            {
                if (-1 == (fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)))
                    ERRLOG("open error");
            }
            //将文件内容写入文件
            if (-1 == write(fd, text, ret - 4))
                ERRLOG("write error");
            // 组装ACK
            *(unsigned short *)_ack = htons(4);
            *(unsigned short *)(_ack + 2) = htons(num);
            回复ACK包
            if (-1 == sendto(sockfd, _ack, 4, 0, (struct sockaddr *)&server_addr, server_addr_len))
                ERRLOG("recvfrom error");
            //文件接收完毕
            if (ret < 512)
                break;
        }
        else if (5 == code)
        {
            printf("接收出错[%s]\n", text); //错误信息
            goto LOOP;
        }
    }

    printf("文件[%s]下载完成\n", filename);
    close(sockfd);
    return 0;
}

执行结果

tftpd32目录下
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

注意

未知原因报警告

文件重命名解决

在这里插入图片描述在这里插入图片描述

6.非原创

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TFTP协议是一种简单文件传输协议,它可以用于在计算机之间传输文件TFTP协议基于UDP协议,因此它不具备TCP协议的可靠传输保证。本文将介绍如何使用C语言实现TFTP协议客户和服务。 ## TFTP协议概述 TFTP协议是一种基于UDP协议文件传输协议,它采用简单的请求/响应模式进行通信。TFTP协议主要有两种模式,分别是Netascii模式和Octet模式。 Netascii模式是一种ASCII码模式,它将传输的文件视为ASCII码字符流进行传输。在Netascii模式下,每个文件的换行符会被转换成Telnet的行结束符CR-LF(回车换行)。 Octet模式是一种二进制模式,它将传输的文件视为二进制数据流进行传输。在Octet模式下,每个文件的换行符会被保留。 TFTP协议定义了五种不同的请求/响应类型,分别是: - RRQ:读请求 - WRQ:写请求 - DATA:数据包 - ACK:确认包 - ERROR:错误包 ## TFTP客户实现 TFTP客户主要有两个功能,分别是向服务器请求文件和向服务器发送文件。下面是使用C语言实现TFTP客户的代码示例。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define SERVER_IP "127.0.0.1" #define SERVER_PORT 69 #define BUF_SIZE 512 void error(char *msg) { perror(msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, n; struct sockaddr_in servaddr; char buf[BUF_SIZE]; if (argc < 3) { fprintf(stderr,"usage: %s <filename> <mode>\n", argv[0]); exit(1); } // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) error("ERROR opening socket"); // 设置服务器地址 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); servaddr.sin_port = htons(SERVER_PORT); // 发送读请求 sprintf(buf, "%c%c%s%c%s%c", 0, 1, argv[1], 0, argv[2], 0); n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); if (n < 0) error("ERROR sending read request"); // 接收数据 FILE *fp = fopen(argv[1], "wb"); while (1) { // 接收数据包 struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len); if (n < 0) error("ERROR receiving data"); // 解析数据包 unsigned short opcode = ntohs(*(unsigned short *)buf); unsigned short block = ntohs(*(unsigned short *)(buf + 2)); if (opcode == 3) { // 写入文件 fwrite(buf + 4, 1, n - 4, fp); // 发送确认包 sprintf(buf, "%c%c%c%c", 0, 4, buf[2], buf[3]); n = sendto(sockfd, buf, 4, 0, (struct sockaddr *)&cliaddr, len); if (n < 0) error("ERROR sending ACK"); if (n < BUF_SIZE - 4) break; } else if (opcode == 5) { // 接收到错误包 fprintf(stderr, "ERROR: %s\n", buf + 4); break; } else { // 接收到无法识别的数据包 fprintf(stderr, "ERROR: unrecognized packet\n"); break; } } fclose(fp); close(sockfd); return 0; } ``` 上面的代码实现TFTP客户的读请求功能,它会向服务器发送一个RRQ请求,并接收服务器返回的数据包。如果接收到的数据包是一个数据包,则将数据写入指定的文件中,并发送一个ACK确认包。 ## TFTP服务实现 TFTP服务主要有两个功能,分别是接收客户文件请求和接收客户发送的文件。下面是使用C语言实现TFTP服务的代码示例。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define SERVER_PORT 69 #define BUF_SIZE 512 void error(char *msg) { perror(msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, n; struct sockaddr_in servaddr, cliaddr; socklen_t len; char buf[BUF_SIZE]; // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) error("ERROR opening socket"); // 设置服务器地址 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(SERVER_PORT); // 绑定套接字 if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) error("ERROR on binding"); while (1) { // 接收请求 len = sizeof(cliaddr); n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len); if (n < 0) error("ERROR receiving request"); // 解析请求 unsigned short opcode = ntohs(*(unsigned short *)buf); if (opcode != 1) { // 接收到非读请求 sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 4, "Unsupported request", 0); n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len); if (n < 0) error("ERROR sending error packet"); } else { // 发送数据 FILE *fp = fopen(buf + 2, "rb"); if (fp == NULL) { // 文件不存在 sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 1, "File not found", 0); n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len); if (n < 0) error("ERROR sending error packet"); } else { // 文件存在,发送数据包 unsigned short block = 1; while (1) { // 读取文件 n = fread(buf + 4, 1, BUF_SIZE - 4, fp); if (n < BUF_SIZE - 4) { if (feof(fp)) { // 文件读取完成 sprintf(buf, "%c%c%c%c", 0, 3, block >> 8, block & 0xFF); n += 4; break; } else { // 文件读取出错 sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 2, "Error reading file", 0); n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len); if (n < 0) error("ERROR sending error packet"); fclose(fp); break; } } else { // 发送数据包 sprintf(buf, "%c%c%c%c", 0, 3, block >> 8, block & 0xFF); n += 4; block++; } n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, len); if (n < 0) { fclose(fp); error("ERROR sending data"); } // 接收ACK确认包 n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len); if (n < 0) { fclose(fp); error("ERROR receiving ACK"); } opcode = ntohs(*(unsigned short *)buf); unsigned short ack_block = ntohs(*(unsigned short *)(buf + 2)); if (opcode != 4 || ack_block != block - 1) { // 接收到错误的ACK确认包 sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 0, "ACK packet error", 0); n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len); if (n < 0) error("ERROR sending error packet"); fclose(fp); break; } } fclose(fp); } } } close(sockfd); return 0; } ``` 上面的代码实现TFTP服务的读请求功能,它会接收客户发来的RRQ请求,并将指定的文件内容发送给客户。如果接收到的数据包是一个ACK确认包,则继续发送下一个数据包。如果接收到的ACK确认包有误,则发送一个错误包告知客户

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值