网络编程_Day03支持上传和下载文件的UDP客户端-tftp协议

本代码为采用tftp协议的UDP客户端,实现上传和下载文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include


#define ERR_MSG(msg) do{\
    fprintf(stderr, "line:%d ", __LINE__);\
    perror(msg);\
}while(0)
                
                                                                                                                                                  
#define SER_PORT 69               //tftp服务器端口号
#define SER_IP  "192.168.0.194"    //IP地址,本机IP ifconfig

#define CLI_PORT 6666               //本机端口
#define CLI_IP  "192.168.0.130"

void print_screen()
{
    printf("\n");
    printf("/请输入数字以选择相应操作:/\n");
    printf("/1.上传/\n");
    printf("/2.下载/\n");
    printf("/3.退出/\n");
    printf("///\n");
}
int upload(int sfd, struct sockaddr_in sin);
int download(int sfd, struct sockaddr_in sin);
int main(int argc, const char *argv[])
{
    //创建报式套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }

//绑定ip和端口//
    //绑定--->非必须绑定
    //如果不绑定,操作系统会自动绑定本机IP,从49152~65535范围内随机一个未被使用的端口号
    //填充客户端自身的地址信息结构体, AF_INET : man 7 ip
    struct sockaddr_in cin;
    cin.sin_family      = AF_INET;
    cin.sin_port        = htons(CLI_PORT);  //客户端自身的端口号,需要从1024~49151中选择没有被使用过的
    cin.sin_addr.s_addr = inet_addr(CLI_IP);    //客户端的本机IP

    if(bind(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success __%d__\n", __LINE__);
绑定ip和端口


    //填充服务器的地址信息结构体
    //供给下面的sendto使用,
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;  //必须填AF_INET;
    sin.sin_port        = htons(SER_PORT);  //服务器绑定的端口,网络字节序
    sin.sin_addr.s_addr = inet_addr(SER_IP);    //服务器绑定的IP,本机IP ifconfig



    printf("socket create success  sfd=%d __%d__\n", sfd, __LINE__);
    while(1)
    {
        //打印交互页面
        print_screen();
        //根据交互结果跳转相应程序
        char choice = getchar();
        //清理多余字符
        while('\n' != getchar()){};
        //根据交互结果跳转相应程序
        switch (choice)
        {
//******************************************上传******************************************
        case '1'://上传
            upload(sfd, sin);
            break;
//******************************************下载******************************************
        case '2'://下载
            download(sfd, sin);
            break;
        case '3'://退出
            return 0;
        default:
            return 0;
        }
        return 0;
        
    }
    //关闭所有文件描述符
    close(sfd);
    return 0;
}

                                                                                                                                                  
int upload(int sfd, struct sockaddr_in sin)
{
    char fileName[128];
    //定义发送和接收缓冲区
    char buf[516] = "";
    //定义操作码
    uint16_t operaCode;
    //recvfrom返回的参数,代表 接收到的 包的大小
    int res;
    //准备发送的包的大小
    int pack_size;
    //接收到的包中,存放的对端ip
    struct sockaddr_in rcvAddr;
    //addrlen=sizeof(rcvAddr)
    socklen_t addrlen;
    //块编号
    uint16_t block_num = 0;

    printf("请输入要上传的文件名:");
    scanf("%s", fileName);
使用sprintf发送请求包
    //准备缓冲区数据
    bzero(buf, sizeof(buf));
    operaCode = htons(2); //读写请求操作码为2,写(上传)
    pack_size = sprintf(buf, "%c%c%s%c%s%c", \
        *(char*)(&operaCode), *((char*)(&operaCode)+1), \
        fileName, 0, "octet", 0); 
    //发送写请求数据包
    //发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
    //想要发送给谁,就填谁的地址信息结构体
    if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("sendto");
        return -1;
    }
    printf("写请求包发送成功\n");
使用sprintf发送请求包
    addrlen = sizeof(rcvAddr);
    //接收数据
    //res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
    //res = recv(sfd, buf, sizeof(buf), 0);
    printf("__%d__\n", __LINE__); //test
        
    //如果接收到ACK包,发送数据包,接收到ERROR包结束程序
    operaCode = htons(3);
    block_num = 0; //块编号
    // uint8_t block_num1; //低字节
    // uint8_t block_num2;


    //打开文件
    int fd_src = open(fileName, O_RDONLY | O_CREAT, 0664);
    if(fd_src<0)
    {
        ERR_MSG("open");
        return -1;
    }
    printf("__%d__\n", __LINE__); //test
    
    //接收包
    res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
    if(res < 0)  //recvfrom函数错误
    {
        ERR_MSG("recvfrom");
        return -1;
    }
    printf("__%d__\n", __LINE__); //test
    //收到的是ACK包
    if(*(short*)buf == htons(4))  
    {
        while(1)
        {
            block_num++;  //块编号++
            // *(unsigned short*)buf = htons(3);
            // *(unsigned short*)(buf+2) = htons(block_num);
            
            printf("[%s : %d] : %u\n", \
                    inet_ntoa(rcvAddr.sin_addr), ntohs(rcvAddr.sin_port), \
                    block_num);
            //准备数据包的操作码和块编号
            pack_size = sprintf(buf, "%c%c%c%c", \
                *(char*)(&operaCode), *((char*)(&operaCode)+1), \
                *((char*)(&block_num)+1), *(char*)(&block_num) );  
            //将文件内容读取到buf中的数据区(包括最后一个不足512bytes的数据包)
            res = read(fd_src, buf+4, 512);
            if( res < 0)
            {
                ERR_MSG("write");
                return -1;
            }
            //发送数据包
            //发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
            //想要发送给谁,就填谁的地址信息结构体
            if( (pack_size = sendto(sfd, buf, res+4, 0, (struct sockaddr *)&rcvAddr, sizeof(rcvAddr)) ) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            printf("data发送成功\n");
            int cnt = 0;
            do
            {
                printf("__%d__\n", __LINE__); //test
                // cnt++;
                //接收ACK包
                res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
                if(res < 0)  //recvfrom函数错误
                {
                    ERR_MSG("recvfrom");
                    return -1;
                }
                printf("__%d__\n", __LINE__); //test
                //接收到数据包对应的ACK包,退出循环
                if(*((short *)buf  +1) == htons(block_num))
                {
                    printf("ACK received\n");
                    break;
                }
                printf("__%d__\n", __LINE__); //test
                if(cnt > 100)
                {
                    ERR_MSG("Waiting ACK for too long");
                    cnt = 0;
                    return -1;
                }
            }while(*(short*)buf != htons(4)); //当收不到对应ACK包时,循环接收,直到接收到对应ACK包
            printf("%d\n", pack_size);
            if(pack_size<516)
            {
                printf("文件上传完毕\n");
                close(fd_src);
                break;
            }
        }
    }
    //收到的是错误包
    else if(*(short*)buf == htons(5))    
    {
        ERR_MSG("Error pack");
        return -1;
    }
}
int download(int sfd, struct sockaddr_in sin)
{
    char fileName[128];
    //定义发送和接收缓冲区
    char buf[516] = "";
    //定义操作码
    uint16_t operaCode;
    //recvfrom返回的参数,代表 接收到的 包的大小
    int res;
    //准备发送的包的大小
    int pack_size;
    //接收到的包中,存放的对端ip
    struct sockaddr_in rcvAddr;
    //addrlen=sizeof(rcvAddr)
    socklen_t addrlen;
    //块编号
    uint16_t block_num = 0;
    printf("请输入要下载的文件名:");
    scanf("%s", fileName);
使用sprintf发送请求包
    //准备缓冲区数据
    bzero(buf, sizeof(buf));
    operaCode = htons(1);  //读写请求操作码为1,读(下载)
    pack_size = sprintf(buf, "%c%c%s%c%s%c", \
        *(char*)(&operaCode), *((char*)(&operaCode)+1), \
        fileName, 0, "octet", 0); 
    //发送读请求数据包
    //发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
    //想要发送给谁,就填谁的地址信息结构体
    if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("sendto");
        return -1;
    }
    printf("读请求包发送成功\n");
使用sprintf发送请求包
    addrlen = sizeof(rcvAddr);
    //接收数据
    //res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
    //res = recv(sfd, buf, sizeof(buf), 0);
        
    //如果接收到数据包,发送ACK包,接收到ERROR包结束程序
    operaCode = htons(4);
    block_num = 0; //块编号
    // uint8_t block_num1; //低字节
    // uint8_t block_num2;


    //打开文件
    int fd_dest = open(fileName, O_WRONLY | O_CREAT, 0664);
    if(fd_dest<0)
    {
        ERR_MSG("open");
        return -1;
    }
    while(1)
    {
        
        //接收包
        res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
        if(res < 0)  //recvfrom函数错误
        {
            ERR_MSG("recvfrom");
            return -1;
        }
        //收到的是数据包
        if(*(short*)buf == htons(3))  
        {
            //将数据写入文件(包括最后一个不足512bytes的数据包)
            if( write(fd_dest, buf+4, res-4) < 0)
            {
                ERR_MSG("write");
                return -1;
            }
            //包计数
            block_num = *((uint16_t*)(buf+2));
            printf("[%s : %d] : %u\n", \
                    inet_ntoa(rcvAddr.sin_addr), ntohs(rcvAddr.sin_port), \
                    block_num);
            //准备ACK包
            pack_size = sprintf(buf, "%c%c%c%c", \
                *(char*)(&operaCode), *((char*)(&operaCode)+1), \
                (uint8_t)block_num, *(((uint8_t*)&block_num)+1)); 
            //发送ACK包
            //发送给服务器,所以上面的代码,需要填充好服务器的地址信息结构
            //想要发送给谁,就填谁的地址信息结构体
            if(sendto(sfd, buf, pack_size, 0, (struct sockaddr *)&rcvAddr, sizeof(rcvAddr)) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            printf("ack发送成功\n");
            if(res<516)
            {
                printf("文件下载完毕\n");
                close(fd_dest);
                break;
            }
        }
        //收到的是错误包
        else if(*(short*)buf == htons(5))    
        {
            ERR_MSG("Error pack");
            return -1;
        }
    }

}
                                                                                                                                                  

你可能感兴趣的:(udp,网络,网络协议)