TFTP 简介、通信过程

    TFTP 概述

          TFTP :  简单文件传送协议

               最初用于引导无盘系统,用来传输小文件

       特点:

            基于UDP协议实现

            不进行用户有效性认证

      数据传输模式:

         octet:二进制模式

         netascii:文本模式

         mail:已经不再支持


   TFTP 通信过程

     TFTP 通信过程总结

  •          服务器在69端口等待客户端的请求
  •         服务器同意,就使用临时端口与客户端通信
  •        每个数据包的编号都会变化(从1开始)
  •        每个数据包都要ACK的确认,如果超时,就会重新发送最后的包
  •       数据的长度以512byte传输
  •        小于512byte的数据意味着传输结束


TFTP 协议分析

注意:

    以上的 0 代表的是'\0'
    不同的差错码对应不同的错误信息

 

错误码:

  •         0         Undefined                                      未定义                      
  •         1         File  not  found                               找不到文件
  •         2         access  violation                             访问冲突
  •         3         disk  full or  allocation exceeded     磁盘已满或超过分配
  •        4          illegal  TFTP operation                     非法的TFTP操作
  •         5         unknown  transfer  ID                        未知的传输ID
  •         6        file  already  exists                           文件已经存在
  •         7        no  such   user                                   没有这样的用户
  •         8        unsupported  option  requested      不支持的选项请求

  

TFTP 带选项

     读写请求中修改了选项
 

      如果发送带选项的读写请求

     tsize 选项

         当读操作,tsize 选项参数必须为 0 ,服务器会返回待读取的文件大小

         当写操作,tsize 选项参数为待写入的文件大小,服务器会显示该参数

     blksize 选项

          修改传输文件时使用的数据块大小(范围:8 ~ 65464)

     timeout   选项

          修改默认的数据传输超时时间(单位:秒)

 

     TFTP通信过程总结(带选项)

  •           可以通过发送 带选项的读/写请求 发送给server
  •          如果server同意修改选项,就发送选项修改确认包
  •         server发送的数据,选项修改确认包都是临时port
  •         server 通过timeout来对丢失数据包的重新发送


练习—TFTP 客户端

    要求:

       使用TFTP协议,下载server上的文件到本地

   实现思路:

  •       构造请求报文,送到服务器(69端口)
  •      等待服务器回应
  •      分析服务器回应
  •     接受数据,直到接收到的数据包小于规定数据包大小

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

static int sockfd;

void sig_dispose(int sig)
{
    if(SIGINT == sig)
    {
        close(sockfd);
        puts("\n close!");
        exit(0);
    }
}

/*
 *function:
 *  tftp客户端程序,从tftp服务器下载程序
 */
int main(int argc, char *argv[])
{
    unsigned short p_num = 0;
    unsigned char cmd = 0;
    char cmd_buf[512] = "";
    char recv_buf[516] = "";
    struct sockaddr_in client_addr;
    socklen_t cliaddr_len = sizeof(client_addr);
    struct sockaddr_in dest_addr;
    int len;

    signal(SIGINT, sig_dispose);

    if(argc < 3)
    {
        /* 参数个数小于3,认为输入命令错误 */
        printf("cmd example: ./tftpc 192.168.x.x hello.text \n");
       return 0;
    }

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

    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(69);
    inet_pton(AF_INET, argv[1], &dest_addr.sin_addr);
    /* 构造下载请求, argv[2]为文件名 */
    len = sprintf(cmd_buf, 
                  "%c%c%s%c%s%c", 
                  0, 
                  1, 
                  argv[2], 
                  0, 
                  "octet", 
                  0);
    /* 发送读数据包请求 */
    sendto(sockfd,
           cmd_buf, 
           len, 
           0, 
           (struct sockaddr *)&dest_addr, 
           sizeof(dest_addr));
    int fd = open(argv[2], O_WRONLY | O_CREAT, 0666);
    if(fd < 0)
    {
        perror("open error");
        close(sockfd);
        exit(-1);
    }

    do
    {
        len =recvfrom(sockfd, 
                      recv_buf, 
                      sizeof(recv_buf),
                      0,
                      (struct sockaddr *)&client_addr,
                      &cliaddr_len);
        cmd = recv_buf[1];
        /* 是否为数据包 */
        if(cmd == 3)
        {
            //接收的包编号是否为上次包的编号+1
            if((unsigned short)(p_num + 1) == ntohs(*(unsigned short *)(recv_buf + 2)))
            {
                write(fd, recv_buf + 4, len -4);
                p_num = ntohs(*(unsigned short *)(recv_buf + 2));
                printf("recv:%d\n",p_num);
            }
            recv_buf[1] = 4;
            sendto(sockfd, 
                   recv_buf, 
                    4, 
                    0, 
                    (struct sockaddr *)&client_addr, 
                    sizeof(client_addr));
        }
        else if(cmd == 5)
        {
            close(sockfd);
            close(fd);
            unlink(argv[2]);        //删除文件
            printf("error:%s \n",recv_buf + 4);
            exit(-1);
        }
    }while(len == 516);
    //如果收到的数据小于516,则出错
    close(fd);
    return 0;
}
#include <stdio.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

