在使用 Tftp.Net.dll 创建客户端时,传输文件失败,故实现tftp客户端协议。协议初版支持512包长度,扩展后支持包长度协商。
具体协议如下:
客户端代码实现:
#region COPYRIGHT (c) 2007 by Matthias Fischer
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Documents;
namespace App.Utils
{
/// <summary>
/// Implementation of Basic TFTP Client Functions
/// </summary>
public class TFTPClientEx
{
#region -=[ Declarations ]=-
/// <summary>
/// TFTP opcodes
/// </summary>
public enum Opcodes
{
Unknown = 0,
Read = 1,
Write = 2,
Data = 3,
Ack = 4,
Error = 5,
Ackex = 6//扩展字段
}
/// <summary>
/// TFTP modes
/// </summary>
public enum Modes
{
Unknown = 0,
NetAscii = 1,
Octet = 2,
Mail = 3
}
/// <summary>
/// A TFTP Exception
/// </summary>
public class TFTPException : Exception
{
public string ErrorMessage = "";
public int ErrorCode = -1;
/// <summary>
/// Initializes a new instance of the <see cref="TFTPException"/> class.
/// </summary>
/// <param name="errCode">The err code.</param>
/// <param name="errMsg">The err MSG.</param>
public TFTPException(int errCode, string errMsg)
{
ErrorCode = errCode;
ErrorMessage = errMsg;
}
/// <summary>
/// Creates and returns a string representation of the current exception.
/// </summary>
/// <returns>
/// A string representation of the current exception.
/// </returns>
/// <filterPriority>1</filterPriority>
/// <permissionSet class="System.Security.permissionSet" version="1">
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" PathDiscovery="*AllFiles*"/>
/// </permissionSet>
public override string ToString()
{
return String.Format("TFTPException: ErrorCode: {0} Message: {1}", ErrorCode, ErrorMessage);
}
}
private int tftpPort;
private string tftpServer = "";
#endregion
#region -=[ Ctor ]=-
/// <summary>
/// Initializes a new instance of the <see cref="TFTPClientEx"/> class.
/// </summary>
/// <param name="server">The server.</param>
public TFTPClientEx(string server)
: this(server, 69)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TFTPClientEx"/> class.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="port">The port.</param>
public TFTPClientEx(string server, int port)
{
Server = server;
Port = port;
}
#endregion
#region -=[ Public Properties ]=-
/// <summary>
/// Gets the port.
/// </summary>
/// <value>The port.</value>
public int Port
{
get { return tftpPort; }
private set { tftpPort = value; }
}
/// <summary>
/// Gets the server.
/// </summary>
/// <value>The server.</value>
public string Server
{
get { return tftpServer; }
private set { tftpServer = value; }
}
#endregion
#region -=[ Public Member ]=-
/// <summary>
/// Gets the specified remote file.
/// </summary>
/// <param name="remoteFile">The remote file.</param>
/// <param name="localFile">The local file.</param>
public void Get(string remoteFile, string localFile)
{
Get(remoteFile, localFile, Modes.Octet);
}
public delegate void TftpProgressHandler(double progress);
public event TftpProgressHandler OnProgress;
/// <summary>
/// Gets the specified remote file.
/// </summary>
/// <param name="remoteFile">The remote file.</param>
/// <param name="localFile">The local file.</param>
/// <param name="tftpMode">The TFTP mode.</param>
public void Get(string remoteFile, string localFile, Modes tftpMode)
{
int len = 0;
int packetNr = 0;
byte[] sndBuffer = CreateRequestPacket(Opcodes.Read, remoteFile, tftpMode);
byte[] rcvBuffer = new byte[8192+4];
BinaryWriter fileStream = new BinaryWriter(new FileStream(localFile, FileMode.Create, FileAccess.Write, FileShare.Read));
IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse(tftpServer), tftpPort);
// 局域网域名解析有问题,回环测试失败
// IPHostEntry hostEntry = Dns.GetHostEntry(tftpServer);
// IPEndPoint serverEP = new IPEndPoint(hostEntry.AddressList[0], tftpPort);
EndPoint dataEP = (EndPoint)serverEP;
Socket tftpSocket = new Socket(serverEP.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
// Request and Receive first Data Packet From TFTP Server
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
tftpSocket.ReceiveTimeout = 1000;
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
// keep track of the TID
serverEP.Port = ((IPEndPoint)dataEP).Port;
if (((Opcodes)rcvBuffer[1]) != Opcodes.Ackex)
{
Console.WriteLine($"tftp: 协商失败!");
return;
}
sndBuffer = CreateAckPacket(packetNr++);
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
Console.WriteLine($"tftp: 开始接收");
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
while (true)
{
// handle any kind of error
if (((Opcodes)rcvBuffer[1]) == Opcodes.Error)
{
fileStream.Close();
tftpSocket.Close();
throw new TFTPException(((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3], Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0'));
}
// expect the next packet
if ((((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == packetNr)
{
// Store to local file
fileStream.Write(rcvBuffer, 4, len - 4);
// Send Ack Packet to TFTP Server
sndBuffer = CreateAckPacket(packetNr++);
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
}
// Was ist the last packet ?
if (len < 8192 + 4)
{
break;
}
else
{
// Receive Next Data Packet From TFTP Server
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
}
}
// Close Socket and release resources
tftpSocket.Close();
fileStream.Close();
}
/// <summary>
/// Puts the specified remote file.
/// </summary>
/// <param name="remoteFile">The remote file.</param>
/// <param name="localFile">The local file.</param>
public void Put(string remoteFile, string localFile)
{
Put(remoteFile, localFile, Modes.Octet);
}
/// <summary>
/// Puts the specified remote file.
/// </summary>
/// <param name="remoteFile">The remote file.</param>
/// <param name="localFile">The local file.</param>
/// <param name="tftpMode">The TFTP mode.</param>
/// <remarks>What if the ack does not come !</remarks>
public void Put(string remoteFile, string localFile, Modes tftpMode)
{
int len = 0;
int packetNr = 0;
int sendDataLen = 8192;
byte[] sndBuffer = CreateRequestPacket(Opcodes.Write, remoteFile, tftpMode);
byte[] rcvBuffer = new byte[sendDataLen+4];
var fs = new FileStream(localFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
BinaryReader fileStream = new BinaryReader(fs);
IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse(tftpServer), tftpPort);
// 局域网域名解析有问题,回环测试失败
// IPHostEntry hostEntry = Dns.GetHostEntry(tftpServer);
// IPEndPoint serverEP = new IPEndPoint(hostEntry.AddressList[0], tftpPort);
EndPoint dataEP = (EndPoint)serverEP;
Socket tftpSocket = new Socket(serverEP.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
// Request Writing to TFTP Server
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
tftpSocket.ReceiveTimeout = 1000;
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
// keep track of the TID
serverEP.Port = ((IPEndPoint)dataEP).Port;
if (((Opcodes)rcvBuffer[1]) != Opcodes.Ackex)
{
Console.WriteLine($"tftp: 协商失败!");
return;
}
Console.WriteLine($"tftp: 开始发送");
List<byte[]> packet_list = new List<byte[]>();
// 发送第一个包
{
sndBuffer = CreateDataPacket(++packetNr, fileStream.ReadBytes(sendDataLen));
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
OnProgress((sendDataLen * (double)packetNr * 100) / fs.Length);
packet_list.Add(sndBuffer);
}
while (true)
{
// handle any kind of error
if (((Opcodes)rcvBuffer[1]) == Opcodes.Error)
{
fileStream.Close();
tftpSocket.Close();
Console.WriteLine($"tftp error code: {((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]} msg: { Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0')}");
throw new TFTPException(((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3], Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0'));
}
// expect the next packet ack
else if (((Opcodes)rcvBuffer[1]) == Opcodes.Ack)
{
if ((((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == packetNr)
{
sndBuffer = CreateDataPacket(++packetNr, fileStream.ReadBytes(sendDataLen));
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
OnProgress((sendDataLen * (double)packetNr * 100) / fs.Length);
packet_list.Add(sndBuffer);
// Console.WriteLine($"tftp: 发送进度 {(sendDataLen * (double)packetNr * 100) / fs.Length}");
// Console.WriteLine($"tftp: 发送 {packetNr}");
}
else if ((((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == packetNr - 1)
{
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
OnProgress((sendDataLen * (double)packetNr * 100) / fs.Length);
// Console.WriteLine($"tftp: 重发进度 {(sendDataLen * (double)packetNr * 100) / fs.Length}");
Console.WriteLine($"tftp: 重发 {packetNr}");
}
else
{
var index = (((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]);
if (index >= packet_list.Count||index<0)
continue;
var buf = packet_list[index];
tftpSocket.SendTo(buf, buf.Length, SocketFlags.None, serverEP);
// OnProgress((sendDataLen * (double)index * 100) / fs.Length);
Console.WriteLine($"tftp: 重发 {packetNr}");
}
}
// we are done
if (sndBuffer.Length < sendDataLen)
{
OnProgress(100);
Console.WriteLine($"tftp: {localFile} 发送完成!");
break;
}
else
{
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
}
}
// Close Socket and release resources
tftpSocket.Close();
fileStream.Close();
}
#endregion
#region -=[ Private Member ]=-
/// <summary>
/// Creates the request packet.
/// </summary>
/// <param name="opCode">The op code.</param>
/// <param name="remoteFile">The remote file.</param>
/// <param name="tftpMode">The TFTP mode.</param>
/// <returns>the ack packet</returns>
private byte[] CreateRequestPacket(Opcodes opCode, string remoteFile, Modes tftpMode)
{
// Create new Byte array to hold Initial
// Read Request Packet
int pos = 0;
string modeAscii = tftpMode.ToString().ToLowerInvariant();
byte[] ret = new byte[modeAscii.Length + remoteFile.Length + 4 + "blksize".Length + "8192".Length + 2];
// Set first Opcode of packet to indicate
// if this is a read request or write request
ret[pos++] = 0;
ret[pos++] = (byte)opCode;
// Convert Filename to a char array
pos += Encoding.ASCII.GetBytes(remoteFile, 0, remoteFile.Length, ret, pos);
ret[pos++] = 0;
pos += Encoding.ASCII.GetBytes(modeAscii, 0, modeAscii.Length, ret, pos);
ret[pos++] = 0;
pos += Encoding.ASCII.GetBytes("blksize", 0, "blksize".Length, ret, pos);
ret[pos++] = 0;
pos += Encoding.ASCII.GetBytes("8192", 0, "8192".Length, ret, pos);
ret[pos++] = 0;
return ret;
}
/// <summary>
/// Creates the data packet.
/// </summary>
/// <param name="packetNr">The packet nr.</param>
/// <param name="data">The data.</param>
/// <returns>the data packet</returns>
private byte[] CreateDataPacket(int blockNr, byte[] data)
{
// Create Byte array to hold ack packet
byte[] ret = new byte[4 + data.Length];
// Set first Opcode of packet to TFTP_ACK
ret[0] = 0;
ret[1] = (byte)Opcodes.Data;
ret[2] = (byte)((blockNr >> 8) & 0xff);
ret[3] = (byte)(blockNr & 0xff);
Array.Copy(data, 0, ret, 4, data.Length);
return ret;
}
/// <summary>
/// Creates the ack packet.
/// </summary>
/// <param name="blockNr">The block nr.</param>
/// <returns>the ack packet</returns>
private byte[] CreateAckPacket(int blockNr)
{
// Create Byte array to hold ack packet
byte[] ret = new byte[4];
// Set first Opcode of packet to TFTP_ACK
ret[0] = 0;
ret[1] = (byte)Opcodes.Ack;
// Insert block number into packet array
ret[2] = (byte)((blockNr >> 8) & 0xff);
ret[3] = (byte)(blockNr & 0xff);
return ret;
}
#endregion
}
}