通过UDP实现简易FTP
通过UDP协议实现简单的停等式FTP文件传输。
头文件:proto.h
#ifndef __PROTO_H__
#define __PROTO_H__
#define FTPPORT "8888" //通讯端口
#define PATHSIZE 256
#define UDPSIZE 512
enum message_type //定义数据传输类型
{
PATH=1,
DATA,
ACK,
ENDFILE
};
struct message_path_st //结构体指定要下载的文件
{
uint8_t type;
uint8_t path[PATHSIZE];
};
struct message_data_st //传输数据
{
uint8_t type;
uint32_t data_num;
uint32_t lenth;
uint8_t data[UDPSIZE];
};
struct message_eof_st //文件传输结束
{
uint8_t type;
};
struct message_ack_st //数据回复
{
uint8_t type;
uint32_t ack_num;
};
union message //组建的共用体
{
uint8_t type;
struct message_data_st data;
struct message_eof_st endfile;
struct message_ack_st ack;
};
#endif
接收端:rcver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include "proto.h"
#define IPSIZE 40
enum fsm_state //有限状态机状态
{
STATE_R=1,
STATE_W,
STATE_ACK,
STATE_EX,
STATE_T
};
struct fsm_st //有限状态机结构体体
{
enum fsm_state stat;
int fd;
int pos;
int lenth;
union message data;
};
static void fsm_driver(struct fsm_st *fsm,int fd,const struct sockaddr *addr,socklen_t lenth)//有限状态机
{
ssize_t ret;
int i=0;
struct message_ack_st ack;
switch(fsm->stat) //状态判断
{
case STATE_R: //读状态
memset(&fsm->data,0,sizeof(fsm->data));
while((fsm->lenth = read(fsm->fd,fsm->data.data.data,UDPSIZE))<0) //读取数据
{
if(errno == EINTR || errno == EAGAIN) //假错
continue;
perror("read()");
fsm->stat = STATE_EX; //真错改变状态
}
if(fsm->lenth ==0) //文件读取完毕
{
fsm->data.type = ENDFILE;
fsm->stat = STATE_W; //改变状态
}
else //读取到数据
{
fsm->data.type = DATA;
fsm->stat = STATE_W; //改变状态
}
break;
case STATE_W: //写状态
if(fsm->data.type == ENDFILE) //当为文件末尾时
{
ret = sendto(fd,&fsm->data,sizeof(struct message_eof_st),0,addr,lenth); //发送文件结束数据包
fsm->stat = STATE_T; //改变状态机状态
// printf("file:%s line:%d\n",__FILE__,__LINE__);
}
else if(fsm->data.type == DATA) //当为数据包时
{
fsm->data.data.data_num++; //改变数据包编号
fsm->data.data.lenth = fsm->lenth; //设置数据长度
ret = sendto(fd,&fsm->data,sizeof(fsm->data),0,addr,lenth); //传输数据
fsm->stat = STATE_ACK; //改变状态机状态
}
break;
case STATE_ACK: //当为等待ack信号时
while((ret = recvfrom(fd,&ack,sizeof(ack),0,NULL,NULL))<0) //等待接收客户端的ack信号
{
if(errno == 11) //当为超时错误时
{
i++;
if(i<=5) //小于5次继续
continue;
else
{
fsm->stat = STATE_W; //跳转到发送模式发送上次的数据
break; //退出
}
}
perror("recvfrom()");
fsm->stat = STATE_EX; //真错切换状态机状态
}
if(ack.type == ACK && ack.ack_num == fsm->data.data.data_num) //对比包号是否正确
{
printf("file:%s line:%d\n",__FILE__,__LINE__);
fsm->stat = STATE_R; //切换到读状态继续读取文件
}
else
fsm->stat = STATE_W; //否者重新写上次数据
break;
case STATE_EX: //出错时报错退出状态机
printf("fsm error\n");
fsm->stat = STATE_T;
break;
case STATE_T:
break;
default:
abort(); //产生段错误
break;
}
}
static int data_analysis(int fd,const char *filename,const struct sockaddr *addr,socklen_t lenth)
{
int sfd;
struct fsm_st fsm;
sfd = open(filename,O_RDONLY); //打开要读取的文件
if(sfd <0)
{
perror("open()");
return -1;
}
fsm.stat = STATE_R; //初始化状态机
fsm.fd = sfd;
fsm.pos =0;
fsm.lenth =0;
fsm.data.data.data_num =0;
while(fsm.stat != STATE_T) //启动状态机
{
fsm_driver(&fsm,fd,addr,lenth);
}
close(sfd); //关闭文件
}
int main()
{
int ret,recvnum;
int lfd;
socklen_t lenth;
struct sockaddr_in laddr,raddr;
struct message_path_st path;
char ip[IPSIZE];
struct timeval val;
lfd = socket(AF_INET,SOCK_DGRAM,0); //获取socket文件描述符
if(lfd <0)
{
perror("socket()");
exit(1);
}
val.tv_sec = 1;
val.tv_usec = 0; //10ms
ret = setsockopt(lfd,SOL_SOCKET,SO_RCVTIMEO,&val,sizeof(val)); //设置超时时间
if(ret <0)
{
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET; //初始化结构体
laddr.sin_port = htons(atoi(FTPPORT));
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr.s_addr);
ret = bind(lfd,(void *)&laddr,sizeof(laddr)); //绑定端口和IP
if(ret<0)
{
perror("bind");
exit(1);
}
lenth = sizeof(raddr);
while(1)
{
while((recvnum = recvfrom(lfd,&path,sizeof(path),0,(void *)&raddr,&lenth))<0) //接收客户端数据
{
if(errno == 11) //超时判断
continue;
perror("recvfrom()"); //真错
printf("file:%s line:%d\n",__FILE__,__LINE__);
exit(1);
}
inet_ntop(AF_INET,&raddr.sin_addr.s_addr,ip,IPSIZE);
printf("--IP:%s PORT:%d--\n",ip,ntohs(raddr.sin_port)); //输出客户端信息
if(path.type == PATH && path.path!=NULL) //判断是否为请求文件
{
ret = data_analysis(lfd,path.path,(void *)&raddr,lenth); //分析数据执行状态机
if(ret <0)
{
printf("data_analysis error\n");
break;
}
}
}
close(lfd); //关闭socket
exit(0);
}
发送端:sender.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include "proto.h"
int main(int argc, char **argv)
{
int ret;
int lfd;
ssize_t lenth;
struct sockaddr_in raddr;
struct message_path_st path;
struct message_ack_st ack;
union message data;
if(argc <3) //参数合法判断
{
printf("Usage:%s <server ip> <filename>\n",argv[0]);
exit(1);
}
lfd = socket(AF_INET,SOCK_DGRAM,0); //创建socket
if(lfd <0)
{
perror("socket()");
exit(1);
}
path.type = PATH; //创建发送数据包
strncpy(path.path,argv[2],PATHSIZE);
raddr.sin_family = AF_INET; //初始化结构体
raddr.sin_port = htons(atoi(FTPPORT));
inet_pton(AF_INET,argv[1],&raddr.sin_addr.s_addr);
ret = sendto(lfd,&path,sizeof(path),0,(void *)&raddr,sizeof(raddr)); //发送数据到服务器
if(ret <0)
{
perror("sendto()");
exit(1);
}
while(1)
{
memset(&data,0,sizeof(data)); //清空接收缓存
lenth = recvfrom(lfd,&data,sizeof(data),0,NULL,NULL); //接收服务器数据
if(lenth<0)
{
perror("recvfrom()");
exit(1);
}
if(data.type == DATA) //是数据包
{
write(1,data.data.data,data.data.lenth); //输出数据
ack.type = ACK; //创建ack包
ack.ack_num = data.data.data_num;
sendto(lfd,&ack,sizeof(ack),0,(void *)&raddr,sizeof(raddr)); //回复服务器
}
else if(data.type == ENDFILE) //结束标志
break;
else
printf("error not defined\n");
}
close(lfd);
exit(0);
}
编译文件:Makefile
all:rcver sender
rcver:rcver.o
$(CC) $^ -o $@
sender:sender.o
$(CC) $^ -o $@
clean:
$(RM) *.o rcver sender
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现