简单的TFTP客户端

   上一篇博文已经详细介绍tftp协议了,下边联系写一个简单的tftp客户端,首先tftp客户端与服务端通过udp通信,udp不是可靠的通信服务,为了保持通信中数据的可靠传输,我们采用简单的确认机制,服务端对每一个数据报文从01开始编号【0号报文为系统保留】,客户端收到数据报文后,发送确认报文,确认报文应该包含服务器发送报文的编号,这样,当服务端收到确认报文并检查确认数据报文编号,如果是以前发送的最新报文则发送下一个报文,这里需要注意的是,如果服务端在一定时间内没有收到确认报文,则会发生超时,具体超时处理看具体的服务端,一般的默认处理是重新发送报文,当超时次数达到一定的数字,则退出,客户端处理是如果没有收到数据报文,则发送最新收到数据报文的确认报文,如果在一定时间没有收到,则发生超时,通常的超时处理是重新发送最新收到数据报文的确认报文,如果超时次数超过一定的数字,则退出。在tftp通信过程,可以随时的中断通信。这就是tftp通信的整个过程。下边是自己写的一个简单的客户端,没有加入超时机制,只是简单的数据接受。

  • main.c  main.h    
  • socket_init.c  socket_init.h //建立网络连接,
  • file_save.c file_save.c  //文件传输

    测试命令格式:  cmd 192.168.220.63 xxx.xxx


MAIN.H

#ifndef __MAIN_H__
#define __MAIN_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#endif
#include "main.h"
#include "socket_init.h"
#include "file_save.h"
// tftp ip file (space)


MAIN.C
                   //命令格式:  cmd  ip  filename 

int main(int argc,char *argv[])//argv[1]:ip地址 argv[2]:传输的文件名
{    
    int fd;
    fd=socket_init(argc,argv,69);//初始化网络,建立网络连接,返回socket_fd 
  //默认的连接端口为69

    file_save(argc,argv,69,fd);//进行UDP通信,保存文件
    return 1;
    
}    

网络初始化:

 

socket_init.h

 

#ifndef __SOCKET__
#define __SOCKET__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
extern int socket_init(int argc,char *argv[],unsigned short int port);
#endif


socket_init.c


