c# tftp 协议客户端实现

在使用 Tftp.Net.dll 创建客户端时,传输文件失败,故实现tftp客户端协议。协议初版支持512包长度,扩展后支持包长度协商。

具体协议如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9df048f54a8843359efab85dc901a35a.png

客户端代码实现:

#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
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值