通过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

posted @ 2022-08-12 08:49  *^VV^*  阅读(191)  评论(0编辑  收藏  举报