java网络编程之TFTP(二)
今天在这里贴一个TFTP客户端实现的实例,希望可以帮助到有需要的人。【标准服务器端大家自行下载,推荐tftpd32】
首先构建一个TftpSocket类(基于UDP通信):
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
class TftpSocket {
private static final String Server_IP ="127.0.0.1"; //指明服务器IP
private static final int Server_port =69; //指明服务器监听端口
private static final byte RRQ = 1; //请求读
private static final byte DAT = 3; //文件数据
private static final byte ACK = 4; //确认,继续进行传输
private static final byte ERROR = 5; //发生错误
private static final int PACKET_SIZE = 516; //数据为0~512字节,加上2字节的操作码和2字节的块编号
private DatagramSocket datagramSocket = null; //基于UDP,所以使用DatagramSocket
private InetAddress address = null;
private byte[] requestArray; //数据包数组
private byte[] buf; //数据缓存区
private DatagramPacket outDatagramPacket; //发出数据包
private DatagramPacket inDatagramPacket; //接收数据包
//该方法设置为public属性,作为与主程序的接口,通过传入文件名进行获取文件
public void get(String fileName) throws Exception {
address = InetAddress.getByName(Server_IP); //使用InetAddress的静态方法getByName(String host)得到服务器的IP地址
datagramSocket = new DatagramSocket(); //基于UDP数据报,所以用到DatagramSocket
requestArray = createRequest(RRQ, fileName, "octet"); //通过createRequest(RRQ, fileName, "octet")方法得到一个请求读报文
outDatagramPacket = new DatagramPacket(requestArray,requestArray.length, address, Server_port); //发到服务器的数据包
datagramSocket.send(outDatagramPacket);
ByteArrayOutputStream byteOut = receiveFile(); //利用receiveFile()从服务器接收文件
writeFile(byteOut, fileName); //利用writeFile()把文件写到当地磁盘
}
//该方法用于从服务器接收文件,保存到一个字符数组并返回
private ByteArrayOutputStream receiveFile() throws Exception{
ByteArrayOutputStream byteOutOS = new ByteArrayOutputStream();
int block = 1;
do {
System.out.println("接收到第"+block+"个数据包"); //block最为一个计数器,计算收到的数据包
block++;
buf = new byte[PACKET_SIZE]; //设置数据缓冲区
inDatagramPacket = new DatagramPacket(buf,buf.length, address,datagramSocket.getLocalPort());
datagramSocket.receive(inDatagramPacket);
byte[] opCode = { buf[0], buf[1] }; //获取接收报文中前两个字节的操作码
if (opCode[1] == ERROR) {
reportError();
} else if (opCode[1] == DAT) {
byte[] blockNumber = { buf[2], buf[3] }; //获取接收报文中操作码之后的两个字节的块编号
DataOutputStream dos = new DataOutputStream(byteOutOS);
dos.write(inDatagramPacket.getData(), 4,inDatagramPacket.getLength() - 4);
sendAcknowledgment(blockNumber); //发送ACK,确认收到该块编号的报文
}
} while (!isLastPacket(inDatagramPacket));
System.out.println("文件接收完毕!!");
return byteOutOS;
}
//该方法是发送ACK数据包,用于确认收到该块编号的数据包
private void sendAcknowledgment(byte[] blockNumber){
byte[] ACKArray = { 0, ACK, blockNumber[0], blockNumber[1] };
DatagramPacket ack = new DatagramPacket(ACKArray, ACKArray.length,address,
inDatagramPacket.getPort()); //ACK数据包
try {
datagramSocket.send(ack);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//该方法用于将接收到的文件写入到本地磁盘中
private void writeFile(ByteArrayOutputStream b, String fileName){
try {
OutputStream outputStream = new FileOutputStream(fileName);
b.writeTo(outputStream); // 将此 byte 数组输出流的全部内容写入到指定的输出流参数中
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private boolean isLastPacket(DatagramPacket datagramPacket) { //判断是否是最后一个数据包
if (datagramPacket.getLength() < 512)
return true;
else
return false;
}
//该方法为构造请求读/写数据包(包括操作码、文件名、模式串)
private byte[] createRequest(final byte opCode, final String fileName,
final String mode) {
byte zeroByte = 0;
int ByteLength = 2 + fileName.length() + 1 + mode.length() + 1; //文件名和模式串都是以0字节终止
byte[] ByteArray = new byte[ByteLength];
int position = 0;
ByteArray[position] = zeroByte;
position++;
ByteArray[position] = opCode; //设置操作码(读或写)
position++;
for (int i = 0; i < fileName.length(); i++) {
ByteArray[position] = (byte) fileName.charAt(i); //返回指定索引处的 char 值,强转为byte类型
position++;
}
ByteArray[position] = zeroByte; //文件名以0字节作为终止
position++;
for (int i = 0; i < mode.length(); i++) {
ByteArray[position] = (byte) mode.charAt(i); //返回指定索引处的 char 值,强转为byte类型
position++;
}
ByteArray[position] = zeroByte; //模式以0字节作为终止
return ByteArray;
}
//该方法为显示差错码和差错信息
private void reportError() {
String errorCode = new String(buf, 3, 1); //获取差错码
String errorText = new String(buf, 4,inDatagramPacket.getLength() - 4); //获取差错信息
System.err.println("Error: " + errorCode + " " + errorText);
}
}
这个是一个简单的测试方法,可以从服务器端指定的位置下载到需要的文件:
public class TftpTest {
public static void main(String[] args) {
String FileName ="doc1.txt";
TftpSocket s= new TftpSocket();
try {
s.get(FileName);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}