TFTP:实现简单文本传输协议的上传功能

上一节我们开发的客户端能成功的从服务器端下载文件,本节我们完成相反功能,实现客户端向服务器端上传文件。文件上传与下载非常相似,首先我们向服务器发送一个写请求,相应数据包的格式与读请求类似,只不过option code对应的值从1变成2,同时在数据包中添加了要上传的文件名,我们首先在tftp客户端通过connect连接到服务器后,通过如下命令上传文件:

put 1.pdf

然后我们在服务器端通过wireshark抓包分析数据包结构,首先我们看看客户端发送给服务器的第一个数据包:


屏幕快照 2019-06-10 下午5.47.55.png

我们看到它的内容与我们上一节构造的读请求没有太大区别。如果服务器接受客户端上传请求,它会向客户端发送一个ack数据包,里面包含了第一个数据块的编号0,如图:


屏幕快照 2019-06-10 下午5.49.55.png

当收到服务器发送过来的ack后,客户端就可以将要上传的文件分割成多个小块,每个小块对应相应编号然后通过数据包发送给服务器,包含数据块的数据包与上一节服务器发送给客户端的数据块数据包一样:
屏幕快照 2019-06-10 下午5.52.50.png

客户端发送第一个数据块时,必须以编号1开头,发送后必须等待服务器返回相应的ack数据包后才能发送第二个数据块,根据这些原理,我们看看相应代码的实现:

public void putFile(String file_name) {
        upload_file = new File(file_name);
        this.file_name = file_name;
        //先打开要上传的文件
        try {
            file_input = new FileInputStream(upload_file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //向服务器发送写请求
        sendRequestPacket(OPTION_CODE_WRITE);
    }

private void sendRequestPacket(short option) {
        //向服务器发送读请求包
        String mode = "netascii";
        //+1表示要用0表示结尾
        byte[] read_request = new byte[OPTION_CODE_LENGTH + this.file_name.length() + 1 + mode.length() + 1];
        ByteBuffer buffer = ByteBuffer.wrap(read_request);
        buffer.putShort(option);
        buffer.put(this.file_name.getBytes());
        buffer.put((byte)0);
        buffer.put(mode.getBytes());
        buffer.put((byte)0);
        
        byte[] udpHeader = createUDPHeader(read_request);
        byte[] ipHeader = createIP4Header(udpHeader.length);
        
        byte[] readRequestPacket = new byte[udpHeader.length + ipHeader.length];
        buffer = ByteBuffer.wrap(readRequestPacket);
        buffer.put(ipHeader);
        buffer.put(udpHeader);
        //将消息发送给路由器
        try {
            ProtocolManager.getInstance().sendData(readRequestPacket, sever_ip);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

首先我们通过put_file函数获得要上传的文件名,并打开文件为读取内容做准备,sendRequestPacket函数构造了一个写请求数据包发送给服务器。接下来服务器会给客户端发送ACK消息,同时附带数据块编号,因此我们要解析该消息:

 private void handleACKPacket(ByteBuffer buff) {
            /*
             * 头两字节是option code,接下来的两字节是数据块编号
             */
            data_block = buff.getShort();
                put_block++;
            sendDataBlockPacket();
        }

接下来我们就可以根据编号,从文件中抽取一个数据块,构造一个数据块数据包,将数据包发送给服务器,然后等待服务器返回下一个数据包:

 private void sendDataBlockPacket() {
            System.out.println("send data block: " + put_block);
            /*
             * 数据块数据包包含三部分,头2字节是操作码,接下来2字节是数据块编号,最后512字节是数据块
             */
            byte[] file_content = new byte[512];
            try {
                int bytes_read = file_input.read(file_content);
                byte[] content = new byte[2 + 2 + bytes_read];
                ByteBuffer buf = ByteBuffer.wrap(content);
                buf.putShort(OPTION_CODE_DATA);
                buf.putShort(put_block);
                buf.put(file_content, 0, bytes_read);
                
                byte[] udpHeader = createUDPHeader(content);
                byte[] ipHeader = createIP4Header(udpHeader.length);
                byte[] dataPacket = new byte[udpHeader.length + ipHeader.length];
                buf = ByteBuffer.wrap(dataPacket);
                buf.put(ipHeader);
                buf.put(udpHeader);
                ProtocolManager.getInstance().sendData(dataPacket, sever_ip);
                System.out.println("send content with bytes: " + bytes_read);
                upload_file_size -= bytes_read;
                if (bytes_read < 512 || upload_file_size <= 0) {
                    System.out.println("put file complete");
                    put_block = 0;
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch(Exception e) {
                e.printStackTrace();
            }
            
        }

完成上面代码后,我们在主入口处增加如下代码:

  try {
               InetAddress ip = InetAddress.getByName("192.168.2.140");
            
               TFTPClient tftp = new TFTPClient(ip.getAddress());
               //EUPL-EN.pdf
               tftp.putFile("2.pdf");
           } catch(Exception e) {
               e.printStackTrace();
           }

运行后即可将给定文件上传给tftp服务器。
更详细的讲解和代码调试演示过程,请点击链接

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:


这里写图片描述

新书上架,请诸位朋友多多支持:
WechatIMG1.jpeg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容