基于UDP传输的客户端从服务器下载与上传
代码:
服务器使用Tftp软件代替,客户端由自己编写;
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define ERR_MSG(msg) {\
fprintf(stderr,"line:%d\n",__LINE__);\
perror(msg);\
}while(0);
#define IP "192.168.8.208"
#define PORT 69
int download(int sfd,struct sockaddr_in sin)
{
//发送下载请求
char filename[30] = "";
printf("请输入下载文件>>");
fgets(filename,sizeof(filename),stdin);
filename[strlen(filename)-1] = 0;
char app[50] = "";
//定义下载请求
int size = sprintf(app,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
//发送
if(sendto(sfd,app,size,0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto application success\n");
char rcv[516] = "";//存放接收的包
ssize_t res = 0;//接收函数的返回值
int flag = 0;//文件创建/打开的标志位
socklen_t size_sin = sizeof(sin);//存放发送方地址信息结构体大小
unsigned short num = 0;//存放上一次的块编码,防止重复写入
int fd = -1;//文件描述符,防止错关描述符置-1
while(1)
{
bzero(rcv,sizeof(rcv));//防止最后一次写入带有倒数第二次的部分内容
//接收数据包
res = recvfrom(sfd,rcv,sizeof(rcv),0,(struct sockaddr*)&sin,&size_sin);
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
if(5 == rcv[1])//接收的是错误包
{
printf("line:[%d] %d : %s\n",__LINE__,ntohs(*(short *)(rcv+2)),rcv+4);//打印错误信息
return -1;
}
else if(3 == rcv[1])//接收到的是数据包
{
if(0 == flag)//打开文件描述符,循环内打开为了防止创建服务器内不存在文件的接收文件
{
fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd < 0)
{
ERR_MSG("open");
return -1;
}
flag = 1;//标志位置1,本次下载不再打开新的文件描述符
}
if(num != ntohs(*(unsigned short *)(rcv+2)))//如果接收的数据包块编码与上次接收的相同,说明数据包重复发送
{
if(write(fd,rcv+4,res-4) < 0)//将读取的内容写入文件中
{
ERR_MSG("write");
return -1;
}
if(res < 516)//诺数据包没有读满说明本次是最后一次接收内容
{
printf("下载完成\n");
break;
}
}
num = ntohs(*(unsigned short *)(rcv+2));//记录本次的块编码
//发送ACK
rcv[1] = 4;//将数据包块编码打包发送回去
if(sendto(sfd,rcv,4,0,(struct sockaddr*)&sin,size_sin) < 0)
{
ERR_MSG("sendto");
return -1;
}
}
}
close(fd);
return 0;
}
int upload(int sfd,struct sockaddr_in sin)
{
char filename[30] = "";
printf("请输入上传文件>>");
fgets(filename,sizeof(filename),stdin);
filename[strlen(filename)-1] = 0;
int fd = open(filename,O_RDONLY);
if(fd < 0)
{
ERR_MSG("open");
return -1;
}
//发送请求
char app[30] = "";
int size = sprintf(app,"%c%c%s%c%s%c",0,2,filename,0,"octet",0);
if(sendto(sfd,app,size,0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
char snd[516] = "";
char buf[516] = "";
ssize_t res = 0;
socklen_t size_sin = sizeof(sin);
unsigned short num = 0;
while(1)
{
bzero(snd,sizeof(snd));
//接收ack
if(recvfrom(sfd,snd,4,0,(struct sockaddr*)&sin,&size_sin) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
//发送数据包
res = read(fd,snd+4,sizeof(snd)-4);
if(res < 0)
{
ERR_MSG("read");
return -1;
}
num =ntohs(*(unsigned short*)(snd+2));
num++;
snd[1] = 3;
*(unsigned short *)(snd+2) = htons(num);
if(sendto(sfd,snd,sizeof(snd),0,(struct sockaddr*)&sin,size_sin) < 0)
{
ERR_MSG("sendto");
return -1;
}
//当读取内容少于512字节,说明最后一次读取,上传完成
if(res < 512)
{
printf("上传完成\n");
break;
}
}
close(fd);
return 0;
}
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//完善通用地址信息结构体,用于向服务器提出申请
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
int choose = 0;
while(1)
{
system("clear");
printf("***********************\n");
printf("*******1.download******\n");
printf("*******2.upload********\n");
printf("*******3.exit**********\n");
printf("***********************\n");
printf("请输入>>");
scanf("%d",&choose);
while(getchar()!=10);
switch(choose)
{
case 1:
download(sfd,sin);
break;
case 2:
upload(sfd,sin);
break;
case 3:
close(sfd);
return 0;
printf("按任意字符清屏>>");
while(getchar()!=10);
}
}
return 0;
}
实现效果:
下载:
上传:
你爱喝鸡蛋汤嘛: 读了好多博客,还是博主表述的通俗易懂
原来的潘潘呀: 考虑的很全面,非常好。但是项目中有用到链表L这个全局变量,在登录或者下线的时候会添加、删除结点,包括发送系统信息的时候又需要遍历这个结点。所以L是一个临界资源。在操作临界资源的时候需要上锁,否则可能会出现我在遍历链表的时候,某一个结点被删除,或者新增的情况
CSDN-Ada助手: 推荐 算法 技能树:https://edu.csdn.net/skill/algorithm?utm_source=AI_act_algorithm