static int sockfd;
static struct sockaddr_in client_addr;

static void sig_dispose(int sig)
{
    if(SIGINT == sig)
    {
        close(sockfd);
        puts("\n nclose");
        exit(0);
    }
}

int main(int argc, char *argv[])
{
    struct sockaddr_in dest_addr;
    unsigned char send_buf[1024];
    int len;
    FILE *fp;
    char recv_buf[2048];
	char client_ip[INET_ADDRSTRLEN];
    unsigned short pack_num;
    socklen_t client_addr_len = sizeof(client_addr);

    signal(SIGINT, sig_dispose);
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        exit(-1);
    }
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(8080);
    inet_pton(AF_INET,"192.168.43.76", &dest_addr.sin_addr.s_addr);

    bzero(send_buf, sizeof(send_buf));
    len = sprintf(send_buf,"%c%c%s%c%s%c%s%c%s%c%s%c%d%c",0,1,"01.jpg",0,
                                                              "octet",0,
                                                              "tsize",0,
                                                              "0",0,
                                                              "blksize",0,600,0);
    sendto(sockfd, send_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    len = recvfrom(sockfd,
                   recv_buf, 
                   sizeof(recv_buf), 
                   0, 
                   (struct sockaddr *)&client_addr,
                   &client_addr_len);
    if(recv_buf[1] != 6)
    {
        perror("fopen:");
        close(sockfd);
        return 0;
    }
    len = sprintf(send_buf, "%c%c%c%c", 0, 4, 0, 0);
    sendto(sockfd, send_buf, len, 0, (struct sockaddr *)&client_addr,sizeof(client_addr));
    
    fp = fopen("01.jpg","wb");
    if(fp == NULL)
    {
        perror("fopen");
        close(sockfd);
        return 0;
    }
    printf("IP:%s port:%d: \n",inet_ntop(AF_INET, 
                                         &client_addr.sin_addr,
                                         client_ip,
                                         INET_ADDRSTRLEN),
                                         ntohs(client_addr.sin_port));
    len = 604;
    while(len == 604)
    {
        len = recvfrom(sockfd, 
                       recv_buf, 
                       sizeof(recv_buf), 
                       0, 
                       (struct sockaddr *)&client_addr,
                       &client_addr_len);
        pack_num = *(unsigned short *)(recv_buf + 2);
        printf("pack_num = %d\n",ntohs(pack_num));
        if(recv_buf[1] == 3)
        {
            send_buf[0] = 0;
            send_buf[1] = 4;
            send_buf[2] = recv_buf[2];
            send_buf[3] = recv_buf[3];
            sendto(sockfd, 
                   send_buf,
                   4,
                   0,
                   (struct sockaddr *)&client_addr, 
                   sizeof(client_addr));
            fwrite(recv_buf + 4, 1, len -4, fp);
        }
        else if(recv_buf[1] == 5)
        {
            puts(recv_buf + 4);
            break;
        }
    }
    
    close(sockfd);
    fclose(fp);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <termios.h>

#define GREEN "\e[32m"         //打印显示绿色
#define RED   "\e[31m"         //打印显示红色
#define PRINT(X,Y) { write(1,Y,5);\
                     printf(X);\
                     fflush(stdout);\
                     write(1, "\e[0m", 4);\
                   }

static int sockfd;
static struct sockaddr_in dest_addr;

void sig_dispose(int sig);
void tftp_down(char *argv);
void tftp_upload(char *argv);
void help_fun(int argc, char *argv[]);
char mygetch();

int main(int argc, char *argv[])
{
    char cmd_line[100];
    signal(SIGINT, sig_dispose);

    bzero(&dest_addr, sizeof(dest_addr));
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(-1);
    }
    while(1)
    {
        int cmd;
        help_fun(argc, argv);
        PRINT("send>", GREEN);
        cmd = mygetch();
        if(cmd == '1')
        {
            puts("input file name:");
            fgets(cmd_line, sizeof(cmd_line),stdin);
            *(strchr(cmd_line,'\n')) = '\0';
            tftp_down(cmd_line);
        }
        else if(cmd == '2')
        {
            puts("input file name:");
            fgets(cmd_line, sizeof(cmd_line), stdin);
            *(strchr(cmd_line, '\n')) = '\0';
            tftp_upload(cmd_line);
        }
        else if(cmd == '3')
        {
            close(sockfd);
            system("stty sane");    //回显
            exit(0);
        }
    }

    close(sockfd);
    return 0;
}

