//-------编写客户端, 使用TFTP协议,完成文件上传及下载的功能----------
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
enum OPCODE{
RRQ = 1,
WRQ,
DATA,
ACK,
ERROR
};
int main(int argc, char const *argv[])
{
//检测命令行4个参数
if (4 != argc){
printf("<format>:%s [ipaddr] [mode] [filename]\n",argv[0]);
printf(" mode:\"get\" means get file by server.\n"); /* get 下载文件 */
printf(" \"put\" means put file to server.\n"); /* put 上传文件 */
return -1;
}
if( !(!strcmp(argv[2],"get") || !strcmp(argv[2],"put"))){
printf("[Error] **Invalid argument.\n");
printf("<format>:%s [ipaddr] [mode] [filename]\n",argv[0]);
printf(" mode:\"get\" means get file by server.\n"); /* get 下载文件 */
printf(" \"put\" means put file to server.\n"); /* put 上传文件 */
return -1;
}
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 1.创建套接字// socket返回的文件描述符 //IPV4 //UDP
if (-1 == sockfd){
perror("socket");
return -1;
}
struct sockaddr_in server_addr; // 2.填充服务器网络信息结构体
memset(&server_addr, 0, sizeof(server_addr)); //清空
server_addr.sin_family = AF_INET; // IPV4
server_addr.sin_port = htons(69); //端口号 填69 将无符号2字节整型 主机-->网络//atoi输入字符串转换为一个整数 atoi(argv[2])
server_addr.sin_addr.s_addr = inet_addr(argv[1]); //windows 的ip地址 将所指的字符串转换成32位的网络字节序二进制值
socklen_t server_addr_len = sizeof(server_addr); //结构体长度
//-----------------------------------------------
char buff[600] = {0}; //数据包--数据的长度以512Byte传输
char _ack[4]; //返回的ACK
unsigned short code = 0;//操作码
unsigned short num = 0; //块编号 或者 错误码
int N = 0; //校验收到的块编号
FILE *fp; //返回的文件描述符
int ret = 0;
char filename[32] = {0};//接收文件名|发送文件名
strcpy(filename,argv[3]);
if(!strcmp(argv[2],"get")){
ret = sprintf(buff, "%c%c%s%c%s%c", 0, RRQ , filename, 0, "octet", 0); //使用 sprintf 组包 //返回值:成功格式化字符的个数
if (-1 == sendto(sockfd, buff, ret, 0, (struct sockaddr *)&server_addr, server_addr_len)){//首次发送请求-----想要发送的数据的字节数
perror("sendto");
return -1;
}
if (NULL == (fp = fopen(filename,"wb"))){
perror("fopen");
return -1;
}
}
else if(!strcmp(argv[2],"put")){
ret = sprintf((char *)buff,"%c%c%s%c%s%c",0,WRQ, filename, 0, "octet", 0); //使用 sprintf 组包//返回值:成功格式化字符的个数
if (-1 == sendto(sockfd, buff, ret, 0, (struct sockaddr *)&server_addr, server_addr_len)){//首次发送请求-----想要发送的数据的字节数
perror("sendto");
return -1;
}
if (NULL == (fp = fopen(filename,"rb"))){
perror("fopen");
return -1;
}
}
//循环接收服务器发来的数据包
while (1)
{
//接收--需要保存服务器的网络信息结构体 因为里面有临时端口
if (-1 == (ret = recvfrom(sockfd, buff, 600, 0, (struct sockaddr *)&server_addr, &server_addr_len))){
perror("recvfrom");
return -1;
}
code = ntohs(*(unsigned short *)buff); //解析块编号 或者 错误码
num = ntohs(*(unsigned short *)(buff + 2)); //解析文件内容 或 错误信息
if (DATA == code && num == N + 1){
N++;//校验块编号+1
if (-1 == fwrite(buff + 4,1, ret - 4,fp)){ //将文件内容写入文件
printf("fwrite error\n");
fclose(fp);
return -1;
}
// 组装ACK
*(unsigned short *)_ack = htons(ACK);
*(unsigned short *)(_ack + 2) = htons(num);
if (-1 == sendto(sockfd, _ack, 4, 0, (struct sockaddr *)&server_addr, server_addr_len)){// 回复ACK包
perror("sendto");
fclose(fp);
return -1;
}
if (ret < 516)//文件接收完毕
break;
}
else if(ACK == code && num == N){
N++;
/*组装数据包*/
*((unsigned short*)buff)=htons(DATA);
*((unsigned short*)(buff+2))=htons(N);
ret = fread(buff+4,1,512,fp);
if(ret != 512){
if(feof(fp)){
printf("end-of-file\n");
}
else{
printf("fread error\n");
fclose(fp);
return -1;
}
}
if (ret ==sendto(sockfd, buff,ret + 4,0,(struct sockaddr *)&server_addr, server_addr_len)){/*发送数据包*/
perror("sendto");
fclose(fp);
return -1;
}
if( ret < 512 ){
printf("ret = %d\n",ret);
break;
}
}
else if (ERROR == code)
{
printf("接收出错[%s]\n", buff); //错误信息
fclose(fp);
return -1;
}
else{
printf("服务器无应答");
fclose(fp);
return -1;
}
}
if(!strcmp(argv[2],"get")){
printf("文件[%s]下载完成\n", filename);
}
else if(!strcmp(argv[2],"put")){
printf("文件[%s]上传完成\n", filename);
}
fclose(fp);
return 0;
}
Linux UDP下C语言实现TFTP协议客户端,文件上传及下载的功能
最新推荐文章于 2023-02-23 23:30:07 发布