http://www.developer.com/net/vb/article.php/3424121/Write-an-FTP-Client-with-VBNET-to-Bridge-Legacy-Software.htm
In this article, I begin an implementation of an FTP client in managed code. (The basis for this article was a previously published knowledge base article from Microsoft.com, knowledge base 832670, How to Access a File Transfer Protocol Site by Using Visual Basic .NET. The code is uniquely my own, but some snippets were mined from that article.) While the implementation is not complete, the article is a good starting point that provides enough information to enable you to write a complete implementation. Why might you want to write an FTP client in the 21st century? Simple: Many of the largest companies in the world are still using 20th century code. Best-in-class businesses use dated technology because their business processes are dependent on legacy systems that would be too costly, too labor-intensive, and too risky to migrate all at once. Thus, many of these companies are bridging the old and new in stages using intermediate techniques like FTP-ing data for batch processing. At such companies, if a business-to-business transaction takes hours or days, a legacy system is in the way. In fact, if whatever you need cannot be done "while you wait," data more than likely is being FTP-ed, batched, or evaluated by a person instead of a machine, and manual steps or legacy systems are in the way. Lost luggage, checks being held, and a latency between the application of a loan and its approval are all examples of dated processes and dated software. Why is this important to know? Simple: Many mega-software systems need to be migrated, and in many instances this migration has only just begun. This makes TCP and socket programming skills useful and valuable, and it also means that much work remains to be done in the field of software development. The File Transfer Protocol (FTP) is a TCP protocol that is described by RFC (Request for Comment) 959. RFCs are basically white papers and every RFC that I have searched for has been posted somewhere on the Internet. This article borrows a few snippets from the aforementioned Microsoft.com knowledge base article, as well as knowledge base article 318380 and RFC 959. I encourage you to look these up, if for no other reason than to familiarize yourself with how to find them. What you won't find in this article is low-level TCP programming or low-level socket or RS232 (serial port) programming. The .NET framework makes many of these skills superfluous unless you are a socket or RS232 programmer. Writing an FTP client is relatively easy with .NET. To write an FTP client, you use the System.Net.Socket namespace, an instance of the Socket class, and an instance of an IPEndPoint. The rest of the code is simply figuring out what to do with the data the FTP server sends back. Because RFC 959 guides the data an FTP server returns, you also pretty much know what the data should be and what the data means. The hardest—though this word seems almost inappropriate—part then is to translate raw codes and text into meaningful behaviors. This article focuses on converting FTP server data into a useful FTP client library. As is true with a lot of programming, this exercise requires a little prep-work. (If you are a Windows pro, you can skip to the section "Implementing the FTP Client.") I prefer to test everything possible on my workstation and the same is true with an FTP client. The word client implies that there is a server, but you don't need to write an FTP server. You need to just install the one that ships with Windows. Installing the Microsoft FTP server on your workstation and then turning it on is all you need to do to prepare, write, and test your client. First, verify that the FTP server is installed on your workstation. To do this, follow either one of the following set of steps: Figure 1: An FTP Service Is Running on Your PC If You Get This Response. The following are the alternate steps for verifying an FTP service: Figure 2: Use Internet Explorer to See If the FTP Service Is Running on Your PC. The default FTP folder is c:\wwwroot\ftproot. You can locate this folder and add some files to experiment with if you'd like. If you receive an error, the service may be installed but not running. To check to see whether the service is installed and stopped, follow these steps: Figure 3: The Computer Management Console Will Show a Running FTP Site If the Service Is Installed and Running. The state you want is Running. If the Default FTP Site is present but its state is Stopped or Paused, click Default FTP Site, right-click, and click Play. If the service is not installed, you need to install it. (By default, the FTP service is no longer installed because it can open security holes, but you are a cowboy developer unafraid of hackers.) To install the FTP service, follow these steps: The last step installs the FTP Service. If the Windows setup files have been copied from your CD, the FTP Service will be installed from your hard drive. If not, you are prompted for your Windows CD-ROM. That's it. You are now ready to write and test the FTP client. Now that you have verified that we have an FTP server to test your client with, you can begin writing the FTP client. The partial client in this example involves creating a socket and an IPEndPoint, connecting to and disconnecting from the server, and authenticating. You can implement everything else, such as obtaining a file list or transferring files, by building on the example here. (In an upcoming article, I will implement a Windows Forms presentation layer and implement additional FTP features.) To connect to an FTP server, you need to know three things: FTP is a TCP service (that's your protocol). FTP uses port 21 for command and port 20 for data (Port 21 is your port). Because you are testing on your own workstation, your IP address is 127.0.0.1 (equivalent to the URL localhost). Knowing which server you want to connect to leaves just a few lines of code to write. In addition to writing the Connect behavior, you need to import some namespaces to make referring to sockets and endpoints shorter, and you can add fields and constructors to hold information about the server to which you would like to connect. Listing 1 contains the class stub, imports statement, a constructor, and the Connect method. Listing 1: The Class Stub and Connect Method. You can look up information about each of the imported namespaces in the help documentation. The fields represent information about the server, an instance of the Socket class, and a field to hold data you received from the FTP server. The parameterized constructor (Sub New) initializes the server fields. You also provide default values for testing. (If you implemented an IconfigSectionHandler, then you could code the default constructor to read the server fields from a .config file.) The method you want is Connect. Connect creates an instance of a socket. The parameters you use are needed for FTP, but you can use the same socket class to connect using other protocols like UDP (User Datagram Protocol) or IPX (Internetwork Packet Exchange). (UDP is commonly used in games to send data without waiting for a response, and IPX is an older Novell netware protocol that is also commonly used in older multiplayer games and with Novell networks.) Next, you need an IPEndPoint, which represents an IP address and port. (All of this information is easily accessible in the integrated help documentation and online, so I will leave it to you to explore further.) Finally, you call Socket.Connect, passing the endpoint. If you receive an exception, something went wrong. Otherwise, you are ready to start sending and receiving data, which you implement as ReadResponse in your FTP client. For each interaction with the server, you get a response. The response comes back in the form of a string. In general, the string contains a three-digit code and some text, and for FTP requests such as GET you might get some data such as the requested file. You implement ReadResponse to try to read a complete response 512 bytes at a time and convert this response into an event. ReadResponse is implemented as a method that invokes a second method called ReadLine and then broadcasts the response to any listeners. Listeners are instances of event handlers. Because the data from the server may come back in multi-line responses, ReadLine can recur. Add the three methods in Listing 2 to the FTPClient class from Listing 1. Listing 2: Reading Responses from the FTP Server When you constructed the Socket, you initialized it as a two-way byte stream with the argument SocketType.Stream. The main read loop—the While..End While loop—receives up to 512 bytes at a time. If the bytes read are less than 512, you stop reading. If more bytes are present, indicated loosely by the somewhat arbitrary presence of a space, we recurse; otherwise, we return the response to ReadResponse. ReadResponse calls a method named BroadcastResponse. To make the client easier to use, BroadcastResponse converts the arguments into a typed event object and raises an event. Remember, because you are writing a client, you don't know what the user may want to do with the data—you just know what the data means. This is an ideal situation for a delegate (also known as an event). Listing 3 shows BroadcastResponse. Because BroadcastResponse reads the FTP code from the server and raises an event, you need to define these event types (as delegates) and the event argument classes that will contain the desired data. Listing 4 contains the complete listing for the FTP Client with the events you have elected to implement, useful constants, delegates, and event argument classes. (The complete listing is provided for convenience). Listing 3: The Implementation of BroadcastResponse Listing 4: The entire listing for the FTP client includinged delegates, event fields, and event agrument classes. BroadcastResponse works the same for all responses: basically, I create an EventArgs-based class for the kind of arguments I think the user might want and a delegate that uses that type. The FtpClient exposes a public event and consumers can subscribe to the events they want. You can use the same technique to support the remaining FTP behaviors. If you are unfamiliar with delegates and events then you may have to practice a little. A delegate is like a C or C++ function pointer. In .NET, a delegate is a special class that encapsulates a multicast delegate or a list of function pointers, and it is the mechanism that supports attaching event handlers to events. (This is an instance where the framework uses the Observer design pattern.) Next, you need to test the client. A simple test is creating an instance of the client, attaching some event handlers to the FtpClient's public events, and invoking some methods. A great way to test a class library like the FtpClient is with NUnit, but I use a simple console application here (see Listing 5). Listing 5: A Console Application That Tests Some Capabilities of the FtpClient Before running the console application, make sure you add a reference to the FtpClient class library and import the FTP namespace. The sample test program tests and responds to the OnConnectionEvent, OnRawDataReceivedEvent, OnAuthenticationEvent, and invokes the Connect, Login, and Disconnect methods. If everything is working, running the console application should produce output that looks something like Figure 4. Figure 4: Output from Our Test Console Application The next step is implementing more of the features you need. Not all implementations need every FTP feature. For example, a general-purpose FTP client might need most of the FTP commands, but simply uploading data as part of a process may need only the ability to change directories and upload a file. Finally, for commercial or private business implementations, additional tests are helpful. For example, you might need to add exception handlers to respond to a timeout, an unavailable server or folder, or unexpected failures. The devil (and the time) are always in the details. This article provides a lot of juicy but pretty straightforward code. The most important thing to note is how relatively easy it is to use sockets in .NET. Create a socket, an IPEndPoint, know a little bit about the data you will be receiving from the other end point, and you are well on your way to writing connected software. This article exemplifies the most important thing about great frameworks: They make programming complex tasks much easier. Without needing to be an RS232, TCP, or sockets expert, one can get down to the business of coding a connected solution at a higher level of abstraction. Ultimately, the .NET framework brings complex solutions—whether in TCP or sockets programming, XML Web services, or advanced graphics programming—within everyone's grasp. Paul Kimmel is the VB Today columnist, has written several books on .NET programming, and is a software architect. You may contact him at pkimmel@softconcepts.com if you need assistance developing software or are interested in joining the Lansing Area .NET Users Group (glugnet.org). Copyright © 2004 by Paul Kimmel. All Rights Reserved.
Write an FTP Client with VB.NET to Bridge Legacy Software
October 20, 2004
Building an FTP Client with .NET
Preparation

Click here for a larger image.

Click here for a larger image.

Click here for a larger image.
Implementing the FTP Client
Connecting to an FTP Server
Note: In a commercial quality implementation, you would probably externalize the FTP configuration data. You can do this using a Web.config or App.config file and the appSettings section, or you can write an IConfigSectionHandler and create your own .config block. Using the <appSettings> is good enough, but using an IConfigSectionHandler is much cooler. Refer to my previous article, "Objectify an XML Node with an IConfigSectionHandler," for an example of implementing a custom XML section handler.
Imports System
Imports System.Net
Imports System.IO
Imports System.Text
Imports System.Text.ASCIIEncoding
Imports System.Net.Sockets
Imports System.Configuration
Imports System.Resources
Public Class FtpClient
Private FRemoteHost As String = "127.0.0.1"
Private FRemotePath As String = "."
Private FRemoteUser As String = "anonymous"
Private FRemotePassword As String = "anonymous@nowhere.com"
Private FRemotePort As Integer = 21
Private clientSocket As Socket = Nothing
Private response As String
Public Sub New()
End Sub
Public Sub New(ByVal remoteHost As String, ByVal remotePath _
As String, _
ByVal remoteUser As String, ByVal remotePassword As String, _
ByVal remotePort As Integer)
FRemoteHost = remoteHost
FRemotePath = remotePath
FRemoteUser = remoteUser
FRemotePassword = remotePassword
FRemotePort = remotePort
End Sub
Public Sub Connect()
clientSocket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Tcp)
Dim endpoint As IPEndPoint = _
New IPEndPoint(Dns.Resolve(FRemoteHost).AddressList(0), _
FRemotePort)
Try
clientSocket.Connect(endpoint)
Catch ex As Exception
Throw New IOException("Connect failed", ex)
End Try
ReadResponse()
End Sub
Reading Server Response
Private Sub ReadResponse()
Dim reply As String = ReadLine()
'Figure out the response and raise that event
BroadcastResponse(reply)
End Sub
Private Function ReadLine() As String
Return ReadLine(False)
End Function
Private Function ReadLine(ByVal clearResponse As Boolean) _
As String
Const EndLine As Char = "\n"
Const BUFFER_SIZE As Integer = 512
Dim data As String = ""
Dim buffer(BUFFER_SIZE) As Byte
Dim bytesRead As Integer = 0
If (clearResponse = True) Then response = String.Empty
While (True)
Array.Clear(buffer, 0, BUFFER_SIZE)
bytesRead = clientSocket.Receive(buffer, _
buffer.Length, 0)
data += ASCII.GetString(buffer, 0, bytesRead)
If (bytesRead < buffer.Length) Then Exit While
End While
Dim parts() As String = data.Split(EndLine)
If (parts.Length > 2) Then
response = parts(parts.Length - 2)
Else
response = parts(0)
End If
If (response.Substring(3, 1).Equals(" ") = False) Then
Return ReadLine(False)
End If
Return response
End Function
Converting a Text Response into a Typed Event
Private Sub BroadcastResponse(ByVal reply As String)
' Permit handling as raw data
RaiseEvent OnRawDataReceivedEvent(Me, _
New RawDataReceivedEventArgs(reply))
Dim code As Integer = Int32.Parse(reply.Substring(0, 3))
Dim data As String = reply.Substring(4)
' Permit special handling as code and data
RaiseEvent OnTcpEvent(Me, New TcpEventArgs(code, data))
' Specific handling based on code
Select Case code
Case SENDING_DATA_PORT_20
Case COMMAND_NOT_IMPLEMENTED
Case CONNECTED
RaiseEvent OnConnectionEvent(Me, _
New ConnectionEventArgs(True, code, data))
Case AUTHENTICATED
RaiseEvent OnAuthentication(Me, _
New AuthenticationEventArgs(True, code, data))
Case NEED_PASSWORD
Case AUTHENTICATION_FAILED
RaiseEvent OnAuthentication(Me, _
New AuthenticationEventArgs(False, code, data))
End Select
End Sub
Imports System
Imports System.Net
Imports System.IO
Imports System.Text
Imports System.Text.ASCIIEncoding
Imports System.Net.Sockets
Imports System.Configuration
Imports System.Resources
Public Class FtpClient
Public Shared ReadOnly SENDING_DATA_PORT_20 As Integer = 150
Public Shared ReadOnly COMMAND_NOT_IMPLEMENTED As Integer = 202
Public Shared ReadOnly CONNECTED As Integer = 220
Public Shared ReadOnly AUTHENTICATED As Integer = 230
Public Shared ReadOnly NEED_PASSWORD As Integer = 331
Public Shared ReadOnly AUTHENTICATION_FAILED As Integer = 530
Private FRemoteHost As String = "127.0.0.1"
Private FRemotePath As String = "."
Private FRemoteUser As String = "anonymous"
Private FRemotePassword As String = "anonymous@nowhere.com"
Private FRemotePort As Integer = 21
Private clientSocket As Socket = Nothing
Private response As String
Public Event OnAuthentication As AuthenticationEvent
Public Event OnConnectionEvent As ConnectionEvent
Public Event OnRawDataReceivedEvent As RawDataReceivedEvent
Public Event OnTcpEvent As TcpEvent
Public Sub New()
End Sub
Public Sub New(ByVal remoteHost As String, _
ByVal remotePath As String, _
ByVal remoteUser As String, ByVal remotePassword As String, _
ByVal remotePort As Integer)
FRemoteHost = remoteHost
FRemotePath = remotePath
FRemoteUser = remoteUser
FRemotePassword = remotePassword
FRemotePort = remotePort
End Sub
Public Sub Connect()
clientSocket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Tcp)
Dim endpoint As IPEndPoint = _
New IPEndPoint(Dns.Resolve(FRemoteHost).AddressList(0), _
FRemotePort)
Try
clientSocket.Connect(endpoint)
Catch ex As Exception
Throw New IOException("Connect failed", ex)
End Try
ReadResponse()
End Sub
Public Sub Disconnect()
If (clientSocket Is Nothing = False) Then
clientSocket.Disconnect(True)
RaiseEvent OnConnectionEvent(Me, _
New ConnectionEventArgs(False))
End If
End Sub
Private Sub ReadResponse()
Dim reply As String = ReadLine()
'Figure out the response and raise that event
BroadcastResponse(reply)
End Sub
Private Function ReadLine() As String
Return ReadLine(False)
End Function
Private Function ReadLine(ByVal clearResponse As Boolean) _
As String
Const EndLine As Char = "\n"
Const BUFFER_SIZE As Integer = 512
Dim data As String = ""
Dim buffer(BUFFER_SIZE) As Byte
Dim bytesRead As Integer = 0
If (clearResponse = True) Then response = String.Empty
While (True)
Array.Clear(buffer, 0, BUFFER_SIZE)
bytesRead = clientSocket.Receive(buffer, buffer.Length, 0)
data += ASCII.GetString(buffer, 0, bytesRead)
If (bytesRead < buffer.Length) Then Exit While
End While
Dim parts() As String = data.Split(EndLine)
If (parts.Length > 2) Then
response = parts(parts.Length - 2)
Else
response = parts(0)
End If
If (response.Substring(3, 1).Equals(" ") = False) Then
Return ReadLine(False)
End If
Return response
End Function
Private Sub BroadcastResponse(ByVal reply As String)
' Permit handling as raw data
RaiseEvent OnRawDataReceivedEvent(Me, _
New RawDataReceivedEventArgs(reply))
Dim code As Integer = Int32.Parse(reply.Substring(0, 3))
Dim data As String = reply.Substring(4)
' Permit special handling as code and data
RaiseEvent OnTcpEvent(Me, New TcpEventArgs(code, data))
' Specific handling based on code
Select Case code
Case SENDING_DATA_PORT_20
Case COMMAND_NOT_IMPLEMENTED
Case CONNECTED
RaiseEvent OnConnectionEvent(Me, _
New ConnectionEventArgs(True, code, data))
Case AUTHENTICATED
RaiseEvent OnAuthentication(Me, New _
AuthenticationEventArgs(True, code, data))
Case NEED_PASSWORD
Case AUTHENTICATION_FAILED
RaiseEvent OnAuthentication(Me, _
New AuthenticationEventArgs(False, code, data))
End Select
End Sub
Public Sub Login()
SendCommand("USER " + FRemoteUser)
SendCommand("PASS " + FRemotePassword)
End Sub
Public Sub Login(ByVal user As String, ByVal password As String)
FRemotePassword = password
FRemoteUser = user
Login()
End Sub
Private Sub SendCommand(ByVal command As String)
command += Environment.NewLine
Dim commandBytes() As Byte = ASCII.GetBytes(command)
clientSocket.Send(commandBytes, commandBytes.Length, 0)
ReadResponse()
End Sub
End Class
Public Class TcpEventArgs
Private FCode As Integer
Private FData As String
Public Sub New(ByVal code As Integer, ByVal data As String)
FCode = code
FData = data
End Sub
Public ReadOnly Property Code() As Integer
Get
Return FCode
End Get
End Property
Public ReadOnly Property Data() As String
Get
Return FData
End Get
End Property
End Class
Public Class ConnectionEventArgs
Inherits TcpEventArgs
Private FConnected As Boolean = False
Public Sub New(ByVal connected As Boolean)
MyBase.New(-1, "")
FConnected = connected
End Sub
Public Sub New(ByVal connected As Boolean, _
ByVal code As Integer, ByVal data As String)
MyBase.New(code, data)
FConnected = connected
End Sub
Public ReadOnly Property Connected() As Boolean
Get
Return FConnected
End Get
End Property
End Class
Public Class AuthenticationEventArgs
Inherits TcpEventArgs
Private FAuthenticated As Boolean = False
Public Sub New(ByVal authenticated As Boolean)
MyBase.New(-1, "")
FAuthenticated = authenticated
End Sub
Public Sub New(ByVal authenticated As Boolean, _
ByVal code As Integer, ByVal data As String)
MyBase.New(code, data)
FAuthenticated = authenticated
End Sub
Public ReadOnly Property Authenticated() As Boolean
Get
Return FAuthenticated
End Get
End Property
End Class
Public Class RawDataReceivedEventArgs
Inherits TcpEventArgs
Private FRawData As String
Public Sub New(ByVal rawData As String)
MyBase.New(-1, "")
FRawData = rawData
End Sub
Public Sub New(ByVal rawData As String, _
ByVal code As Integer, ByVal data As String)
MyBase.New(code, data)
FRawData = rawData
End Sub
Public ReadOnly Property RawData() As String
Get
Return FRawData
End Get
End Property
End Class
Public Delegate Sub AuthenticationEvent(ByVal sender As Object, _
ByVal e As AuthenticationEventArgs)
Public Delegate Sub ConnectionEvent(ByVal sender As Object, _
ByVal e As ConnectionEventArgs)
Public Delegate Sub RawDataReceivedEvent(ByVal sender As Object, _
ByVal e As RawDataReceivedEventArgs)
Public Delegate Sub TcpEvent(ByVal sender As Object, _
ByVal e As TcpEventArgs)
Testing the FTP Client
Imports Ftp
Module Module1
Sub Main()
Dim client As FtpClient = New FtpClient()
AddHandler client.OnConnectionEvent, _
AddressOf OnConnection
AddHandler client.onRawDataReceivedEvent, _
AddressOf OnRawDataReceived
AddHandler client.OnAuthentication, _
AddressOf OnAuthenticated
client.Connect()
client.Login()
Console.WriteLine("press enter to exit")
Console.ReadLine()
client.Disconnect()
End Sub
Public Sub OnConnection(ByVal sender As Object, _
ByVal e As ConnectionEventArgs)
Console.WriteLine("Connected: " & e.Connected.ToString)
End Sub
Public Sub OnRawDataReceived(ByVal sender As Object, _
ByVal e As RawDataReceivedEventArgs)
Console.WriteLine("Received: " & e.RawData)
End Sub
Public Sub OnAuthenticated(ByVal sender As Object, _
ByVal e As AuthenticationEventArgs)
Console.WriteLine("Logged in: " + e.Authenticated.ToString())
End Sub
End Module

Click here for a larger image.
Writing Connected Software Simply
Biography