Write an FTP Client with VB.NET to Bridge Legacy Software, Page 2
Implementing 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.)
Connecting to an FTP Server
To connect to an FTP server, you need to know three things:
- The IP Address or URL of the host
- The port
- The protocol
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).
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.
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.
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 = "email@example.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
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.
Reading Server Response
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
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
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.