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;
}
下载功能截图:![](https://i-blog.csdnimg.cn/blog_migrate/e5c4323d559ee2f16b60fb9c49bc34c6.png)
上传功能截图:
退出运行截图: