上一节我们开发的客户端能成功的从服务器端下载文件,本节我们完成相反功能,实现客户端向服务器端上传文件。文件上传与下载非常相似,首先我们向服务器发送一个写请求,相应数据包的格式与读请求类似,只不过option code对应的值从1变成2,同时在数据包中添加了要上传的文件名,我们首先在tftp客户端通过connect连接到服务器后,通过如下命令上传文件:
put 1.pdf
然后我们在服务器端通过wireshark抓包分析数据包结构,首先我们看看客户端发送给服务器的第一个数据包:
我们看到它的内容与我们上一节构造的读请求没有太大区别。如果服务器接受客户端上传请求,它会向客户端发送一个ack数据包,里面包含了第一个数据块的编号0,如图:
当收到服务器发送过来的ack后,客户端就可以将要上传的文件分割成多个小块,每个小块对应相应编号然后通过数据包发送给服务器,包含数据块的数据包与上一节服务器发送给客户端的数据块数据包一样:
客户端发送第一个数据块时,必须以编号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服务器。
更详细的讲解和代码调试演示过程,请点击链接
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号: