Link to home
Start Free TrialLog in
Avatar of Lycaon
Lycaon

asked on

Seeking complete asynchronous, thread-safe TCP/UDP socket implementation for VB.NET

I am currently migrating to VB.NET, and have converted all of my VB6 projects to VB.NET, however, I would like to get away from the dependency on the old winsock library and go to full framework dependency.  My problem is, the Sockets namespace in general is driving me nuts!  Instead of creating an array of Winsock controls and dealing with them, I now have to consider thread safety, etc.

I have spent the last 18 hours scouring the net for either instructions, or an actual class that mimics the Winsock control as closely as possible, and have found none that encompass both thread-safety and asynchronous callbacks.

The info / class I'm looking for needs to include:

Proper use of creating a thread-safe method of creating an asynchronous 'listen' and 'connect' connection, including data retrieval/closing
Proper implementation of a thread-safe Event for both of these.

As an example, I am porting a VB6 game server to VB.NET, and need the server to be able to handle multiple connections as it did in VB6, by having one Winsock control listen for connections, then handing off the RequestID to a separate array of Winsock controls.

I have not had time to experiment with UDP yet, but I'm assuming it's similar to TCP, but I'd like information on doing this asynchronously as well.

I will increase points for more detailed information, up to 500 for a complete class.  It'd be worth a lot more to me, but unfortunately I've noticed that a cap has been placed on points awarded.  Please note that, in the interest of rewarding the person who has helped me out the most, I'd like to refrain from splitting points, so if it's just a small code snippet here or there, or a URL to a site that doesn't provide more than what I am looking for, I'll avoid considering the comment as an answer.  Sorry guys, but limiting the points a guy can reward limits the people he can reward. :\
Avatar of checoo
checoo

To start with you need to use the TCPListen and TCPClient, and to make it thread safe you need to use a Synclock. Start the listening process on a seperate process, look at the samples below --

Server
=====

Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Collections

Public Class SocketServer

    Private ClientCol As Hashtable
    Private objThread As Thread
    Private objListner As TcpListener
    Private intListeningPort As Integer

    Public Sub New(ByVal intPort As Integer)
        ClientCol = New Hashtable
        intListeningPort = intPort
        objThread = New Thread(AddressOf DoListen)
        objThread.Start()
    End Sub

    Private Sub DoListen()
        Dim newClient As Client
        Try
            objListner = New TcpListener(IPAddress.Any, intListeningPort)
            objListner.Start()
aaa:        Do
                newClient = New Client(objListner.AcceptTcpClient, strConnectionString)
                ClientCol.Add(newClient.ClientID, newClient)
                AddHandler newClient.Connected, AddressOf onConnected
                AddHandler newClient.Disconnected, AddressOf onDisconected
                AddHandler newClient.DataReceived, AddressOf onDataReceived
            Loop Until False
        Catch e As Exception
            GoTo aaa
        End Try
    End Sub

    Private Sub onConnected(ByVal Sender As Client)
        ClientCol.Add(Sender.ClientID, Sender)
    End Sub

    Private Sub onDisconected(ByVal Sender As Client)
        Sender.DisposeClient()
        ClientCol.Remove(Sender.ClientID)
    End Sub

    Private Sub onDataReceived(ByVal Sender As Client, ByVal strData As String)
        Try
            'write code to process the inputs
            Sender.Send(strResponseString)
        Catch e As Exception
            Sender.Send(e.Message.ToString)
        End Try
    End Sub

    Public Sub DisposeServer()
       'clean up code
    End Sub

End Class

Client
====

Imports System.Net.Sockets
Imports System.Text

