tftp报文格式
RRQ 和WRQ包(代码分别为1和2)的格式:
2 bytes string 1 byte string 1 byte
------------------------------------------------
| Opcode | Filename | 0 | Mode | 0 |
OP码为3:从第五个字节开始才是数据
2 bytes 2 bytes n bytes
----------------------------------
| Opcode | Block # | Data |
其他基本信息就不废话,直接贴码
服务端:
package com;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @author zhaixx
* @version 1.0
* @category com
*/
public class ServiceAgint extends Thread
{
// over the parent's run methord
public void run()
{
try
{
DatagramSocket tftpd = new DatagramSocket(69); // tftp server socket
byte[] buf = new byte[516]; // a buffer for UDP packet
DatagramPacket dp = new DatagramPacket(buf, 516); // a UDP packet
DataInputStream din = null;
ClientAgent newClient = null;
short tftp_opcode = 0; // opcode: the 2 bytes in the front of tftp packet
String tftp_filename = null;
String tftp_mode = null;
while (true)
{
System.out.println("启动了");
tftpd.receive(dp); // wait for a client
int i = dp.getLength(); // 接收数据
if (i > 0)
{ // 指定接收到数据的长度,可使接收数据正常显示,开始时很容易忽略这一点
buf = dp.getData(); // get the UDP packet data
// 处理数据
cleandata(buf, dp.getAddress(), dp.getPort());
i = 0;// 循环接收
}
din = new DataInputStream(new ByteArrayInputStream(buf));
tftp_opcode = din.readShort(); // get the opcode
{ // get the filename
int fnoffset = 2;
int fnlen = 0;
while (din.readByte() != 0)
{
fnlen++; // filename will end with a null char('\0')
}
tftp_filename = new String(buf, fnoffset, fnlen);
// get the mode
int mdnoffset = fnoffset + fnlen + 2;
int mdnlen = 0;
while (din.readByte() != 0)
{
mdnlen++; // filename will end with a null char('\0')
}
tftp_mode = new String(buf, mdnoffset, mdnlen);
}
switch (tftp_opcode)
{
case 1:
// RRQ
case 2:
// WRQ
newClient = new ClientAgent(dp.getAddress(), dp.getPort(),
tftp_opcode, tftp_filename, tftp_mode);
newClient.setDaemon(true);
newClient.start();
// System.out.println("debug: Main.main() --> a RRQ thread start ....");
break;
}
}
}
catch (Exception e)
{
}
}
/**
* 处理数据
*
* @param buf
* @param address
* @param port
*/
private void cleandata(byte[] receiveByte, InetAddress address, int port)
{
System.out.println("开始了");
try
{
DataInputStream din = null;
ClientAgent newClient = null;
short tftp_opcode = 0; // opcode: 每个数据包都有一个opcode(2个字节),表示包的类型
String tftp_filename = null;// 文件名
String tftp_mode = null;// 传输模式:netASCII 模式即 8 位 网络ASCII码
// ;octet,即八位组模式(替代了以前版本的二进制模式),如原始八位字节
din = new DataInputStream(new ByteArrayInputStream(receiveByte));
System.out.println("tftp_opcode:"+din.readShort());
tftp_opcode = din.readShort();
short i = 2;
if (1 == tftp_opcode)
{
System.out.println("[{}]端口[{}]暂不支持文件下载!" + address.toString() + "----"
+ port);
}
else if ( i == tftp_opcode)
{
System.out.println("[{}]端口[{}]文件上传!"+address.toString()+"--"+port);
//WRQ
/*获取文件名,只有opcode=1,2才能获取到文件名,现在支持2上传文件
* RRQ/WRQ包:
--------------------------------------------------------------
| Opcode | Filename | 0 | Mode | 0 |
---------------------------------------------------------------
2 bytes string 1 byte string 1 byte
RRQ和WRQ包(代码分别为1和2)的格式如上所示。文件名是NETASCII码字符,
以0结束。 而MODE域包括了字符串"netascii"或"octet",名称不分大小写。
接收到NETASCII格式数据的主机必须将数据转换为本地格式。OCTET模式用于传输文件,
这种文件在源机上以8位格式存储。
*/
int fnoffset = 2;//去除Opcode从2开始
int fnlen = 0;//文件名的长度
while (din.readByte() != 0) {
fnlen++; //filename will end with a null char('\0')
}
tftp_filename = new String(receiveByte, fnoffset, fnlen);
//get the mode
int mdnoffset = fnoffset + fnlen + 2;
int mdnlen = 0;
while (din.readByte() != 0) {
mdnlen++; //mode will end with a null char('\0')
}
tftp_mode = new String(receiveByte, mdnoffset, mdnlen);
System.out.println("[{}]端口[{}]文件上传[{}]模式[{}]"+address.toString()+"---"+port+"---"+tftp_filename+"---"+tftp_mode);
newClient = new ClientAgent(address, port, tftp_opcode, tftp_filename, tftp_mode);
newClient.run();
}else{
System.out.println("暂不支持!:"+ String.valueOf(receiveByte));
System.out.println("tftp_opcode:"+din.readShort());
}
}
catch (Exception e)
{
}
}
public static void main(String[] args) {
ServiceAgint agetn = new ServiceAgint();
agetn.run();
}
}
客户端:
package com;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
/**
* @author zhaixx
* @version 1.0
* @category com
*/
public class ClientAgent extends Thread
{
private InetAddress m_ClientAddress; // ip of the client
private int m_ClientPort; // port of the client
private DatagramSocket m_so_tftp; // the socket object send or get message
private short m_curopcode; // the current opcode( wrq/rrq )
private String m_filename;
private String m_mode;
private final int m_MAX_nTimeOut = 5;
public ClientAgent(InetAddress ip, int port, short opcode, String fname, String mode)
{
this.m_ClientAddress = ip;
this.m_ClientPort = port;
this.m_curopcode = opcode;
this.m_filename = fname;
this.m_mode = mode;
}
/**
* 创建客户端发送给服务端的tftp的udp连接
*/
private boolean createTftp()
{
// 如果端口被占用或者超时,允许重试10次,重新找10个端口
int nfail = 10;
while (nfail-- > 0)
{
int port = 9191;
try
{
if (port == 0)
{
}
else
{
this.m_so_tftp = new DatagramSocket(port);
}
break; // get a random port number
}
catch (SocketException e)
{
}
catch (IllegalArgumentException e1)
{
System.out.println("[{}]端口[{}]文件[{}]服务器没有可用端口[{}]了" + m_ClientAddress
+ "--" + m_ClientPort + "--" + m_filename + "--" + port);
e1.printStackTrace();
}
}
if (this.m_so_tftp == null)
{
System.out.println("kong");
return false;
}
return true;
}
public void run()
{
if (!createTftp())
{
return;
}
switch (this.m_curopcode)
{
case 1:
// RRQ
// this.RRQ();
break;
case 2:
// WRQ
this.WRQ();
// System.out.println("debug: tftpClientAgent.run() --> a WRQ ended ...");
break;
}
this.m_so_tftp.close();
}
// wait for data packet
private DatagramPacket waitForData()
{
int ntimeout = 1;
DatagramPacket dp = null;
byte[] buf = new byte[516];
this.initZeroByteArray(buf);
dp = new DatagramPacket(buf, 516);
while (ntimeout > 0)
{
try
{
this.m_so_tftp.setSoTimeout(1000);
this.m_so_tftp.receive(dp);
System.out.println("dp.getLength()" + dp.getLength());
break;
}
catch (SocketTimeoutException e1)
{
ntimeout--;
e1.printStackTrace();
}
catch (IOException e2)
{
ntimeout--;
e2.printStackTrace();
}
}
return (ntimeout > 0 ? dp : null);
}
// init a byte[] with 0s
private void initZeroByteArray(byte[] buf)
{
for (int i = 0; i < buf.length; i++)
{
buf[i] = 0;
}
}
public void WRQ()
{
int ntimeout = this.m_MAX_nTimeOut;
try
{
short nblock = 0;
// send the #0 block ACK to start the transfer
this.SendACK(nblock++);
// wait for the data packet
while (ntimeout > 0)
{
DatagramPacket dp;
dp = this.waitForData();
System.out.println("dp.getAddress():" + dp.getAddress());
System.out.println("dp.getPort():" + dp.getPort());
if (dp == null)
{
ntimeout--;
}
else
{
if (!((dp.getAddress().equals(this.m_ClientAddress)) && (dp.getPort() == this.m_ClientPort)))
{
ntimeout--;
}
else
{ // right ip and port
// get the opcode
System.out.println("开始解析");
DataInputStream din = new DataInputStream(
new ByteArrayInputStream(dp.getData()));
int opcode = din.readShort();
// System.out.println("debug: the opcode is " + opcode);
if (opcode != 3)
{
// 不是03号data包
if (opcode == 2 && nblock == 1)
{
// 是02号WRQ包,且正在等待#1号data包
// 此时收到WRQ,说明#0号ACK有可能失败了,重发
this.SendACK((short) (nblock - 1));
}
else
{
this.SendERROR((short) 4, "非法的TFTP操作");
}
ntimeout--;
}
else
{ // data packet
int nblk = din.readShort();
// " -- the curBlock is #" + nblk);
if (nblk != nblock)
{
// 不是期待的块号,重发上一个包的ACK,超时计数减1
System.out.println("+++++++++++++++++++++++nblk:" + nblk
+ "=s" + String.valueOf(nblk != nblock) + nblock);
this.SendACK((short) (nblock - 1));
ntimeout--;
}
else
System.out.println("------------nblk:" + nblk);
{ // the right packet waiting for
if (!this.SaveFile(nblock, dp, this.m_filename,
this.m_mode))
{
ntimeout = 0;
this.SendERROR((short) 3, "磁盘满或超过分配的配额");
}
else
{
// send the current block ACK
this.SendACK(nblock);
if (dp.getLength() - 4 >= 512)
{
// the transfer isn't ended
nblock++; // wait for the next block
ntimeout = this.m_MAX_nTimeOut; // 重置超时记数
}
else
{
// ok, there is no more data, the transfer succ
ntimeout = 1;
}
}
}
}
}
}
}
}
catch (Exception e)
{
}
}
// send an Error packet with special ErrCode and ErrMsg
private void SendERROR(short ErrCode, String ErrMsg)
{
byte[] bufERROR;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try
{
dout.writeShort(5); // opcode
dout.writeShort(ErrCode);
dout.write(ErrMsg.getBytes());
dout.writeByte(0); // end of the ErrMsg
bufERROR = bout.toByteArray();
// construct a UDP packet
DatagramPacket dpERROR = new DatagramPacket(bufERROR, bufERROR.length,
this.m_ClientAddress, this.m_ClientPort);
// construct a UDP packet and send it
this.m_so_tftp.send(dpERROR);
}
catch (IOException ex)
{
}
}
/**
* | Opcode | Block | ----------------------- 2 bytes 2 bytes
*
* @param nblock
*/
// send an ACK packet with special block number
private void SendACK(short nblock)
{
System.out.println("*******nblock:" + nblock);
byte[] bufACK;
ByteArrayOutputStream bout = new ByteArrayOutputStream(4);
DataOutputStream dout = new DataOutputStream(bout);
try
{
dout.writeShort(4); // opcode
dout.writeShort(nblock); // block number
bufACK = bout.toByteArray();
// construct a UDP packet
DatagramPacket dpACK = new DatagramPacket(bufACK, 4, this.m_ClientAddress,
this.m_ClientPort);
// construct a UDP packet and send it
this.m_so_tftp.send(dpACK);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
/**
* @param nblock
* @param dp
* @param fpath
* @param mode
* @return
*/
// save the data into the special file
private boolean SaveFile(int nblock, DatagramPacket dp, String fpath, String mode)
{
// ignore the mode, use the binary mode will keep the data away from being changed
System.out.println("fpath:" + fpath);
boolean isSucc = false;
if (nblock < 1)
{
return false;
}
// 传输的文件
File f = new File("D:\\tee.xml");
if (!f.exists())
{
// file not exists
try
{
f.createNewFile(); // create it
}
catch (IOException ex)
{
ex.printStackTrace();
return false;
}
}
RandomAccessFile rf;
try
{
rf = new RandomAccessFile(f, "rw");
}
catch (FileNotFoundException ex)
{
ex.printStackTrace();
return false;
}
byte[] buf = dp.getData(); // get the data buffer
int buflen = dp.getLength();
try
{
rf.seek((nblock - 1) * 512); // move the file pointer to last write position
rf.write(buf, 4, buflen - 4); // write the data
rf.setLength((nblock - 1) * 512 + buflen - 4); // reset the file size
isSucc = true;
rf.close();
}
catch (IOException ex)
{
ex.printStackTrace();
return false;
}
return isSucc;
}
}