网络编程DAY 4(TFTP客户端)

 tftp_client.h:

/***********************************************************************
 * File name    : 
 * Module Name  : (omit)
 * Author       : George
 * Create Date  : 
 * Abstract Description: (description omit)
 * 
 * ------------------------- Revision History -------------------------
 *  No  Version Date        Revise By   Item        Deacription
 *  1   V1.0    0000.00.00  George      (omit)      (omit)
 * 
 ***********************************************************************/ 

#ifndef TFTP_CLIENT
#define TFTP_CLIENT

/***********************************************************************
 *  (1)Debug switch Section
 ***********************************************************************/


/***********************************************************************
 *  (2)Include File Section
 ***********************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

 /***********************************************************************
 *  (3)Macro Define Section
 ***********************************************************************/
#define OK 1
#define ERROR 0

#define MSG_ERR_MAIN(NUM, INFO) do{\
    if(NUM < 0)\
    {\
        perror(INFO);\
        return ERROR;\
    }\
    else\
    {\
        printf(INFO);\
        printf(" success __%d__.\n", __LINE__);\
    }\
}while(0)
#define MSG_ERR_LOOP(NUM, INFO) do{\
    if(NUM < 0)\
    {\
        perror(INFO);\
        continue;\
    }\
    else\
    {\
        printf(INFO);\
        printf(" success __%d__.\n", __LINE__);\
    }\
}while(0)

#define IP      "192.168.9.72"
// #define IP      "192.168.71.33"
#define PORT    69

// tftp协议宏定义[操作码]
#define RD      ((short)1)
#define WR      ((short)2)
#define DATA    ((short)3)
#define ACK     ((short)4)
#define ERR     ((short)5)
// tftp协议宏定义[各部分]
#define BUF         buf
#define OPCODE      (BUF)
// 读写请求          [操作码-文件名-0-模式-0]
#define FILENAME    ((BUF)+2)
#define MODE        ((BUF)+2)+((strlen(FILENAME)+1))
#define RDWR_LEN    (short)(sizeof(short) + strlen(FILENAME)+1 + strlen(MODE)+1)
// 数据包 ACK        [操作码-块编号-数据][操作码-块编号]
#define BLOCK       ((BUF)+2)
#define DATAMSG     ((BUF)+4)
#define BAG_LEN     (short)(sizeof(short) + sizeof(short) + 512)
#define ACK_LEN     (short)(sizeof(short) + sizeof(short))
// ERROR            [操作码-差错码-差错信息-0]
#define ERRNUM      ((BUF)+2)
#define ERRMSG      ((BUF)+4)
// 取2字节的宏函数
#define SHORT(P)    (*((short *)(P)))
// 清空整个容器的宏函数
#define CLEANBUF    (bzero((BUF),(sizeof(BUF))))
 /***********************************************************************
 *  (4)Struct(Data Types) Define Section
 ***********************************************************************/


 /***********************************************************************
 *  (5)Prototype Declare Section
 ***********************************************************************/


#endif

tftp_client.c:

#include "tftp_client.h"

int     ret = 0;        // int型数据返回值,用于宏函数判断调用函数返回正误
ssize_t res = 0;        // ssize_t型数据返回值,用于宏函数判断调用函数返回正误
char    buf[1024] = ""; // 容器,用于传输信息给tftp服务器

