// TFTP Class TftpSocket.java -- AD_Li package net.ad_li.network; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /** *//** * A simple class for TFTP,can be use as server and client. * * Use UDP as lower level protocol. * * Super class:{@link DatagramSocket} * * @author AD */ publicclass TftpSocket extends DatagramSocket implements Runnable ...{ /** *//** DEBUG FLAG set it to true to print the debug information. */ privatestaticfinalboolean DEBUG =false; /** *//** Type of TFTP packet(local code none). */ protectedfinalstaticint NONE =0; /** *//** Type of TFTP packet(read request). */ protectedfinalstaticint RRQ =1; /** *//** Type of TFTP packet(write request). */ protectedfinalstaticint WRQ =2; /** *//** Type of TFTP packet(data). */ protectedfinalstaticint DAT =3; /** *//** Type of TFTP packet(acknowledgment). */ protectedfinalstaticint ACK =4; /** *//** Type of TFTP packet(error). */ protectedfinalstaticint ERR =5; /** *//** Type of TFTP packet(local code unknown). */ protectedfinalstaticint UNKNOWN =6; /** *//** Type of TFTP packet(local code timeout). */ protectedstaticint TIMEOUT =7; /** *//** File name in string form. */ protected String m_fNameString; /** *//** File name in byte array form. */ protectedbyte[] m_fNameByte; /** *//** File type in byte array form. */ protectedbyte[] m_fType; /** *//** Host Address use in TFTP client. */ protected InetAddress m_hostAddr; /** *//** Port use in TFTP server. */ protectedint m_serverPort =69; /** *//** Data bytes for send. */ protectedbyte[] m_bytesOut =newbyte[516]; /** *//** Data bytes for receive. */ protectedbyte[] m_bytesIn =newbyte[1024]; /** *//** The sequence number of the packet. */ protectedint m_block; /** *//** Input stream. */ protected FileInputStream m_fileIn; /** *//** Output stream. */ protected FileOutputStream m_fileOut; /** *//** Flag of end of files */ protectedboolean m_EOF =false; /** *//** Operation request(use in TFTP server as a flag of server type). */ protectedint m_serverCode =0; /** *//** Error output stream */ protected PrintStream m_errOut = System.out; /** *//** * @param fName * file name * @param fType * file type * @param host * host name can be URL or IP address */ public TftpSocket(String fName, String fType, String host) throws UnknownHostException, SocketException ...{ create(fName, fType, host); } /** *//** * * @param fName * file name * @param fType * file type * @param host * host name can be URL or IP address * @param serPort * local port for server * @param serAddress * local address for server */ public TftpSocket(String fName, String fType, String host, int serPort, InetAddress serAddress) throws UnknownHostException, SocketException ...{ super(serPort, serAddress); create(fName, fType, host); } /** *//** * private constructor(used by server only) */ private TftpSocket() throws SocketException ...{ } /** *//** * * @param fName * file name * @param fType * file type * @param host * host name can be URL or IP address */ privatevoid create(String fName, String fType, String host) throws UnknownHostException, SocketException ...{ // try {// try get the file name by ISO-8859-1 and convert it into bytes // m_fNameByte = fName.getBytes("ISO-8859-1"); // } catch (Exception exce) { // exce.printStackTrace(m_errOut); // m_fNameByte = fName.getBytes(); // }//TODO:here may be need some kind of code transformation m_fNameByte = fName.getBytes(); // get the file name in string form m_fNameString = fName; // get the file type m_fType = fType.getBytes(); // set 1.5 second time-out setSoTimeout(1500); // get the host Address m_hostAddr = InetAddress.getByName(host); } /** *//** * Send a specific error packet. * * @param code * error code * @param msg * error message * @return if the error packet send successful */ protectedboolean sendErrPacket(int code, String msg) ...{ // setup the error tag in packet m_bytesOut[0] = (byte) ((ERR >>8) &0xFF); m_bytesOut[1] = (byte) (ERR &0xFF); // setup the error code in packet m_bytesOut[2] = (byte) ((code >>8) &0xFF); m_bytesOut[3] = (byte) (code &0xFF); // setup data in packet System.arraycopy(msg.getBytes(), 0, m_bytesOut, 4, msg.length()); // setup the end in packet m_bytesOut[4+ msg.length()] =0; // make up the whole packet DatagramPacket pack =new DatagramPacket(m_bytesOut, 5+ msg.length(), m_hostAddr, m_serverPort); // try to send the packet try...{ send(pack); }catch (Exception exce) ...{// send fail exce.printStackTrace(m_errOut); returnfalse; } // send success returntrue; } /** *//** * Send a specific acknowledgment. * * @param block * the sequence number of the packet. * @return if the packet send successful */ protectedboolean sendAck(int block) ...{ // setup the ACK tag in packet m_bytesOut[0] = (byte) ((ACK >>8) &0xFF); m_bytesOut[1] = (byte) (ACK &0xFF); // setup the sequence number in packet m_bytesOut[2] = (byte) ((block >>8) &0xFF); m_bytesOut[3] = (byte) (block &0xFF); // setup the whole packet DatagramPacket pack =new DatagramPacket(m_bytesOut, 4, m_hostAddr, m_serverPort); // try to send the packet try...{ send(pack); }catch (Exception exce) ...{// send fail exce.printStackTrace(m_errOut); returnfalse; } // send success returntrue; } /** *//** * Send a packet (generic error or current block ACK) * * @param packType * packet type * @return if the packet send successful */ protectedboolean sendPacket(int packType) ...{ // packet length int packLen =2; // setup packet type tag in packet m_bytesOut[0] = (byte) ((packType >>8) &0xFF); m_bytesOut[1] = (byte) (packType &0xFF); // deal with each type of packet switch (packType) ...{ case RRQ:// deal with RRQ packet case WRQ:// deal with WRQ packet // setup the file in packet System.arraycopy(m_fNameByte, 0, m_bytesOut, 2, m_fNameByte.length); m_bytesOut[2+ m_fNameByte.length] =0; // setup the file type in packet System.arraycopy(m_fType, 0, m_bytesOut, 3+ m_fNameByte.length, m_fType.length); m_bytesOut[3+ m_fNameByte.length + m_fType.length] =0; packLen += m_fNameByte.length + m_fType.length +2; break; case DAT:// deal with DAT packet case ACK:// deal with ACK packet // setup sequence number in packet m_bytesOut[2] = (byte) ((m_block >>8) &0xFF); m_bytesOut[3] = (byte) (m_block &0xFF); packLen +=2; // more for file if (packType == DAT) ...{// deal with DAT packet // file length int fLen; // file buffer byte[] fBuf =newbyte[512]; try...{// try to load the packet data fLen = m_fileIn.read(fBuf, 0, 512); }catch (IOException ioExce) ...{// load packet data fail fLen =0;// assume EOF ioExce.printStackTrace(); } if (fLen <0) /**//* * if load packet fail(like load empty file),the fLen will * be -1,then the array copy will fail,so turn it into zero */ fLen =0; // copy the data into packet System.arraycopy(fBuf, 0, m_bytesOut, packLen, fLen); packLen += fLen; if (fLen !=512) m_EOF =true; } break; case ERR:// deal with ERR packet m_bytesOut[2] = m_bytesOut[3] = m_bytesOut[4] =0; packLen =5; break; default:// unknown packet type, send fail returnfalse; } // setup the whole packet DatagramPacket pack =new DatagramPacket(m_bytesOut, packLen, m_hostAddr, m_serverPort); if (DEBUG) ...{// DEBUG information System.out.println("sending_packet "+ pack.getAddress() +":" + pack.getPort() +" packet_code="+ packType); } try...{// try to send the packet send(pack); }catch (Exception exce) ...{// send fail exce.printStackTrace(m_errOut); returnfalse; } // send success returntrue; } /** *//** * Process a received packet * * @return type of packet */ protectedint rcvPacket() throws SocketException ...{ DatagramPacket pack =new DatagramPacket(m_bytesIn, 1024); // receive packet status boolean recStatus =false; try...{// try to receive the packet receive(pack); recStatus =true; }catch (IOException ioExce) ...{ ioExce.printStackTrace(m_errOut); } if (!recStatus) return TIMEOUT; // deal with each type of packet m_serverPort = pack.getPort(); // data in the packet byte[] bytData = pack.getData(); // get the packet type int packType = (bytData[0] <<8) + bytData[1]; // the sequence number of block in the packet received if (DEBUG) ...{// DEBUG information System.out.println("packet receive "+ pack.getAddress() +":" + pack.getPort() +" packet code="+ packType); } int blockIn; switch (packType) ...{ case RRQ:// deal with RRQ packet case WRQ:// deal with RRQ packet // do nothing, things has been done in superior function return packType; case ACK:// check block # blockIn = (bytData[2] <<8) | (bytData[3] &0xFF); if (DEBUG) ...{// DEBUG information System.out.println("block number="+ m_block +" blockIn number="+ blockIn); } if (blockIn != m_block) // block received is not the same as the socket (check fail) return NONE; ++m_block; return ACK; case DAT:// check block # and store blockIn = (bytData[2] <<8) | (bytData[3] &0xFF); if (DEBUG) ...{// DEBUG information System.out.println("block numer="+ m_block +" blockIn number="+ blockIn); } if (blockIn > m_block)// some packet may be lost return UNKNOWN; if (blockIn < m_block) ...{// the ACK packet may lost, so send it again sendAck(blockIn); return NONE; } // block number matches try...{ m_fileOut.write(bytData, 4, pack.getLength() -4); }catch (IOException ioExce) ...{// write fail ioExce.printStackTrace(m_errOut); } // the file is end if (pack.getLength() <516) m_EOF =true; // ACK current sendPacket(ACK); ++m_block; return DAT; case ERR:// do something with the error message String errMsg =new String(bytData, 4, pack.getLength()); m_errOut.println(errMsg); return ERR; default:// unknown packet type return UNKNOWN; } } /** *//** * Send a file * * @return send file success or not */ publicboolean fsend() throws SocketException ...{ try...{// try to get the file m_fileIn =new FileInputStream(m_fNameString); }catch (Exception exce) ...{// get the file fail exce.printStackTrace(m_errOut); returnfalse; } // send success or not boolean rv; try...{// try to send the packet // retry time int retry =0; // set block number to 0 m_block =0; rv = sendPacket(WRQ); if (!rv) // send fail return rv; // receive packet state int recState; while (!m_EOF) ...{ do...{// send the packet until receive the ACK packet recState = rcvPacket(); if (recState == TIMEOUT) ...{ if (++retry >5) // timeout too many times, send fail returnfalse; if (m_block !=0) if (!sendPacket(DAT))// send the DAT packet again returnfalse; continue; } // reset retry count retry =0; if (recState == ERR)// receive error packet, send fail returnfalse; if (recState == UNKNOWN) ...{// receive unknown packet, send // fail sendErrPacket(0, "Unknown error"); returnfalse; } }while (recState != ACK); if (!sendPacket(DAT))// send packet fail returnfalse; } // send success returntrue; }finally...{// close the stream no matter what streamClose(m_fileIn); } } /** *//** * Get a file * * @return get file success or not */ publicboolean freceive() throws SocketException ...{ // send success or not boolean rv; // receive packet state int recState; try...{// try to save the file m_fileOut =new FileOutputStream(m_fNameString); }catch (Exception exce) ...{// save the file fail exce.printStackTrace(m_errOut); returnfalse; } try...{// try to receive the packet // retry time int retry =0; // set the block number to 1 m_block =1; rv = sendPacket(RRQ); if (!rv)// send packet fail return rv; while (!m_EOF) ...{ do...{// try get the packet until receive the DAT packet recState = rcvPacket(); if (recState == TIMEOUT) ...{ if (++retry >5)// timeout too many times, get fail returnfalse; continue; } // reset retry count retry =0; if (recState == ERR) // receive the ERR packet, get fail returnfalse; if (recState == UNKNOWN) ...{// receive unknown packet, send // fail sendErrPacket(0, "Unknown error"); returnfalse; } }while (recState != DAT); } // get success returntrue; }finally...{// close stream in all cases if (!streamClose(m_fileOut)) returnfalse; } } /** *//** * Handy function to close a file input stream. * * @param inStrem * the file input stream to close * @return close success or fail */ privateboolean streamClose(FileInputStream fInStream) ...{ try...{// try to close the stream fInStream.close(); }catch (Exception exce) ...{// close stream fail exce.printStackTrace(m_errOut); returnfalse; } // close success returntrue; } /** *//** * Handy function to close a file output stream. * * @param fOutStream * the file output stream to close * @return success or fail */ privateboolean streamClose(FileOutputStream fOutStream) ...{ try...{// try to close the stream fOutStream.close(); }catch (Exception exce) ...{// close stream fail exce.printStackTrace(m_errOut); } // send success returntrue; } /** *//** * Server thread */ publicvoid run() ...{ if (m_serverCode == WRQ) ...{// deal with the WRQ request try...{// try setup the output stream m_fileOut =new FileOutputStream(m_fNameString); }catch (IOException exce) ...{// can't get the file sendErrPacket(2, "Access violation"); return; } // send ACK 0 sendAck(0); // set block number to 1 m_block =1; // wait for DATA int recState;// receive state do...{// while the file is not end receive the packet try...{// try receive the packet recState = rcvPacket(); }catch (SocketException sockExce) ...{// receive fail sockExce.printStackTrace(); recState = ERR; } if (recState == ERR) ...{ // close the output stream streamClose(m_fileOut); return; } if (recState != DAT && recState != NONE) ...{ // close the output stream streamClose(m_fileOut); sendErrPacket(0, "?"); return; } }while (!m_EOF); // close the output stream streamClose(m_fileOut); } if (m_serverCode == RRQ) ...{// deal with RRQ packet try...{// try to setup input stream m_fileIn =new FileInputStream(m_fNameString); }catch (IOException ioExce) ...{// can't get the file if (DEBUG) ...{// DEBUG information ioExce.printStackTrace(m_errOut); } sendErrPacket(1, "File not found."); return; } // send DATA m_block =1; // retry time int retry =0; do...{ int recState; if (!sendPacket(DAT)) ...{// send packet fail sendErrPacket(0, "?"); // close the input stream streamClose(m_fileIn); return; } do...{ try...{// try to get the ACK recState = rcvPacket(); }catch (SocketException sockExce) ...{ sockExce.printStackTrace(m_errOut); recState = ERR; } if (recState == ERR) ...{ // close the input stream streamClose(m_fileIn); return; } if (recState == UNKNOWN || recState == RRQ || recState == WRQ || recState == DAT) ...{ sendErrPacket(4, "Illegal"); // close the input stream streamClose(m_fileIn); return; } if (recState == TIMEOUT &&++retry >5) ...{ // time out and retry too many times sendErrPacket(0, "TIMEOUT"); // close the input stream streamClose(m_fileIn); return; } if (recState == ACK) ...{ // ++m_block; // reset retry count to 0 retry =0; } }while (recState != ACK && recState != TIMEOUT); }while (!m_EOF); // close the output stream streamClose(m_fileIn); } } /** *//** * Interface for server */ publicstaticvoid tftpd() ...{ DatagramSocket serSock; try...{ serSock =new DatagramSocket(69); }catch (SocketException sockExce) ...{ System.out.print("Unable to start server:"+ sockExce); return; } byte[] buf =newbyte[1024]; DatagramPacket pack =new DatagramPacket(buf, 1024); while (true) ...{ try...{// wait for WRQ or RRQ -- all others are errors serSock.setSoTimeout(0); serSock.receive(pack); byte[] data = pack.getData(); int packType = (data[0] <<8) + data[1]; if (DEBUG) ...{// DEBUG information System.out .println("receive packet "+ pack.getAddress() +":"+ pack.getPort() +" packet code=" + packType); } if (packType != WRQ && packType != RRQ) ...{ // send back error packet byte[] errBytes =newbyte[5]; errBytes[0] = (byte) ((ERR >>8) &0xFF); errBytes[1] = (byte) (ERR &0xFF); errBytes[2] = (byte) 0; errBytes[3] = (byte) 0; errBytes[4] =0; pack.setData(errBytes); serSock.send(pack); continue; } // start new thread (use private constructor) TftpSocket worker =new TftpSocket(); // file name length and file type length int zlen, zlen1; worker.m_serverPort = pack.getPort(); worker.m_hostAddr = pack.getAddress(); // get the file name from packet data for (zlen =0; data[zlen +2] !=0; ++zlen) ; worker.m_fNameString =new String(data, 2, zlen); worker.m_fNameByte = worker.m_fNameString.getBytes(); // get the file type from packet data for (zlen1 =0; data[zlen1 +3+ zlen] !=0; ++zlen1) ; worker.m_fType =new String(data, zlen +3, zlen1).getBytes(); worker.m_serverCode = packType; // start server thread new Thread(worker).start(); }catch (IOException exce) ...{ // ignore exce.printStackTrace(); } } } /** *//** * Test main. provide file name, file type, host on command line follow with * an S to send R(or nothing) to receive */ publicstaticvoid main(String[] args) throws Exception ...{ if (args.length ==0) ...{ System.out.println("Starting server"); new Tftpd().start(); return; } TftpSocket sock =new TftpSocket(args[0], args[1], args[2]); if (args.length <=3||!args[3].equals("S")) ...{ System.out.println("Client request "+ sock.freceive()); }else...{ System.out.println("Client request "+ sock.fsend()); } } /** *//** * Helper thread for test main */ staticclass Tftpd extends Thread ...{ publicvoid run() ...{ TftpSocket.tftpd(); } } }
从书上抄来的TFTP协议,终于调试到可以运行,已发现但未解决的问题:1文件名编码装换不明确,不知是否需要转换,如过用ISO-8859-1转换中文文件名会出错.2UDP用wireshark截包UDP校验和出现错误:提示:Checksum: 0x0020 [incorrect, should be 0x131f (maybe caused by "UDP checksum offload"?)