JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

Uploads

The file uploading and downloading logic represents the heart of the FileSwapper application. The user needs the ability not only to perform both of these operations at the same time, but also to serve multiple upload requests or download multiple files in parallel. To accommodate this requirement, we must use a two-stage design, in which one class is responsible for creating new upload or download objects as needed. In the case of an upload, this is the FileServer class. The FileServer waits for requests and creates a FileUpload object for each new file upload. The diagrams in Figure 9-7 show how the FileServer and FileUpload classes interact.

Click To expand
Figure 9-7: The uploading process

The FileServer Class

The FileServer class listens for connection requests on the defined port using a TcpListener. It follows the same pattern as the asynchronous Search class:

  • The thread used to monitor the port is stored in a private member variable.

  • The thread is created with a call to StartWaitForRequest(), and aborted with a call to Abort(). The actual monitoring code exists in the WaitForRequest() method.

  • The ListView that tracks uploads is stored in a private member variable.

This framework is shown in the following code listing. One of the differences you'll notice is that an additional member variable is used to track individual upload threads. The Abort() method doesn't just stop the thread that's waiting for connection requests—it also aborts all the threads that are currently transferring files.

Public Class FileServer

    ' The thread where the port is being monitored.
    Private WaitForRequestThread As System.Threading.Thread

    ' The TcpListener used to monitor the port.
    Private Listener As TcpListener

    ' The ListView that tracks current uploads.
    Private ListView As ListView

    ' The current state.
    Private _Working As Boolean
    Public ReadOnly Property Working() As Boolean
        Get
            Return _Working
        End Get
    End Property

    ' The threads that are allocated to transfer files.
    Private UploadThreads As New ArrayList()

    Public Sub New(ByVal linkedControl As ListView)
        ListView = linkedControl
    End Sub

    Public Sub StartWaitForRequest()
        If _Working Then
            Throw New ApplicationException("Already in progress.")
        Else
            _Working = True

            WaitForRequestThread = New Threading.Thread(AddressOf WaitForRequest)
            WaitForRequestThread.Start()
        End If
    End Sub

    Public Sub Abort()
        If _Working Then
            Listener.Stop()

            WaitForRequestThread.Abort()

            ' Abort all upload threads.
            Dim UploadThread As FileUpload
            For Each UploadThread In UploadThreads
                UploadThread.Abort()
            Next

            _Working = False
        End If
    End Sub

    Public Sub WaitForRequest()
        ' (Code omitted.)
    End Sub

End Class

The WaitForRequest() method contains some more interesting code. First, it instantiates a TcpListener object and invokes the AcceptTcpClient() method, blocking the thread until it receives a connection request. Once a connection request is received, the code creates a new FileUpload object, starts it, and adds the FileUpload object to the UploadThreads collection.

The WaitForRequest() code doesn't create threads indiscriminately, however. Instead, it examines the Global.MaxUploadThreads setting to determine how many upload threads can exist at any one time. If there's already that number of items in the UploadThreads collection, new requests will receive a busy message instructing them to try again later. The connection will be closed and no new FileUpload object will be created. To ensure that the server is always ready to serve new clients, it automatically scans the UploadThreads collection for objects that have finished processing every time it receives a request. Once it removes these, it decides whether the new request can be accommodated.

Tip 

.NET is quite efficient when destroying and creating new threads. However, you could optimize performance even further by reusing upload and download threads and maintaining a thread pool, rather than by creating new threads. One way to do this is to use the ThreadPool class that was introduced in Chapter 5.

Public Sub WaitForRequest()

    Listener = New TcpListener(Global.Settings.Port)
    Listener.Start()
    Do

        ' Block until connection received.
        Dim Client As TcpClient = Listener.AcceptTcpClient()

        ' Check for completed requests.
        ' This will free up space for new requests.
        Dim UploadThread As FileUpload
        Dim i As Integer
        For i = (UploadThreads.Count - 1) To 0 Step -1

            UploadThread = CType(UploadThreads(i), FileUpload)
            If UploadThread.Working = False Then
                UploadThreads.Remove(UploadThread)
            End If
        Next
        Try
            Dim s As NetworkStream = Client.GetStream()
            Dim w As New BinaryWriter(s)
            If UploadThreads.Count > Global.Settings.MaxUploadThreads Then
                w.Write(Messages.Busy)
                s.Close()
            Else
                w.Write(Messages.Ok)
                Dim Upload As New FileUpload(s, ListView)
                UploadThreads.Add(Upload)
                Upload.StartUpload()
            End If
        Catch Err As Exception
            ' Errors are logged for future reference, but ignored, so that the
            ' peer can continue serving clients.
            Trace.Write(Err.ToString())
        End Try

    Loop

End Sub

FileSwapper peers communicate using simple string messages. A peer requests a file for downloading by submitting its GUID. The server responds with a string "OK" or "BUSY" depending on its state. These values are written to the stream using the BinaryWriter. To ensure that the correct values are always used, they aren't hard-coded in the WaitForRequest() method, but defined as constants in a class named Messages. As you can see from the following code listing, FileSwapper peers only support a very limited vocabulary.