int main(int argc, char const *argv[])
{
    // 第一步:创建套接字
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    MSG_ERR_MAIN(cfd, "socket");

    // 第二步:修改套接字属性允许快速启用端口
    int reuse = 1;
    ret = setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    MSG_ERR_MAIN(ret, "setsockopt");

    // 第三步:绑定IP[忽略]
    // 第四步:连接[限定只接收指定ip服务器的信息][忽略]

    // 第五步:创建结构体录入目标服务器信息
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);

    short opcode        = 0;    // 全:操作码
    int buflen          = 0;    // 全:长度参数
    char filename[128]  = "";   // 读写请求:文件名
    char mode[8]        = "";   // 读写请求:模式
    short blockflag     = 1;    // ACK:最近收到的数据包块编号
    int fd              = 0;    // 用于下载文件的打开文件描述符
    ssize_t ret         = 0;    // 用于上传/下载文件功能的退出条件
    short func          = 0;    // 用于区分功能的判断值[为RD时下载|为WR时上传]
    // 整个对话的循环
    struct sockaddr_in tmp;
    tmp.sin_family      = AF_INET;
    socklen_t tmp_len   = sizeof(tmp);
    while(1)
    {
        system("clear");
        printf("1. download\n2. upload\n3. exit\n");
        /// 第一部分:打开文件的循环
        while(1)
        {
            CLEANBUF;
            /// 第一步:录入读写请求
            // 读写请求:操作码
            #if 1
            {
                printf("Please input opcode >>> ");
                fscanf(stdin, "%hd", &opcode);
                func = opcode;
                if(3 == func)// 出口
                {
                    break;
                }
            }
            #else
            {
                printf("Please input opcode >>> 1\n");
                opcode = RD;
            }
            #endif
            // 读写请求:文件名
            #if 1
            {
                printf("Please input filename >>> ");
                getchar();
                fgets(filename, sizeof(filename), stdin);
                filename[strlen(filename)-1] = '\0';// \n
            }
            #else
            {
                printf("Please input filename >>> 1_udpSer.c\n");
                strcpy(filename, "1_udpSer.c");
            }
            #endif
            // 读写请求:模式
            #if 0
            {
                printf("Please input mode[octet/mail] >>> ");
                fgets(mode, sizeof(mode), stdin);
                mode[strlen(mode)-1] = '\0';// \n
            }
            #else
            {
                printf("Please input mode[octet/mail] >>> octet\n");
                strcpy(mode, "octet");
            }
            #endif
            
            /// 第二步:打包读写请求
            SHORT(OPCODE) = htons(opcode);
            strcpy(FILENAME, filename);
            strcpy(MODE, mode);

            /// 第三步:验证读写请求
            printf("\topcode : %hd\n", htons(SHORT(OPCODE)));
            printf("\tfilename : %s\n", FILENAME);
            printf("\tmode : %s\n", MODE);
            buflen = RDWR_LEN;

            /// 第四步:发送读写请求
            res = sendto(cfd, (void *)buf, buflen, 0, (struct sockaddr *)&sin, sizeof(sin));
            MSG_ERR_LOOP(res, "sendto");


            CLEANBUF;
            /// 第一步:服务器信息接收
            res = recvfrom(cfd, (void *)buf, sizeof(buf), 0, (struct sockaddr *)&tmp, &tmp_len);
            MSG_ERR_LOOP(res, "recvfrom");
            printf(">>>>>> 端口号: %d\n", htons(tmp.sin_port));

            /// 第二步:服务器信息分类
            if(DATA == ntohs(SHORT(OPCODE)))// 数据包
            {
                printf("读写请求发送成功...\n");
                ret = res;
                /// 下一步:进入下载文件循环
                break;
            }
            else if(ACK == ntohs(SHORT(OPCODE)))// ACK包
            {
                printf("读写请求发送成功...\n");
                printf("接收到的操作码 : [%hd]%hd\n", ntohs(SHORT(OPCODE)), ntohs(SHORT(BLOCK)));
                /// 下一步:进入上传文件循环
                break;
            }
            else// 其它[ERROR包]
            {
                /// 打印错误信息
                printf("%s[%d] >>> [%hd]%s __%d__\n", \
                IP, PORT, ntohs(SHORT(OPCODE)), ERRMSG, __LINE__);
                /// 下一步:重新发送读写请求
                continue;
            }
        }/// 第一部分:打开文件的循环[结束]

        if(3 == func){break;}// 出口

        /// /// [下载文件]功能 /// ///
        if(RD == func)
        {
            fd          = 0;    // 用于下载文件的打开文件描述符        [循环初始化]
            opcode      = ACK;  // 全:操作码                       [循环初始化>>>固定为发送ACK包]
            blockflag   = 1;    // ACK:最近收到的数据包块编号        [循环初始化]
            
            /// 第二部分:下载文件的循环
            while(1)
            {
                // 接续发送读写请求收到的数据包
                if(1 == blockflag)// 写入第一个读取的数据包
                {
                    /// 第一步:打开文件
                    if(0 == fd || fd < 0)
                    {
                        // 读写请求:输入本机下载文件名
                        bzero(filename, sizeof(filename));
                        printf("Please input the filename for download >>> ");
                        fgets(filename, sizeof(filename), stdin);
                        filename[strlen(filename)-1] = '\0';// \n
                        if(0 == strlen(filename))// 若无输入则选择默认下载文件名
                        {
                            // strcpy(filename, "./down/download.txt");
                            strcpy(filename, "./down/download.png");
                        }
                        umask(0);
                        fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664);
                        if(fd < 0)// 打开文件失败重新输入存储文件名
                        {
                            perror("open");
                            continue;
                        }
                    }
                    /// 第二步:测试信息
                    // printf("将发送:%d\t已收到:%d __%d__\n", blockflag, ntohs(SHORT(BLOCK)), __LINE__);
                    printf("%s", DATAMSG);
                    /// 第三步:录入数据
                    write(fd, DATAMSG, ret-4);

                    // 文件读完
                    if(ret < 516 && DATA == htons(SHORT(OPCODE)))
                    {
                        printf("\n>>>>>> 文件下载完毕,最后一次传输%ld字节...\n", ret);
                        break;
                    }

                    /// 第一步:封装ACK包
                    CLEANBUF;
                    SHORT(OPCODE)   = htons(opcode);
                    SHORT(BLOCK)    = htons(blockflag);
                    buflen = ACK_LEN;
                    /// 第二步:测试信息
                    // printf("将发送:%d\t已收到:%d __%d__\n", blockflag, ntohs(SHORT(BLOCK)), __LINE__);
                    /// 第三步:发送信息
                    res = sendto(cfd, (void *)buf, buflen, 0, (struct sockaddr *)&tmp, tmp_len);
                    MSG_ERR_LOOP(res, "sendto");
                }
                // 接收其它读写请求发送的数据包
                /// 第一步:接收信息
                CLEANBUF;
                res = recvfrom(cfd, (void *)buf, sizeof(buf), 0, (struct sockaddr *)&tmp, &tmp_len); ret = res;
                MSG_ERR_LOOP(res, "recvfrom");
                /// 第二步:判断信息
                if(DATA == htons(SHORT(OPCODE)))// 数据包
                {
                    /// 第三步:测试信息
                    // printf("将发送:%d\t已收到:%d __%d__\n", blockflag, ntohs(SHORT(BLOCK)), __LINE__);
                    // 若收到的数据包块编号是已收到的下一个
                    if((blockflag+1) == htons(SHORT(BLOCK)))
                    {
                        // 第三步:测试信息
                        printf("\n>>>>>> %s", DATAMSG);
                        // 第四步:录入数据
                        write(fd, DATAMSG, ret-4);

                        // 文件读完
                        if(ret < 516 && DATA == htons(SHORT(OPCODE)))
                        {
                            printf("\n>>>>>> 文件下载完毕,最后一次传输%ld字节...\n", ret);
                            break;
                        }

                        // 第五步:回应块编号加一
                        blockflag++;
                        printf("\n>>>>>> 发送块编号加一: %hd\n", blockflag);
                        // sleep(3);
                    }
                    /// 第一步:封装ACK包
                    CLEANBUF;
                    SHORT(OPCODE)   = htons(opcode);
                    SHORT(BLOCK)    = htons(blockflag);
                    buflen = ACK_LEN;
                    /// 第二步:测试信息
                    printf("\n>>>>>> 将发送:%d\t已收到:%d[%ld] __%d__\n", blockflag, ntohs(SHORT(BLOCK)), ret, __LINE__);
                    /// 第三步:发送信息
                    res = sendto(cfd, (void *)buf, buflen, 0, (struct sockaddr *)&tmp, tmp_len);
                    MSG_ERR_LOOP(res, "sendto");
                }
                else// 其它[ERROR包]
                {
                    /// 打印错误信息
                    printf("\n>>>>>> %s[%d] >>> [%hd]%s __%d__\n", \
                    IP, PORT, ntohs(SHORT(OPCODE)), ERRMSG, __LINE__);
                    /// 重新读写文件
                    // break;
                    /// 继续发送ACK
                    continue; sleep(1);
                }
            }/// 第二部分:下载文件的循环[结束]
        }/// /// 下载文件功能[结束] /// ///
        
        /// /// [上传文件]功能 /// ///
        if(WR == func)
        {
            fd          = 0;    // 用于下载文件的打开文件描述符        [循环初始化]
            opcode      = DATA; // 全:操作码                       [循环初始化>>>固定为发送数据包]
            blockflag   = 0;    // ACK:最近收到的数据包块编号        [循环初始化]

            /// 第二部分:上传文件的循环
            while(1)
            {
                /// 第一步:打开文件
                if(0 == fd || fd < 0)
                {
                    // 读写请求:输入本机下载文件名
                    bzero(filename, sizeof(filename));
                    printf("Please input the filename for upload >>> ");
                    fgets(filename, sizeof(filename), stdin);
                    filename[strlen(filename)-1] = '\0';// \n
                    if(0 == strlen(filename))// 若无输入则选择默认上传文件名
                    {
                        // strcpy(filename, "./upload/1.txt");
                        strcpy(filename, "./upload/des.jpg");
                    }
                    fd = open(filename, O_RDONLY);
                    if(fd < 0)// 打开文件失败重新输入上传文件名
                    {
                        perror("open");
                        continue;
                    }
                }

                /// 发送 ///
                printf(">>>>>> 刚发送: [%d]%d[%ld Byte]\t已收到:%d __%d__\n", opcode, blockflag, ret, ntohs(SHORT(BLOCK)), __LINE__);
                /// 第一步:封装数据包
                SHORT(OPCODE)   = htons(opcode);
                if(blockflag == ntohs(SHORT(BLOCK)))// 若对方已收到刚发送的上传文件块编号, 准备下一次发送的块编号和数据
                {
                    // 第一步:录入数据
                    ret = read(fd, DATAMSG, 512);
                    if(ret < 0)// 读文件出错
                    {
                        perror("read");
                        break;// 退出本次上传文件读写
                    }

                    // 第二步:发送块编号加一
                    blockflag++;
                    printf(">>>>>> 发送块编号加一: %hd\n", blockflag);
                    // sleep(3);
                }
                else
                {
                    // 第一步:录入数据
                    lseek(fd, -ret, SEEK_CUR);
                    ret = read(fd, DATAMSG, 512);
                    if(ret < 0)// 读文件出错
                    {
                        perror("read");
                        break;// 退出本次上传文件读写
                    }
                }
                SHORT(BLOCK)    = htons(blockflag);
                buflen = BAG_LEN;

                /// 第二步:测试信息
                printf("%s\n", DATAMSG);

                /// 第三步:发送信息
                res = sendto(cfd, (void *)buf, buflen, 0, (struct sockaddr *)&tmp, tmp_len);
                MSG_ERR_LOOP(res, "sendto");
                
                /// 第四步:判断结束
                if(ret < 512)// 上传文件读到末尾
                {
                    printf(">>>>>> 文件上传完毕,最后一次传输%ld字节...\n", ret);
                    break;
                }

                /// 接收 ///
                /// 第一步:接收信息
                CLEANBUF;
                res = recvfrom(cfd, (void *)buf, sizeof(buf), 0, (struct sockaddr *)&tmp, &tmp_len);
                // sleep(1);

                /// 第二步:测试信息
                printf(">>>>>> 刚发送: [%d]%d[%ld Byte]\t已收到:%d __%d__\n", opcode, blockflag, ret, ntohs(SHORT(BLOCK)), __LINE__);
                if(ERR == htons(SHORT(OPCODE)))
                {
                    printf(">>>>>> 接收到的错误信息: [%hd]%s\n", ntohs(SHORT(ERRNUM)), ERRMSG);
                    continue;
                }
                else if(ACK == htons(SHORT(OPCODE)))
                {
                    printf(">>>>>> 接收到的ACK码 : [%hd]%hd\n", ntohs(SHORT(OPCODE)), ntohs(SHORT(BLOCK)));
                    continue;
                }

            }/// 第二部分:上传文件的循环[结束]
        }/// /// 上传文件功能[结束] /// ///

        printf(">>>>>> 本次读写请求结束...按任意键继续运行\n");
        getchar();
    }// 整个对话的循环[结束] //

    // 最后一步:关闭套接字的文件描述符
    close(cfd);
    printf("客户端结束运行...\n");
    return 0;
}

下载功能截图:

上传功能截图:

   退出运行截图:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值