void sig_dispose(int sig)
{
    if(SIGINT == sig)
    {
        close(sockfd);
        puts("\nclose");
        system("stty sane");    //回显
        exit(0);
    }
}

void tftp_upload(char *argv)
{
    int fd, read_len;
    unsigned short p_num = 0;
    unsigned char cmd = 0;
    char cmd_buf[512] = "";
    char recv_buf[516] = "";
    struct sockaddr_in client_addr;
    int len;
    socklen_t cliaddr_len = sizeof(client_addr);

    if(dest_addr.sin_port == 0)
    {
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(69);
        puts("send to IP:");
        fgets(recv_buf, sizeof(recv_buf),stdin);
        *(strchr(recv_buf, '\n')) = '\0';
        inet_pton(AF_INET, recv_buf, &dest_addr.sin_addr);
    }
    /* 构造下载请求,argv为文件名 
     * 发送读数据包请求
     */
    len = sprintf(cmd_buf, "%c%c%s%c%s%c", 0, 2, argv, 0, "octet", 0);
    sendto(sockfd, cmd_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

    fd = open(argv, O_RDONLY);
    if(fd < 0)
    {
        perror("open error");
        close(sockfd);
        exit(-1);
    }

    do
    {
        len = recvfrom(sockfd, 
                       recv_buf, 
                       sizeof(recv_buf),
                       0,
                       (struct sockaddr *)&client_addr,
                       &cliaddr_len);
        cmd = recv_buf[1];
        if(cmd == 4)    //是否为ACK
        {
            p_num = ntohs(*(unsigned short *)(recv_buf + 2));
            read_len = read(fd, recv_buf + 4, 512);
            printf("recv:%d\n",p_num);
            //十进制方式打印包消息
            recv_buf[1] = 3;    //构造数据包
            (*(unsigned short *)(recv_buf + 2)) = htons(p_num + 1);
            printf("%s\n", recv_buf+3);
            sendto(sockfd, 
                   recv_buf, 
                   read_len + 4, 
                   0, 
                   (struct sockaddr *)&client_addr, 
                   sizeof(client_addr));
        }
        else if(cmd == 5)   //是否为错误应答
        {
            close(sockfd);
            close(fd);
            printf("error:%s\n",recv_buf + 4);
            exit(-1);
        }

    }while(read_len == 512);    //读取数据小于512认为结束
    len = recvfrom(sockfd,
                   recv_buf,
                   sizeof(recv_buf),
                   0,
                   (struct sockaddr *)&client_addr,
                   &cliaddr_len);
    //接收最后一个ACK确认包
    close(fd);
    PRINT("Download file is successful \n", RED);
}

void tftp_down(char *argv)
{
    int fd;
    unsigned short p_num = 0;
    unsigned char cmd = 0;
    char cmd_buf[512] = "";
    char recv_buf[516] = "";
    struct sockaddr_in client_addr;
    socklen_t cliaddr_len = sizeof(client_addr);
    int len;

    if(dest_addr.sin_port == 0)
    {
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(69);
        puts("send to IP:");
        fgets(recv_buf, sizeof(recv_buf),stdin);
        *(strchr(recv_buf, '\n')) = '\0';
        inet_pton(AF_INET, recv_buf, &dest_addr.sin_addr);
    }
    //构造下载请求,argv为文件名
	len = sprintf(cmd_buf, "%c%c%s%c%s%c", 0, 1, argv, 0, "octet", 0);	
	//发送读数据包请求
	sendto(sockfd, cmd_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    fd = open(argv, O_WRONLY | O_CREAT, 0666);
    if(fd < 0)
    {
        perror("open error");
        close(sockfd);
        exit(-1);
    }

    do
    {
        //接收服务器发送的内容
        len = recvfrom(sockfd, 
                       recv_buf, 
                       sizeof(recv_buf), 
                       0, 
                       (struct sockaddr *)&client_addr, 
                       &cliaddr_len);
        cmd = recv_buf[1];
        if(cmd == 3)    //是否为数据包
        {
            //接收的包编号是否为上次包编号+1
			if((unsigned short)(p_num + 1) == ntohs(*(unsigned short *)(recv_buf + 2)))
            {
                write(fd, recv_buf + 4, len - 4);
                p_num = ntohs(*(unsigned short *)(recv_buf + 2));
                //十进制方式打印包编号
                printf("recv:%d\n",p_num);
            }
            recv_buf[1] = 4;
            sendto(sockfd, 
                   recv_buf, 
                   4, 
                   0, 
                   (struct sockaddr *)&client_addr, 
                   sizeof(client_addr));
        }
        else if(cmd == 5)   //是否错误应答
        {
            close(sockfd);
            close(fd);
            unlink(argv);   //删除文件
            printf("error:%s\n",recv_buf + 4);
            exit(-1);
        }
    }while((len == 516) || (cmd == 6));
    //如果收到的数据小于516则认为出错
    close(fd);
    PRINT("download file is successful\n",RED);
}

char mygetch()
{
    struct termios oldt, newt;
    char ch;
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    ch = getchar();
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    return ch;
}

void help_fun(int argc, char *argv[])
{
	printf("1  down\n");
	printf("2  upload\n");
	printf("3  exit\n");
	return;
}

UDP 广播

     广播:由一台主机向该主机所在子网内所有主机发送数据方式

      广播只能用于UDP或原始IP实现,不能用TCP

  广播的用途

        单个服务器与多个客户主机通信时减少分组流通

       以下协议用到广播

  •           地址解析协议(ARP)
  •         动态主机配置协议(DHCP)
  •        网络时间协议(NTP)

   UDP广播的特点

  •       处在同一子网的所有主机都必须处理数据
  •       UDP数据包会沿协议栈上一直到UPD层
  •      运行音视频等高速率工作的应用,会带来大负
  •     局限于局域网内使用

   UDP广播地址

         {网络ID,主机ID}

            网络ID 表示 由子网掩码中 1 覆盖的连续位

            主机ID 表示 由子网掩码中 0 覆盖的连续位

     定向广播地址:主机ID全 1

  •            192.168.220.0/24,其定向广播地址为 192.168.220.255
  •           通常路由器不转发该广播

    受限广播地址:255.255.255.255

          路由器从不转发该广播

广播与单播的对比
  单播

    广播

    套接口选项

/*
 *成功:  执行 0
 *失败: -1
*/
int setsockopt(int sockfd, int level, int optname, const viod *optval, socklen_t optlen);

 

 

广播示例
 

int main(int argc, char *argc[])
{
    int sock_fd = 0;
    char buff[1024] = "";
    unsigned short port = 8000;
    struct sockaddr_in send_addr;
    
    bzero(&send_addr, sizeof(send_addr));
    send_addr.sin_family = AF_INET;
    send_addr.sin_port = htons(port);
    
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd < 0)
    {
        perror("socket failed");
        close(sock_fd);
        exit(1);
    }
    if(argc > 1)
    {
        send_addr.sin_addr.s_addr = inet_addr(argv[1]);
    }
    else
    {
        printf("not have a server IP");
        exit(1);
    }
    
    int yes = 1;
    setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
    strcpy(buf, "boardcast sucess");
    
    int len = sendto(sock_fd, 
                     buff, 
                     strlen(buff),
                     0, 
                     (struct sockaddr *)&send_addr,
                     sizeof(send_addr));
    if(len < 0)
    {
        printf("send error \n");
        close(sock_fd);
        exit(1);
    }
    
    return 0;
}

 


UDP 多播
 

  多播概述

       多播:

             数据的收发仅仅在同一个分组中进行

     多播的特点:

  •        多播地址标识一组接口
  •        多播可以用于广域网使用
  •       在ipv4中,多播是可选的

 

多播地址
     IPv4 的 D 类地址是多播地址

    十进制:224.0.0.1 239.255.255.254

    十六进制:E0.00.00.01 EF.FF.FF.FE

   多播地址向以太网 MAC 地址的映射
 

UDP 多播工作过程
 

  多播地址结构体
      在 IPv4 因特网域(AF_INET)中,多播地址结构体用如下结构体 ip_mreq 表示

struct in_addr
{
    in_addr_t s_addr;
};
struct ip_mreq
{
    struct in_addr imr_multiaddr;       //多播组IP
    struct in_addr imr_interface;       //将要添加到多播组的IP
};

多播套接口选项
 

/*
 *成功: 执行 0
 *失败: -1
 */
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

 

加入多播组示例
 

char group[INET_ADDRSTRLEN] = "244.0.1.1";

struct ip_mreq mreq;                            //定义一个多播组地址

mreq.imr_multiaddr.s_addr = inet_addr(group);    //添加一个多播组IP
mreq.imr_interface.s_addr = htonl(INADDR_ANY);    //添加一个将要添加到多播组的IP

setsockopt(sockfd, IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));