Public Class Messages

    ' The server will respond to the request.
    Public Const Ok = "OK"

    ' The server has reached its upload limit. Try again later.
    Public Const Busy = "BUSY"

    ' The requested file isn't in the shared collection.
    Public Const FileNotFound = -1

End Class

The FileUpload Class

The FileUpload class uses the same thread-wrapping design as the FileServer and Search classes. The actual file transfer is performed by the Upload() method. This method is launched asynchronously when the FileServer calls the StartUpload() method and canceled if the FileServer calls Abort(). A reference is maintained to the ListView control with the upload listings in order to provide real-time progress information.

Public Class FileUpload

    ' The thread where the file transfer takes place.
    Private UploadThread As System.Threading.Thread

    ' The underlying network stream.
    Private Stream As NetworkStream

    ' The current state.
    Private _Working As Boolean
    Public ReadOnly Property Working() As Boolean
        Get
            Return _Working
        End Get
    End Property

    ' The ListView where results are recorded.
    Private ListView As ListView

    Public Sub New(ByVal stream As NetworkStream, ByVal listView As ListView)
        Me.Stream = stream
        Me.ListView = listView
    End Sub

    Public Sub StartUpload()
        If _Working Then
            Throw New ApplicationException("Already in progress.")
        Else
            _Working = True
            UploadThread = New Threading.Thread(AddressOf Upload)
            UploadThread.Start()
        End If
    End Sub
    Public Sub Abort()
        If _Working Then
            UploadThread.Abort()
            _Working = False
        End If
    End Sub

    Private Sub Upload()
        ' (Code omitted)
    End Sub

End Class

We'll dissect the code in the Upload() method piece by piece. The first task the Upload() method undertakes is to create a BinaryWriter and BinaryReader for the stream, and then it reads the GUID of the requested file into a string.

' Connect.
Dim w As New BinaryWriter(Stream)
Dim r As New BinaryReader(Stream)

' Read file request.
Dim FileRequest As String = r.ReadString()

It then walks through the collection of shared files, until it finds the matching GUID.

Dim File As SharedFile
Dim Filename
For Each File In Global.SharedFiles
    If File.Guid.ToString() = FileRequest Then
        Filename = File.FileName
        Exit For
    End If
Next
Tip 

Download requests use a GUID instead of a file name. This design allows you to enhance the FileSwapper program to allow sharing in multiple directories, in which case the file name may no longer be unique. The GUID approach also makes it easy to validate a user request before starting a transfer. This is a key step, which prevents a malicious client from trying to trick a FileSwapper peer into downloading a sensitive file that it isn't sharing.

If the file isn't found in the collection of shared files, the message constant for file not found (-1) is written to the network stream, and no further action is taken.

' Check file is shared.
If Filename = "" Then
    w.Write(Messages.FileNotFound)

If the file is found, a new ListViewItem is added to the upload display, using a helper class named ListViewItemWrapper. The ListViewItemWrapper handles the logic needed to create the ListViewItem and change the status text in a thread-safe manner, by marshaling these operations to the correct thread.

Else
    ' Create ListView.
    Dim ListViewItem As New ListViewItemWrapper(ListView, Filename, _
      "Initializing")

The next step is to open the file and write the file size (in bytes) to the network stream. This information allows the remote peer to determine progress information while downloading the file.

    Try
        ' Open file.
        Dim Upload As New FileInfo(Path.Combine(Global.Settings.SharePath, _
          Filename))

        ' Read file.
        Dim TotalBytes As Integer = Upload.Length
        w.Write(TotalBytes)

Next, the file is opened, and the data is written to the network stream 1KB at a time. The ListViewItem.ChangeStatus method is used to update the status display in the loop, but a time limit is used to ensure that no more than one update is made every second. This reduces on-screen flicker for fast downloads.

        Dim TotalBytesRead, BytesRead As Integer

        Dim fs As FileStream = Upload.OpenRead()
        Dim Buffer(1024) As Byte
        Dim Percent As Single
        Dim LastWrite As DateTime = DateTime.MinValue
        Do
            ' Write a chunk of bytes.
            BytesRead = fs.Read(Buffer, 0, Buffer.Length)
            w.Write(Buffer, 0, BytesRead)
            TotalBytesRead += BytesRead

            ' Update the display once every second.
            If DateTime.Now.Subtract(LastWrite).TotalSeconds > 1 Then
                Percent = Math.Round((TotalBytesRead / TotalBytes) * 100, 0)
                LastWrite = DateTime.Now
                ListViewItem.ChangeStatus(Percent.ToString() & "% transferred")
            End If
        Loop While BytesRead > 0

        fs.Close()
        ListViewItem.ChangeStatus("Completed")

    Catch Err As Exception
        Trace.Write(Err.ToString)
        ListViewItem.ChangeStatus("Error")
    End Try

End If

Stream.Close()
_Working = False

In this case, the client simply disconnects when it stops receiving data and notices that the connection has been severed. Alternatively, you could use a special signal (such as a specific byte sequence) to indicate that the file is complete or, more practically, you could precede every 1KB chunk with an additional byte describing the status (last chunk, more to come, and so on). The client would have to remove this byte before writing the data to the file.

Figure 9-8 shows the upload status list with three entries. Two uploads have completed, while one is in progress.

Click To expand
Figure 9-8: FileSwapper uploads

Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor


R7