本代码为采用tftp协议的UDP客户端,实现上传和下载文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(msg) do{\
fprintf(stderr, "line:%d ", __LINE__);\
perror(msg);\
}while(0)
#define SER_PORT 69 //tftp服务器端口号
#define SER_IP "192.168.0.194" //IP地址,本机IP ifconfig
#define CLI_PORT 6666 //本机端口
#define CLI_IP "192.168.0.130"
void print_screen()
{
printf("\n");
printf("/请输入数字以选择相应操作:/\n");
printf("/1.上传/\n");
printf("/2.下载/\n");
printf("/3.退出/\n");
printf("///\n");
}
int upload(int sfd, struct sockaddr_in sin);
int download(int sfd, struct sockaddr_in sin);
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//绑定ip和端口//
//绑定--->非必须绑定
//如果不绑定,操作系统会自动绑定本机IP,从49152~65535范围内随机一个未被使用的端口号
//填充客户端自身的地址信息结构体, AF_INET : man 7 ip
struct sockaddr_in cin;
cin.sin_family = AF_INET;
cin.sin_port = htons(CLI_PORT); //客户端自身的端口号,需要从1024~49151中选择没有被使用过的
cin.sin_addr.s_addr = inet_addr(CLI_IP); //客户端的本机IP
if(bind(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success __%d__\n", __LINE__);
绑定ip和端口
//填充服务器的地址信息结构体
//供给下面的sendto使用,
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(SER_PORT); //服务器绑定的端口,网络字节序
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器绑定的IP,本机IP ifconfig
printf("socket create success sfd=%d __%d__\n", sfd, __LINE__);
while(1)
{
//打印交互页面
print_screen();
//根据交互结果跳转相应程序
char choice = getchar();
//清理多余字符
while('\n' != getchar()){};
//根据交互结果跳转相应程序
switch (choice)
{
//******************************************上传******************************************
case '1'://上传
upload(sfd, sin);
break;
//******************************************下载******************************************
case '2'://下载
download(sfd, sin);
break;
case '3'://退出
return 0;
default:
return 0;
}
return 0;
}
//关闭所有文件描述符
close(sfd);
return 0;
}
int upload(int sfd, struct sockaddr_in sin)
{
char fileName[128];
//定义发送和接收缓冲区
char buf[516] = "";
//定义操作码
uint16_t operaCode;
//recvfrom返回的参数,代表 接收到的 包的大小
int res;
//准备发送的包的大小
int pack_size;
//接收到的包中,存放的对端ip
struct sockaddr_in rcvAddr;
//addrlen=sizeof(rcvAddr)
socklen_t addrlen;
//块编号
uint16_t block_num = 0;
printf("请输入要上传的文件名:");
scanf("%s", fileName);
使用sprintf发送请求包
//准备缓冲区数据
bzero(buf, sizeof(buf));
operaCode = htons(2); //读写请求操作码为2,写(上传)
pack_size = sprintf(buf, "%c%c%s%c%s%c", \
*(char*)(&operaCode), *((char*)(&operaCode)+1), \
fileName, 0, "octet", 0);
//发送写请求数据包
//发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
//想要发送给谁,就填谁的地址信息结构体
if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("写请求包发送成功\n");
使用sprintf发送请求包
addrlen = sizeof(rcvAddr);
//接收数据
//res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
//res = recv(sfd, buf, sizeof(buf), 0);
printf("__%d__\n", __LINE__); //test
//如果接收到ACK包,发送数据包,接收到ERROR包结束程序
operaCode = htons(3);
block_num = 0; //块编号
// uint8_t block_num1; //低字节
// uint8_t block_num2;
//打开文件
int fd_src = open(fileName, O_RDONLY | O_CREAT, 0664);
if(fd_src<0)
{
ERR_MSG("open");
return -1;
}
printf("__%d__\n", __LINE__); //test
//接收包
res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
if(res < 0) //recvfrom函数错误
{
ERR_MSG("recvfrom");
return -1;
}
printf("__%d__\n", __LINE__); //test
//收到的是ACK包
if(*(short*)buf == htons(4))
{
while(1)
{
block_num++; //块编号++
// *(unsigned short*)buf = htons(3);
// *(unsigned short*)(buf+2) = htons(block_num);
printf("[%s : %d] : %u\n", \
inet_ntoa(rcvAddr.sin_addr), ntohs(rcvAddr.sin_port), \
block_num);
//准备数据包的操作码和块编号
pack_size = sprintf(buf, "%c%c%c%c", \
*(char*)(&operaCode), *((char*)(&operaCode)+1), \
*((char*)(&block_num)+1), *(char*)(&block_num) );
//将文件内容读取到buf中的数据区(包括最后一个不足512bytes的数据包)
res = read(fd_src, buf+4, 512);
if( res < 0)
{
ERR_MSG("write");
return -1;
}
//发送数据包
//发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
//想要发送给谁,就填谁的地址信息结构体
if( (pack_size = sendto(sfd, buf, res+4, 0, (struct sockaddr *)&rcvAddr, sizeof(rcvAddr)) ) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("data发送成功\n");
int cnt = 0;
do
{
printf("__%d__\n", __LINE__); //test
// cnt++;
//接收ACK包
res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
if(res < 0) //recvfrom函数错误
{
ERR_MSG("recvfrom");
return -1;
}
printf("__%d__\n", __LINE__); //test
//接收到数据包对应的ACK包,退出循环
if(*((short *)buf +1) == htons(block_num))
{
printf("ACK received\n");
break;
}
printf("__%d__\n", __LINE__); //test
if(cnt > 100)
{
ERR_MSG("Waiting ACK for too long");
cnt = 0;
return -1;
}
}while(*(short*)buf != htons(4)); //当收不到对应ACK包时,循环接收,直到接收到对应ACK包
printf("%d\n", pack_size);
if(pack_size<516)
{
printf("文件上传完毕\n");
close(fd_src);
break;
}
}
}
//收到的是错误包
else if(*(short*)buf == htons(5))
{
ERR_MSG("Error pack");
return -1;
}
}
int download(int sfd, struct sockaddr_in sin)
{
char fileName[128];
//定义发送和接收缓冲区
char buf[516] = "";
//定义操作码
uint16_t operaCode;
//recvfrom返回的参数,代表 接收到的 包的大小
int res;
//准备发送的包的大小
int pack_size;
//接收到的包中,存放的对端ip
struct sockaddr_in rcvAddr;
//addrlen=sizeof(rcvAddr)
socklen_t addrlen;
//块编号
uint16_t block_num = 0;
printf("请输入要下载的文件名:");
scanf("%s", fileName);
使用sprintf发送请求包
//准备缓冲区数据
bzero(buf, sizeof(buf));
operaCode = htons(1); //读写请求操作码为1,读(下载)
pack_size = sprintf(buf, "%c%c%s%c%s%c", \
*(char*)(&operaCode), *((char*)(&operaCode)+1), \
fileName, 0, "octet", 0);
//发送读请求数据包
//发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
//想要发送给谁,就填谁的地址信息结构体
if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("读请求包发送成功\n");
使用sprintf发送请求包
addrlen = sizeof(rcvAddr);
//接收数据
//res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
//res = recv(sfd, buf, sizeof(buf), 0);
//如果接收到数据包,发送ACK包,接收到ERROR包结束程序
operaCode = htons(4);
block_num = 0; //块编号
// uint8_t block_num1; //低字节
// uint8_t block_num2;
//打开文件
int fd_dest = open(fileName, O_WRONLY | O_CREAT, 0664);
if(fd_dest<0)
{
ERR_MSG("open");
return -1;
}
while(1)
{
//接收包
res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
if(res < 0) //recvfrom函数错误
{
ERR_MSG("recvfrom");
return -1;
}
//收到的是数据包
if(*(short*)buf == htons(3))
{
//将数据写入文件(包括最后一个不足512bytes的数据包)
if( write(fd_dest, buf+4, res-4) < 0)
{
ERR_MSG("write");
return -1;
}
//包计数
block_num = *((uint16_t*)(buf+2));
printf("[%s : %d] : %u\n", \
inet_ntoa(rcvAddr.sin_addr), ntohs(rcvAddr.sin_port), \
block_num);
//准备ACK包
pack_size = sprintf(buf, "%c%c%c%c", \
*(char*)(&operaCode), *((char*)(&operaCode)+1), \
(uint8_t)block_num, *(((uint8_t*)&block_num)+1));
//发送ACK包
//发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
//想要发送给谁,就填谁的地址信息结构体
if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&rcvAddr, sizeof(rcvAddr)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("ack发送成功\n");
if(res<516)
{
printf("文件下载完毕\n");
close(fd_dest);
break;
}
}
//收到的是错误包
else if(*(short*)buf == htons(5))
{
ERR_MSG("Error pack");
return -1;
}
}
}