目录

  • 编写客户端,使用TFTP协议,完成文件下载的功能。
    • 1.安装tftpd32
    • 2.TFTP协议
    • 3.打开服务器tftpd32
    • 4.客户端
      • 代码实现
      • 执行结果
      • 注意
  • 6.非原创

编写客户端,使用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

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.非原创

更多推荐

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