Imports System.Net.Sockets
Imports System.Net
Imports System.Threading
Imports MethodBase = System.Reflection.MethodBase

Public Class TCPClientSocket
    Public m_clientSocket As New TcpClient
    Public m_dataByte(2048) As Byte

    Public Event Connected()
    Public Event Disconnected()
    Public Event ErrorOccurred(ByVal errorMessage As String)
    Public Event DataReceived(ByRef dataByte() As Byte)
    Public Event SendComplete(ByVal bytesSent As Integer)

    Private m_endConnectCallback As New AsyncCallback(AddressOf EndConnectCallback)
    Private m_endDisconnectCallback As New AsyncCallback(AddressOf EndDisconnectCallback)
    Private m_endReceiveCallback As New AsyncCallback(AddressOf EndReceiveCallback)
    Private m_endSendCallback As New AsyncCallback(AddressOf EndSendCallback)

    Private m_localHostName As String = ""
    Private m_lockString As String = "LockMe"
    Private _remoteIP As String
    Private _remotePort As Integer
    Private _isConnected As Boolean

    ReadOnly Property IsConnected() As Boolean
        Get
            Return m_clientSocket.Client.Connected
        End Get
    End Property


    ReadOnly Property RemoteIP() As String
        Get
            Return _remoteIP
        End Get
    End Property

    ReadOnly Property RemotePort() As Integer
        Get
            Return _remotePort
        End Get
    End Property

    Private Sub EndDisconnectCallback(ByVal ar As IAsyncResult)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name

        Try
            m_clientSocket.Client.EndDisconnect(ar)

            If m_clientSocket.Client.Connected Then
                m_clientSocket.Client.Disconnect(True)
            End If
            RaiseEvent Disconnected()
        Catch ex As SocketException
            RaiseEvent ErrorOccurred(procedureName & ": " & ex.Message)
        Catch ex As Exception
            RaiseEvent ErrorOccurred(procedureName & ": " & ex.Message)
        End Try
    End Sub

    Public Sub CloseConnection()
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name

        Try
            m_clientSocket.Client.BeginDisconnect(True, m_endDisconnectCallback, Nothing)
        Catch ex As Exception
            'Throw ex
        End Try
    End Sub

    Private Sub EndSendCallback(ByVal ar As IAsyncResult)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name
        Dim bytesSent As Integer

        Try
            bytesSent = m_clientSocket.Client.EndSend(ar)
            RaiseEvent SendComplete(bytesSent)
        Catch ex As Exception
            CloseConnection()
            RaiseEvent ErrorOccurred(procedureName & ": " & ex.Message)
        End Try
    End Sub

    Public Sub Send(ByRef dataByte() As Byte)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name

        Try
            m_clientSocket.Client.BeginSend(dataByte, 0, dataByte.Length, SocketFlags.None, m_endSendCallback, Nothing)
        Catch ex As SocketException
            RaiseEvent ErrorOccurred(procedureName & ": " & ex.Message)
            CloseConnection()
        Catch ex As Exception
            RaiseEvent ErrorOccurred(procedureName & ": " & ex.Message)
            CloseConnection()
        End Try
    End Sub

    ReadOnly Property LocalHostName()
        Get
            Return m_localHostName
        End Get
    End Property

    Public Sub GetLocalIP(ByRef localIP() As String)
        Dim ipCount As Integer
        Dim i As Integer

        ipCount = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList.Length

        localIP = New String(ipCount - 1) {}

        i = 0
        For Each ip As Net.IPAddress In _
    System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList
            ' Eg: Display with message box
            localIP(i) = ip.ToString
            i += 1
        Next
    End Sub

    Private Sub EndReceiveCallback(ByVal ar As IAsyncResult)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name
        Dim bytesReceived As Integer
        Dim dataByte() As Byte

        Try
            bytesReceived = m_clientSocket.Client.EndReceive(ar)
            If bytesReceived > 0 Then
                dataByte = New Byte(bytesReceived - 1) {}
                Array.Copy(m_dataByte, 0, dataByte, 0, bytesReceived)
                m_clientSocket.Client.BeginReceive(m_dataByte, 0, m_dataByte.Length, SocketFlags.None, m_endReceiveCallback, Nothing)
                RaiseEvent DataReceived(dataByte)
            Else
                If m_clientSocket.Client.Connected Then
                    m_clientSocket.Client.Disconnect(True)
                End If
                RaiseEvent ErrorOccurred(procedureName & ": The connection may be closed by remote host.")
                RaiseEvent Disconnected()
            End If
        Catch e As SocketException
            CloseConnection()

            If e.ErrorCode = &H2749 Then
                'the connection is already closed.
                'do nothing here
            Else
                'the connection is connected but still occurs error
                RaiseEvent ErrorOccurred(procedureName & ": " & e.Message)
            End If

        Catch e As Exception
            CloseConnection()
            RaiseEvent ErrorOccurred(procedureName & ": " & e.Message)
        End Try
    End Sub

    Private Sub EndConnectCallback(ByVal ar As IAsyncResult)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name

        Try
            m_clientSocket.EndConnect(ar)
            m_clientSocket.Client.BeginReceive(m_dataByte, 0, m_dataByte.Length, SocketFlags.None, m_endReceiveCallback, Nothing)
            RaiseEvent Connected()
        Catch e As SocketException
            RaiseEvent ErrorOccurred(procedureName & ": " & e.Message)
        Catch e As Exception
            RaiseEvent ErrorOccurred(procedureName & ": " & e.Message)
        End Try
    End Sub

    Public Sub Connect(ByVal remoteHostIP As String, _
                       ByVal remotePort As Integer)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name
        Dim hostIPAddress As IPAddress = Nothing

        _remoteIP = remoteHostIP
        _remotePort = _remotePort

        If IPAddress.TryParse(remoteHostIP, hostIPAddress) Then
            Try
                SyncLock m_lockString
                    If m_clientSocket.Client.Connected = False Then
                        m_clientSocket.Close()
                        m_clientSocket = New TcpClient
                        m_clientSocket.BeginConnect(remoteHostIP, remotePort, m_endConnectCallback, Nothing)
                    Else
                        Throw New Exception("Already connects to host.")
                    End If
                End SyncLock
            Catch ex As Exception
                Throw New Exception(procedureName & ": " & ex.Message)
            End Try
        Else
            Throw New Exception(procedureName & ": Invalid IP format")
        End If
    End Sub
End Class