Public Class Client
    Public Event Connected(ByVal sender As Client)
    Public Event Disconnected(ByVal sender As Client)
    Public Event DataReceived(ByVal sender As Client, ByVal strData As String)

    Private myID As Guid
    Private objClient As TcpClient
    Private bClientData(1024) As Byte
    Private objText As New StringBuilder

    Public Sub New(ByVal Client As TcpClient, ByVal strConn As String)
        Dim strResponse As String
        objClient = Client
        objClient.NoDelay = True
        myID = Guid.NewGuid
    End Sub

    Public ReadOnly Property ClientID() As String
        Get
            Return myID.ToString
        End Get
    End Property

    Private Sub ReceiveClientData(ByVal asyncResult As IAsyncResult)
        Dim intcount As Integer
        Try
            SyncLock objClient.GetStream
                intcount = objClient.GetStream.EndRead(asyncResult)
            End SyncLock

            If intcount < 1 Then
                RaiseEvent Disconnected(Me)
                Exit Sub
            End If

            ByteToString(bClientData, intcount)

            SyncLock objClient.GetStream
                objClient.GetStream.BeginRead(bClientData, 0, 1024, AddressOf ReceiveClientData, Nothing)
            End SyncLock

        Catch e As Exception
            RaiseEvent Disconnected(Me)
        End Try
    End Sub

    Private Sub ByteToString(ByVal Bytes() As Byte, ByVal intCount As Integer)
        Dim intIndex As Integer
        For intIndex = 0 To intCount - 1
            If Bytes(intIndex) = 94 Then
                RaiseEvent DataReceived(Me, objText.ToString)
                objText = New StringBuilder
            Else
                objText.Append(ChrW(Bytes(intIndex)))
            End If
        Next
    End Sub

    Public Sub Send(ByVal strData As String)
        SyncLock objClient.GetStream
            Dim wri As New IO.StreamWriter(objClient.GetStream)
            wri.Write(strData)
            wri.Flush()
        End SyncLock
        If blnOracleError Then
            blnOracleError = False
            objconn = Nothing
            RaiseEvent Disconnected(Me)
        End If
    End Sub

    Public Sub Dispose()
      'Clean up code here
    End Sub
End Class
Avatar of Lycaon

ASKER

And that's all there is to an all async, thread-safe server and client?  None of the server sockets are goingto block the main thread, or the threads of the other sockets?
I have used a code piece quite similar to the one i have put here, and so far i have not faced any problem with it.
Avatar of Lycaon

ASKER

Alrighty, let me give it a go and I'll accept your answer soon.
Avatar of Lycaon

ASKER

checco:

I was hoping for a bit more generic / reusable solution.  For example, in the OnDataReceived sub in the server class, there is the line:

Sender.Send(strResponseString)

I'd rather this raise an event so that I can a) handle the data in whatever module I declared the Server class in, and b) be able to reuse the server/client classes without having to edit that particular sub each time (which kind of ties in with a)

Feel like revising at all?  I'm still willing to increase points for better code.
a) handle the data in whatever module I declared the Server class in

you can declare a public event in client and raise that event in the send method. Have a handler for that event in whatever module you declared the server.

b) be able to reuse the server/client classes without having to edit that particular sub each time

for each instance of the server you can write specific code in the event handler (as mentioned in the above point a)
Avatar of Lycaon

ASKER

checoo:

Still messing with it, I'm having problems getting the event to work right.  I kinda jumped headlong into .NET and it seems I'm in danger of drowning, hehe.
can you post some code snippets as to where u r having problem
Avatar of Lycaon

ASKER

Problem is getting the code to raise events properly.

Pretty much I need an event with two arguments, a string GUID to identify the actual connection, and a byte array that holds the received data.  Then, in the event, I'd handle some variable and database manipulation, broadcast these changes to some of the other connections, and wait for more data.
can you post some code snippets..and from where do you want to raise the event
Avatar of Lycaon

ASKER

I can't post actual pieces of the working code due to an NDA, but pretty much what I was looking for is a class that behaves much like a Winsock control array.

EG, in the main form I'd Dim SocketArray As ThisSocketClass, then modify it so it would have .CreateSocket (which would return a GUID), Send with GUID as the argument, Close with GUID as the argument, then a Connect, Disconnect, and DataArrival event with the GUID, and in the case of DataArrival, a byte array as arguments.

The event should be raised from inside the class that has the actual array of sockets... But the event would be raised IN the form where the class was dimmed.

I really have looked into this, I'm just working 60+ hour weeks at my normal job, and I've got this on the side to complete. :\
ASKER CERTIFIED SOLUTION
Avatar of checoo
checoo

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If lycaon can post some sample code snippets related to his problem, I am still ready to help..