#Region " 命名空间 " Imports System.IO Imports System.Net Imports System.Net.Sockets Imports System.Text #End Region ''' <summary> ''' FTP客户端 ''' </summary> ''' <remarks>2010-02-13</remarks> Public Class FTPClient #Region " 构造方法 " ''' <summary> ''' 构造方法 ''' </summary> ''' <remarks>2010-02-13</remarks> Public Sub New() '设置默认端口号 _port = 21 End Sub #End Region #Region " 枚举类型 " ''' <summary> ''' 数据类型 ''' </summary> ''' <remarks>2010-02-17</remarks> Public Enum DataType ASCII 'ASCII类型 BINARY '二进制类型 EBCDIC '扩增二进式十进交换码 End Enum #End Region #Region " 成员常量 " ''' <summary> ''' 缓冲的大小 ''' </summary> ''' <remarks>2010-02-13</remarks> Private Const BUFFER_SIZE As Integer = 512 ''' <summary> ''' FTP命令 ''' </summary> ''' <remarks>2010-02-14</remarks> Private Const FTP_COMMAND_USER As String = "USER {0}" '判断登录用户名 Private Const FTP_COMMAND_PASS As String = "PASS {0}" '判断登录密码 Private Const FTP_COMMAND_QUIT As String = "QUIT" '断开与FTP的连接 Private Const FTP_COMMAND_LIST As String = "LIST " '取得文件列表 Private Const FTP_COMMAND_STOR As String = "STOR {0}" '上传文件 Private Const FTP_COMMAND_TYPE_A As String = "TYPE A" '设置数据类型(ASCII类型) Private Const FTP_COMMAND_TYPE_E As String = "TYPE E" '设置数据类型(EBCDIC类型) Private Const FTP_COMMAND_TYPE_I As String = "TYPE I" '设置数据类型(二进制类型) Private Const FTP_COMMAND_RETR As String = "RETR {0}" '下载文件 Private Const FTP_COMMAND_SIZE As String = "SIZE {0}" '取得文件大小 Private Const FTP_COMMAND_RNFR As String = "RNFR {0}" '重新命名文件文件夹(旧) Private Const FTP_COMMAND_RNTO As String = "RNTO {0}" '重新命名文件文件夹(新) Private Const FTP_COMMAND_MKD As String = "MKD {0}" '创建目录 #End Region #Region " 成员变量 " ''' <summary> ''' Socket对象实例 ''' </summary> ''' <remarks>2010-02-13</remarks> Private _socketIns As Socket ''' <summary> ''' 主机 ''' </summary> ''' <remarks>2010-02-13</remarks> Private _host As String ''' <summary> ''' 端口 ''' </summary> ''' <remarks>2010-02-13</remarks> Private _port As Integer ''' <summary> ''' 最后信息ID ''' </summary> ''' <remarks>2010-02-14</remarks> Private _lastMessageID As Integer ''' <summary> ''' 最后信息内容 ''' </summary> ''' <remarks>2010-02-14</remarks> Private _lastMessage As String #End Region #Region " 成员属性 " ''' <summary> ''' 返回/设置主机名或主机IP ''' </summary> ''' <value>主机名或主机IP</value> ''' <returns>主机名或主机IP</returns> ''' <remarks>2010-02-13</remarks> Public Property Host() As String Get Return _host End Get Set(ByVal value As String) _host = value End Set End Property ''' <summary> ''' 返回/设置主机IP的端口号 ''' </summary> ''' <value>主机IP的端口号</value> ''' <returns>主机IP的端口号</returns> ''' <remarks>2010-02-13</remarks> Public Property Port() As Integer Get Return _port End Get Set(ByVal value As Integer) _port = value End Set End Property #End Region #Region " 过程函数 " ''' <summary> ''' 连接取FTP ''' </summary> ''' <param name="UserName">登录用用户名</param> ''' <param name="Password">登录用密码</param> ''' <returns> ''' 0 : 连接成功 ''' -1 : 连接失败 ''' </returns> ''' <remarks>2010-02-14</remarks> Public Function Connect(ByVal UserName As String, ByVal Password As String) As Integer Dim retVal As Integer '返回值 '连接 retVal = Connect(_host, _port, UserName, Password) '返回连接返回值 Return retVal End Function ''' <summary> ''' 连接取FTP ''' </summary> ''' <param name="Host">主机名或IP</param> ''' <param name="Port">端口号</param> ''' <param name="UserName">登录用用户名</param> ''' <param name="Password">登录用密码</param> ''' <returns> ''' 0 : 连接成功 ''' -1 : 连接失败 ''' </returns> ''' <remarks>2010-02-14</remarks> Public Function Connect( _ ByVal Host As String, _ ByVal Port As Integer, _ ByVal UserName As String, _ ByVal Password As String) As Integer Dim ipEPIns As IPEndPoint '网络端点实例 '初始化实例 _socketIns = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) ipEPIns = New IPEndPoint(Dns.GetHostAddresses(Host)(0), Port) '连接FTP _socketIns.Connect(ipEPIns) '读取信息,判断是否连接成功 ReadMessage() If _lastMessageID <> 220 Then Return -1 End If '设置连接超时时间 _socketIns.ReceiveTimeout = 60000 '发送用户名 SendCommand("USER " + UserName) If _lastMessageID <> 230 AndAlso _lastMessageID <> 331 Then DisConnect() Return -1 End If '发送密码 SendCommand("PASS " + Password) If _lastMessageID <> 230 AndAlso _lastMessageID <> 202 Then DisConnect() Return -1 End If '返回连接成功 Return 0 End Function ''' <summary> ''' 断开与FTP的连接 ''' </summary> ''' <remarks>2010-02-14</remarks> Public Sub DisConnect() Try '判断FTP的连接状态 '如果处于已连接的场合 If IsConnected() = True Then '发送退出命令 SendCommand("QUIT") '关闭连接 _socketIns.Close() End If Finally _socketIns = Nothing End Try End Sub ''' <summary> ''' 判断是否已连接FTP ''' </summary> ''' <returns> ''' True : 已连接 ''' False : 未连接 ''' </returns> ''' <remarks></remarks> Private Function IsConnected() As Boolean If _socketIns IsNot Nothing AndAlso _socketIns.Connected = True Then Return True End If Return False End Function ''' <summary> ''' 判断指定文件是否存在 ''' </summary> ''' <param name="FileName">文件名</param> ''' <returns> ''' 0 : 存在 ''' -1 : 不存在 ''' -2 : 读取失败 ''' -100 : FTP未连接 ''' </returns> ''' <remarks>2010-02-16</remarks> Public Function IsFileExists(ByVal FileName As String) As Integer Dim fileList() As String = Nothing '文件列表 Dim fileCount As Integer '文件总数 '判断连接状态 '如果状态是未连接的场合 If IsConnected() = False Then Return -100 End If '取得当前目录下的所有文件 fileCount = GetFileNameList(fileList) Select Case fileCount Case -100 Return -100 Case -1 Return -2 End Select '判断指定文件是否存在 For i As Integer = 0 To fileCount - 1 If fileList(i) = FileName Then '返回结果(存在) Return 0 End If Next i '返回结果(不存在) Return -1 End Function ''' <summary> ''' 取得FTP上当前目录下面所有文件 ''' </summary> ''' <param name="FileList">文件列表</param> ''' <returns> ''' >=0 : 文件个数 ''' -1 : 取得失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-14</remarks> Private Function GetFileList(ByRef FileList() As String) As Integer Dim dataSocket As Socket '读取数据用Socket Dim originalMessage As StringBuilder '原始信息 Dim byteLength As Integer '本次读取的字节长度 Dim buffer(BUFFER_SIZE) As Byte '用于存放已读数据的缓存 '判断连接状态 '如果状态是未连接的场合 If IsConnected() = False Then Return -100 End If '创建读取数据用Socket dataSocket = CreateDataSocket() If dataSocket Is Nothing Then Return -100 End If '发送LIST命令 SendCommand("LIST ") If _lastMessageID <> 150 AndAlso _lastMessageID <> 125 Then Return -1 End If '读取文件LIST originalMessage = New StringBuilder Do While True '接收数据 byteLength = dataSocket.Receive(buffer, buffer.Length, SocketFlags.None) originalMessage.Append(Encoding.Default.GetString(buffer, 0, byteLength)) '判断有没有接收完 '如果接收的数据大小小于规定数据的场合 If byteLength < buffer.Length Then Exit Do End If Loop '关闭读取数据用的Socket dataSocket.Close() '保存文件列表 FileList = Strings.Split(originalMessage.ToString, vbCrLf) ReDim Preserve FileList(FileList.GetUpperBound(0) - 1) '读取信息,判断是否取得成功 ReadMessage() If _lastMessageID <> 226 Then Return -1 End If '返回文件个数 Return FileList.Length End Function ''' <summary> ''' 取得FTP上当前目录下面所有文件名 ''' </summary> ''' <param name="FileNameList">文件名列表</param> ''' <returns> ''' >=0 : 文件个数 ''' -1 : 取得失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function GetFileNameList(ByRef FileNameList() As String) As Integer Const FILE_NAME_POSITION As Integer = 55 '文件名位置 Dim count As Integer '文件总数 Dim length As Integer '文件名长度 '取得文件列表 count = GetFileList(FileNameList) If count < 0 Then Return count End If '根据文件列表设置文件名列表 For i As Integer = 0 To count - 1 length = FileNameList(i).Length FileNameList(i) = FileNameList(i).Substring(FILE_NAME_POSITION) Next i '返回成功 Return count End Function ''' <summary> ''' 上传文件到FTP的当前目录 ''' </summary> ''' <param name="SourceFileName">需要上传的源文件</param> ''' <returns> ''' 0 : 上传成功 ''' -1 : 上传失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-16</remarks> Public Function Upload(ByVal SourceFileName As String) As Integer Dim dataSocket As Socket '写入数据用Socket Dim input As FileStream '输入文件流 Dim byteLength As Integer '读取的字节长度 Dim buffer(BUFFER_SIZE) As Byte '存放已读取的数据 '如果源文件不存在的场合 If File.Exists(SourceFileName) = False Then Return -1 End If '判断连接状态 '如果状态是未连接的场合 If IsConnected() = False Then Return -100 End If '创建写入数据用Socket dataSocket = CreateDataSocket() '发送上传命令 SendCommand("STOR " & SourceFileName) If _lastMessageID <> 125 AndAlso _lastMessageID <> 150 Then dataSocket.Close() Return -1 End If '打开源文件 input = New FileStream(SourceFileName, FileMode.Open) '读取上传源文件 Do While True '读取源文件中的部分数据 byteLength = input.Read(buffer, 0, buffer.Length) If byteLength <= 0 Then Exit Do End If '将读取到的数据发送到FTP dataSocket.Send(buffer, byteLength, SocketFlags.None) Loop '关闭源文件 input.Close() input.Dispose() '关闭写入数据用Socket dataSocket.Close() '读信息,判断是否上传成功 ReadMessage() If _lastMessageID <> 226 AndAlso _lastMessageID <> 250 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 上传文件到FTP的当前目录 ''' </summary> ''' <param name="Input">源文件流</param> ''' <returns> ''' 0 : 上传成功 ''' -1 : 上传失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function Upload(ByVal Input As FileStream) As Integer Dim dataSocket As Socket '写入数据用Socket Dim byteLength As Integer '读取的字节长度 Dim buffer(BUFFER_SIZE) As Byte '存放已读取的数据 '如果源文件流不存在或不能读的场合 If Input Is Nothing OrElse Input.CanRead = False Then Return -1 End If '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '创建写入数据用Socket dataSocket = CreateDataSocket() '读取上传源文件 Do While True '读取源文件中的部分数据 byteLength = Input.Read(buffer, 0, buffer.Length) If byteLength <= 0 Then Exit Do End If '将读取到的数据发送到FTP dataSocket.Send(buffer, byteLength, SocketFlags.None) Loop '关闭源文件 Input.Close() Input.Dispose() '关闭写入数据用Socket dataSocket.Close() '读信息,判断是否上传成功 ReadMessage() If _lastMessageID <> 226 AndAlso _lastMessageID <> 250 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 从FTP下载文件 ''' </summary> ''' <param name="SourceFileName">源文件</param> ''' <param name="DestFileName">目标文件</param> ''' <returns> ''' 0 : 上传成功 ''' -1 : 上传失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function Download(ByVal SourceFileName As String, ByVal DestFileName As String) As Integer Dim dataSocket As Socket '读取数据用Socket Dim output As FileStream '输出流 Dim retVal As Integer '返回值 Dim byteLength As Integer '读取的字节长度 Dim buffer(BUFFER_SIZE) As Byte '存放已读取的数据 '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '判断文件是否存在 retVal = IsFileExists(SourceFileName) If retVal < 0 Then Return retVal End If '设置数据类型为二进制类型 retVal = SetType(DataType.ASCII) If retVal < 0 Then Return -1 End If '如果目录文件不存在的场合 If DestFileName Is Nothing Or Equals(DestFileName.Trim, "") = True Then DestFileName = SourceFileName End If '如果目标文件不存在场合则创建路径 If File.Exists(DestFileName) = False Then If Directory.Exists(Path.GetDirectoryName(DestFileName)) = False Then Directory.CreateDirectory(Path.GetDirectoryName(DestFileName)) End If End If '打开或创建目标文件 output = New FileStream(DestFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite) '创建读取数据用Socket dataSocket = CreateDataSocket() If dataSocket Is Nothing Then output.Close() output.Dispose() File.Delete(DestFileName) Return -1 End If '发送下载命令 SendCommand("RETR " & SourceFileName) If _lastMessageID <> 150 AndAlso _lastMessageID <> 125 Then output.Close() output.Dispose() File.Delete(DestFileName) Return -1 End If '下载数据 Do While True '读取并写入数据 byteLength = dataSocket.Receive(buffer, buffer.Length, SocketFlags.None) output.Write(buffer, 0, byteLength) '如果没有读取到数据的场合则下载结束 If byteLength <= 0 Then Exit Do End If Loop '关闭流文件 output.Close() output.Dispose() '关闭读取数据用Socket dataSocket.Close() '读取信息,判断是否下载成功 ReadMessage() If _lastMessageID <> 226 AndAlso _lastMessageID <> 250 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 取得文件大小 ''' </summary> ''' <param name="FileName">文件</param> ''' <returns> ''' >=0 : 文件大小 ''' -1 : 取得失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function GetFileSize(ByVal FileName As String) As Long Dim size As Long '文件大小 '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '发送取得文件大小命令 SendCommand("SIZE " & FileName) '取得文件大小成功的场合 If _lastMessageID = 213 Then size = Long.Parse(_lastMessage.Substring(4)) '取得文件大小失败的场合 Else Return -1 End If '返回文件大小 Return size End Function ''' <summary> ''' 重新命名 ''' </summary> ''' <param name="OldName">旧的文件或目录名</param> ''' <param name="NewName">新的文件或目录名</param> ''' <returns> ''' 0 : 重新命名成功 ''' -1 : 重新命名失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function ReName(ByVal OldName As String, ByVal NewName As String) As Integer Dim retVal As Integer '返回值 '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '如果新文件或目录存在的场合或判断出错的场合 retVal = IsFileExists(NewName) Select Case retVal Case -100 Return -100 Case 0, -2 Return -1 End Select '发送旧的文件或目录名 SendCommand("RNFR " & OldName) If _lastMessageID <> 350 Then Return -1 End If '发送新的文件或目录名 SendCommand("RNTO " & NewName) If _lastMessageID <> 250 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 创建目录 ''' </summary> ''' <param name="DirectoryName">目录名</param> ''' <returns> ''' 0 : 创建目录成功 ''' -1 : 创建目录失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function CreateDirectory(ByVal DirectoryName As String) As Integer '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '发送创建目录命令 SendCommand("MKD " & DirectoryName) If _lastMessageID <> 257 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 删除目录 ''' </summary> ''' <param name="DirectoryName">目录名</param> ''' <returns> ''' 0 : 删除目录成功 ''' -1 : 删除目录失败 ''' -100 : FTP还未连接 ''' </returns> ''' <remarks>2010-02-17</remarks> Public Function RemovDirectory(ByVal DirectoryName As String) As Integer '如果连接状态是未连接的场合 If IsConnected() = False Then Return -100 End If '发送创建目录命令 SendCommand("RMD " & DirectoryName) If _lastMessageID <> 250 Then Return -1 End If '返回成功 Return 0 End Function ''' <summary> ''' 发送指定命令到FTP ''' </summary> ''' <param name="Command">要发送的命令</param> ''' <remarks>2010-02-14</remarks> Private Sub SendCommand(ByVal Command As String) Dim commandBtyes() As Byte '用于存放命令 '将命令转发成字节 commandBtyes = Encoding.Default.GetBytes((Command & vbCrLf).ToCharArray()) _socketIns.Send(commandBtyes, commandBtyes.Length, SocketFlags.None) '读取信息 ReadMessage() End Sub ''' <summary> ''' 读取信息 ''' </summary> ''' <remarks>2010-02-14</remarks> Private Sub ReadMessage() '取得信息内容,和信息ID _lastMessage = ReadLine() _lastMessageID = Int32.Parse(_lastMessage.Substring(0, 3)) End Sub ''' <summary> ''' 从FTP读取一行信息 ''' </summary> ''' <returns>读取到的信息</returns> ''' <remarks>2010-02-14</remarks> Private Function ReadLine() As String Dim originalMessage As StringBuilder '原始信息 Dim messages() As String '信息组 Dim message As String '需要返回的信息 Dim byteLength As Integer '本次读取的字节长度 Dim buffer(BUFFER_SIZE) As Byte '用于存放已读数据的缓存 '初始化 originalMessage = New StringBuilder message = "" Do While True '接收数据 byteLength = _socketIns.Receive(buffer, buffer.Length, SocketFlags.None) originalMessage.Append(Encoding.Default.GetString(buffer, 0, byteLength)) '判断有没有接收完 '如果接收的数据大小小于规定数据的场合 If byteLength < buffer.Length Then Exit Do End If Loop '根据空格分割信息,并取得相应的信息 'messages = originalMessage.ToString.Split(SEPERATOR) messages = Strings.Split(originalMessage.ToString, vbCrLf) If messages.Length > 2 Then message = messages(messages.Length - 2) Else message = messages(0) End If ''''上面已按空格进行分割,为什么这里还要判断“空格”??? If message.Substring(3, 1).Equals(" ") = False Then 'message = ReadLine End If '返回信息内容 Return message End Function ''' <summary> ''' 创建访问文件用Socket实例 ''' </summary> ''' <returns>已经创建的Socket实例,如果连接FTP失败则为Nothing</returns> ''' <remarks>2010-02-14</remarks> Private Function CreateDataSocket() As Socket Dim dataSocketIns As Socket '需要返回的Socket Dim ipEPIns As IPEndPoint '网络端点实例 Dim ipData As String 'IP地址数据 Dim ipDatas() As String 'IP地址数据 Dim ipAddress As String 'IP地址 Dim position1 As Integer '左括号位置 Dim position2 As Integer '右括号位置 Dim port As Integer '端口号 '发“PASV”送命令,请求FTP服务器并等待连接 SendCommand("PASV") If _lastMessageID <> 227 Then Return Nothing End If '取得括号位置 position1 = _lastMessage.IndexOf("(") position2 = _lastMessage.IndexOf(")") '取得IP地址数据 ipData = _lastMessage.Substring(position1 + 1, position2 - position1 - 1) ipDatas = ipData.Split(","c) '设置IP地址和端口号 ipAddress = ipDatas(0) & "." & ipDatas(1) & "." & ipDatas(2) & "." & ipDatas(3) port = (Integer.Parse(ipDatas(4)) << 8) + Integer.Parse(ipDatas(5)) '初始化返回用的Socket dataSocketIns = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) dataSocketIns.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000) ipEPIns = New IPEndPoint(Dns.GetHostAddresses(ipAddress)(0), port) '连接FTP,如果连接失败则返回Nothing Try dataSocketIns.Connect(ipEPIns) Catch Return Nothing End Try '返回Socket Return dataSocketIns End Function ''' <summary> ''' 设置数据类型 ''' </summary> ''' <param name="type">数据类型</param> ''' <returns> ''' 0 : 设置成功 ''' -1 : 设置失败 ''' </returns> ''' <remarks>2010-02-17</remarks> Private Function SetType(ByVal type As DataType) As Integer '设置数据类型 Select Case type Case DataType.ASCII SendCommand("TYPE A") Case DataType.BINARY SendCommand("TYPE I") Case DataType.EBCDIC SendCommand("TYPE E") Case Else SendCommand("TYPE A") End Select '设置失败的场合 If _lastMessageID <> 200 Then Return -1 End If '返回成功 Return 0 End Function #End Region End Class