#include "socket_init.h"
int socket_init(int argc,char *argv[],unsigned short int port)
{    
    int sock_fd;
    char request[512];//请求报文
    int len;
    char rec_buf[512];//接受缓存
    unsigned long ip;
    struct sockaddr_in tftp_addr;
    bzero(&tftp_addr,sizeof(struct sockaddr_in));
    bzero(rec_buf,512);
    bzero(request,512);
    //inet_pton(AF_INET,argv[1],&tftp_addr.sin_addr.s_addr);

    inet_pton(AF_INET,argv[1],&ip);//字符串ip转为二进制ip存入ip变量
    printf("ip=%d\n",ip);
    tftp_addr.sin_addr.s_addr=htonl(ip);//大端转换
    tftp_addr.sin_family=AF_INET;//ipv4
    tftp_addr.sin_port=htons(port);
    tftp_addr.sin_addr.s_addr=htonl(tftp_addr.sin_addr.s_addr); // ip

    sprintf(request," %s %s",argv[2],"octet");//字符串格式输出到request
    request[0]=0;
    request[1]=1;
    request[2+strlen(argv[2])]=0;
    request[2+strlen(argv[2])+5+1]=0;//请求报文
    puts(request);
    if( (sock_fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
    {
        perror("socket error");
        exit(-1);
    }    
    len=sizeof(struct sockaddr_in);
    sendto(sock_fd,request,2+5+2+strlen(argv[2]),0,(struct sockaddr *)&tftp_addr,len);//2+5+2+strlen(argv[2]):没有好的计算大小的方法,因为在request中存在多个'\0',不能使用strlen(),而使用sizeof(),输入宏操作,只能返回request数组的大小,同样不能知道实际的大小,通过对request报文格式的观察,发现操作码占2bytes,模式占5bytes,'\0'有两个,占2个byets,在加入文件名大小strlen(argv[2])就可以获取request报文的实际长度了。
    puts("udp init ok ");
    return sock_fd; //返回网络描述符,实际代表一个网络连接
}    

文件传输保存:

 

 

file_save.h

 

#ifndef __FILE_H__
#define __FILE_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>

extern int file_save(int argc,char *argv[],unsigned short int port,int fd);
#endif

fille.c


#include "file_save.h"
int file_save(int argc,char *argv[],unsigned short int port,int socket_fd)
{    
    int fd;
    char *p;
    char rec_buf[550]; //接受数据报文缓存
    int len;
    char ack_buf[4];//应答报文
    unsigned long ip;
    int len_addr;
    struct sockaddr_in tftp_addr,rev_addr;//本地客户端信息
    struct sockaddr tmp_addr;//存储服务端信息
    bzero(&tftp_addr,sizeof(struct sockaddr_in));
    bzero(rec_buf,550);
    bzero(ack_buf,4);
    inet_pton(AF_INET,argv[1],&ip);
    tftp_addr.sin_family=AF_INET;
    tftp_addr.sin_port=htons(port);
    tftp_addr.sin_addr.s_addr=htonl(ip); // ip

    if( (fd=open(argv[2],O_CREAT|O_RDWR))<0 )
    {
        perror("file open error");
        exit(1);
    }    
    bzero(&rev_addr,sizeof(struct sockaddr_in));
    len_addr=sizeof(struct sockaddr_in);
    //while( ( len=recvfrom(socket_fd,rec_buf,512,0,(struct sockaddr *)&rev_addr,&len_addr) )==512 )

    while( ( len=recvfrom(socket_fd,rec_buf,516,0,&tmp_addr,&len_addr) )==516 )
    {    
        write(fd,&rec_buf[4],512);//写入文件,除去报文额外信息
        ack_buf[0]=0;
        ack_buf[1]=4;//应答报文操作码
        ack_buf[2]=rec_buf[2];
        ack_buf[3]=rec_buf[3];//获得报文编号  
        bzero(rec_buf,550);
      //sendto(socket_fd,ack_buf,4,0,(struct sockaddr *)&tftp_addr,len_addr);
        sendto(socket_fd,ack_buf,4,0,&tmp_addr,len_addr);//发送应答报文
    }
    write(fd,&rec_buf[4],len-4);//写入文件,除去报文额外信息。
    ack_buf[0]=0;
    ack_buf[1]=4;//应答报文操作码
    ack_buf[2]=rec_buf[2];
    ack_buf[3]=rec_buf[3];//获取报文编号
  //    sendto(socket_fd,ack_buf,4,0,(struct sockaddr *)&tftp_addr,len_addr);
    sendto(socket_fd,ack_buf,4,0,&tmp_addr,len_addr);//发送应答报文
    close(fd);
    return 0;
}    


上边是一个简单的tftp的客户端,没有加入超时机制,上边的程序都是调试通过的,没有问题的,下边总结下在编写过程中,犯的一些错误,总结下,先看代码吧:

错误一:

有逻辑错误的代码:

if( sock_fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)<0)
    {
        perror("socket error");
        exit(-1);
    }  
  
没有错误的代码:

if( (sock_fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
    {
        perror("socket error");
        exit(-1);
    }    


sock_fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)<0 这个逻辑错误比较隐蔽, '=' 的优先级要小与'<'的优先级,那么执行的结构就是socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)<0这个逻辑式比较的结构存入到sock_fd,而不是把sock()的结构存入到socket_fd中,在求socket_fd<0逻辑式的结构。这个错误的由来是对编写代码不够仔细,对与这个错误还是比较熟悉,往往在写代码时遗漏(),这样加入()后就正常,同时这种类型的逻辑错误编译器是不会报错的,也就是编译是没有问题的,往往是程序运行的结构不对。需要特别的注意逻辑错误。

错误二:

struct sockaddr_in tftp_addr,rev_addr;
struct sockaddr tmp_addr;

while( ( len=recvfromsocket_fd,rec_buf,516,0,&tmp_addr,&len_addr) )==512 )
    {    
        write(fd,&rec_buf[4],512);//写文件
        ack_buf.... //处理应答报文,代码省略
        tftp_addr.sin_port=htons(rev_addr.sin_port);
       //sendto(socket_fd,ack_buf,4,0,(struct sockaddr *)&tftp_addr,len_addr);
        sendto(socket_fd,ack_buf,4,0,&tmp_addr,len_addr);
    }


recvfromsocket_fd,rec_buf,512,0,&tmp_addr,&len_addr)这条语句会将服务端的信息存入到tmp_addr中,这里的tmp_addr是struct sockaddr 类型的,而struct sockaddr_in类型和struct sockaddr是同大小的,可以之间进行安全的赋值转换,如果tmp_addr是struct sockaddr_in类型的,在recvfrom中需要类型转换(struct sockaddr *),如果不进行格式的转换,编译器换警告,在函数内部会进行自动的类型转换,在发送的时候在使用这个tmp_addr时,tmp_addr内部的转矩其实已经经过两次的类型转换,先从struct sockaddr_in转换为struct sockaddr,在从struct sockaddr 转换为struct sockaddr_in,内部的数据就会发生移位,实际的服务端返回的端口号。 比较安全的方法是使用sruct sockaddr类型,不进行类型的转换。
相关文章
相关标签/搜索