There are many reasons that a TCP connection can be lost, not the least of which is the NAT router that it might sit behind. In my ongoing efforts to develop a private mail system, I needed to know if a client was still connected to the server. There was also a need to provide for a Client to connect to the server when it becomes available. So I developed a Heart Beat system.
This is a dual purpose system. In the examples I have provided, the Client includes a timer that is set to activate every 5 time slots. Normally the time slot would be 60 seconds (1 minute), but for testing I reduced it to 10 seconds. If the client is not currently connected to the server, it will attempt to connect. If it is already connected to the server, then it sends a Heart Beat packet to signal to the server that it is still alive and connected.
The server in essence ignores the packet, except that it increments a counter. The server also contains a timer, but this one activates every 10 time slots. If the Client has not sent any traffic in the last 10 time slots, the counter is zero and it is disconnected.
J.A. Coutts
Last edited by couttsj; Sep 23rd, 2017 at 11:23 PM.
'* ************************************************ *************** *
'* Module name: NetHeartbeat.vb
'* Function Description: Heartbeat packet processing class
'* Author: lyserver
'* Encoding date: November 14, 2011
'* Modified date:
'* ************************************************ *************** *
Imports System.Net.Sockets
'' '<summary>
'' 'Heartbeat package class
'' '</ summary>
Public Class NetHeartbeat
****Implements IDisposable
****'' '<summary>
****'' 'Heartbeat packet handling (send or receive)
****'' '</ summary>
****Public Enum MethodConstants
********[Send] = 0
********[Recv] = 1
****End Enum
****'' '<summary>
****'' 'Heartbeat package description class
****'' '</ summary>
****Private Class HeartbeatInfo 'heartbeat package information
********Public Buffer As Byte () = New Byte (0) {} 'Heartbeat Packet Data Buffer
********Public Times As Integer = 0 'Heartbeat Packet Detection Times
********Public Success As Boolean = True 'Heartbeat packet send or receive success flag
****End Class
****Private m_Socket As Socket = Nothing 'socket
****Private WithEvents m_Timer As Timers.Timer = New Timers.Timer (1000) 'Timer
****Private m_HeartbeatInfo As HeartbeatInfo = Nothing 'Heartbeat Package Description Object
****Private m_Method As MethodConstants = MethodConstants.Send 'heartbeat packet handling
****Private m_TryTimes As UInteger = 5 'Maximum number of attempts after heartbeat packet error
****Public Delegate Sub MyEventHandler (ByVal sender As NetHeartbeat, ByVal message As String)
****Public Event OnError As MyEventHandler
****Public Sub New ()
****End Sub
****Public Sub New (ByVal sock As Socket)
********m_Socket = sock
****End Sub
****Public Sub New (ByVal sock As Socket, ByVal method As MethodConstants)
********m_Socket = sock
********m_Method = method
****End Sub
****Public Sub New (ByVal sock As Socket, ByVal method As MethodConstants, ByVal enabled As Boolean)
********m_Socket = sock
********m_Method = method
********Me.Enabled = enabled
****End Sub
****Public Sub New (ByVal sock As Socket, ByVal method As MethodConstants, ByVal enabled As Boolean, ByVal interval As Integer)
********m_Socket = sock
********m_Method = method
********Me.Enabled = enabled
********Me.Interval = interval
****End Sub
****'' '<summary>
****'' 'Resets the heartbeat handling state
****'' '</ summary>
****Public Sub Reset ()
********If m_HeartbeatInfo Is Nothing Then
************m_HeartbeatInfo = New HeartbeatInfo ()
********End If
********m_HeartbeatInfo.Times = 0
********m_HeartbeatInfo.Success = True
****End Sub
****'' '<summary>
****'' 'Bind the socket and specify the way heartbeat packet is handled
****'' '</ summary>
****Public Sub Bind (ByVal sock As Socket, ByVal method As MethodConstants)
********m_Socket = sock
********m_Method = method
****End Sub
****'' '<summary>
****'' 'Start or stop the heartbeat pack
****'' '</ summary>
****Public Property Enabled () As Boolean
********Get
************Return m_Timer.Enabled
********End Get
********Set (ByVal value As Boolean)
************Reset ()
************m_Timer.Enabled = value
********End Set
****End Property
****'' '<summary>
****'' 'Heartbeat packet processing interval (ms)
****'' '</ summary>
****Public Property Interval () As UInteger
********Get
************Return m_Timer.Interval
********End Get
********Set (ByVal value As UInteger)
************m_Timer.Interval = value
********End Set
****End Property
****'' '<summary>
****'' 'Heartbeat packet received or sent error, timeout, failed after the number of retries
****'' '</ summary>
****Public Property TryTimes () As UInteger
********Get
************Return m_TryTimes
********End Get
********Set (ByVal value As UInteger)
************m_TryTimes = value
********End Set
****End Property
****'' '<summary>
****'' 'Heartbeat package handling: receive or send
****'' '</ summary>
****Public Property Method () As MethodConstants
********Get
************Return m_Method
********End Get
********Set (ByVal value As MethodConstants)
************m_Method = value
********End Set
****End Property
****'' '<summary>
****'' 'Send heartbeat package
****'' '</ summary>
****Private Sub SendHeartbeat ()
********Try
************If m_HeartbeatInfo.Success = True Then 'If the last send operation was successful, continue to send heartbeat packets
****************m_HeartbeatInfo.Success = False
****************m_Socket.BeginSend (m_HeartbeatInfo.Buffer, 0, 1, SocketFlags.OutOfBand, AddressOf Me.EndSendOOB, Nothing)
************Else 'otherwise, the timeout counter is incremented by one
****************m_TryTimes + = 1
************End If
********Catch ex As Exception
************ErrorHandler (ex.Message)
********End Try
****End Sub
****Private Sub EndSendOOB (ByVal ar As IAsyncResult)
********Try
************Dim nSendCount As Integer = m_Socket.EndSend (ar)
************m_HeartbeatInfo.Success = True 'Set the heartbeat packet to send the success flag
************m_TryTimes = 0 'timeout counter 0
********Catch ex As Exception
************ErrorHandler (ex.Message)
********End Try
****End Sub
****'' '<summary>
****'' 'Receive heartbeat package
****'' '</ summary>
****Private Sub RecvHeartbeat ()
********Try
************If m_HeartbeatInfo.Success = True Then 'If the last receive action was successful, continue to receive heartbeat packets
****************m_HeartbeatInfo.Success = False
****************m_Socket.BeginReceive (m_HeartbeatInfo.Buffer, 0, 1, SocketFlags.OutOfBand, AddressOf Me.EndRecvOOB, Nothing)
************Else 'otherwise, the timeout counter is incremented by one
****************m_TryTimes + = 1
************End If
********Catch ex As Exception
************ErrorHandler (ex.Message)
********End Try
****End Sub
Private Sub EndRecvOOB (ByVal ar As IAsyncResult)
********Try
************Dim nRecvCount As Integer = m_Socket.EndReceive (ar)
************m_HeartbeatInfo.Success = True 'Sets the heartbeat packet reception success flag
************m_TryTimes = 0 'timeout counter 0
********Catch ex As Exception
************ErrorHandler (ex.Message)
********End Try
****End Sub
**
****'' '<summary>
****'' 'Check the heartbeat packet to send or receive the situation, and according to the results to determine whether to continue to send or receive heartbeat package, or throw the heartbeat packet processing exception
****'' '</ summary>
****Private Sub m_Timer_Elapsed (ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles m_Timer.Elapsed
********If m_HeartbeatInfo Is Nothing Then
************m_Timer.Enabled = False
************Exit Sub
********End If
********Try
************If m_Method = MethodConstants.Send Then 'if the heartbeat packet is processed as a send
****************If m_HeartbeatInfo.Success = True Then 'If the heartbeat packet has been sent successfully, continue sending
********************SendHeartbeat ()
**
********************Console.WriteLine ("Heartbeat packet sent successfully")
**
****************Else 'otherwise, the heartbeat packet sends a timeout or an error
********************If m_HeartbeatInfo.Times <m_TryTimes Then 'If the timeout or error count is less than the specified number of times, then continue to send
************************SendHeartbeat ()
********************Else 'otherwise, throw the heartbeat packet sent exception error
************************ErrorHandler ("Heartbeat Packet Sending Timeout")
********************End If
****************End If
************Else 'if the heartbeat packet is treated as receiving
****************If m_HeartbeatInfo.Success = True Then 'If the heartbeat packet has been successfully received, continue to receive
********************RecvHeartbeat ()
**
********************Console.WriteLine ("Heartbeat Packet Received Success")
****************Else 'otherwise, the heartbeat pack receives a timeout or an error
********************If m_HeartbeatInfo.Times <m_TryTimes Then 'If the timeout or error count is less than the specified number of times, then continue to receive
************************RecvHeartbeat ()
********************Else 'otherwise, throw the heartbeat packet to receive an exception error
************************ErrorHandler ("Heartbeat Packet Received Timeout")
********************End If
****************End If
************End If
********Catch ex As Exception
************ErrorHandler (ex.Message)
********End Try
****End Sub
**
****'' '<summary>
****'' 'Exception handling
****'' '</ summary>
****Private Sub ErrorHandler (ByVal message As String)
********Try
************m_Timer.Enabled = False
************If m_HeartbeatInfo IsNot Nothing Then
****************m_HeartbeatInfo.Times = m_TryTimes
****************m_HeartbeatInfo.Success = False
************End If
********Catch ex As Exception
********End Try
********RaiseEvent OnError (Me, message)
****End Sub
**
****Private disposedValue As Boolean = False 'Detects redundant calls
**
****'IDisposable'
****Protected Overridable Sub Dispose (ByVal disposing As Boolean)
********If Not Me.disposedValue Then
************If disposing then
****************'TODO: Unmanaged resources are freed when explicitly called
************End If
**
************'TODO: free shared unmanaged resources
************m_Timer.Enabled = False
************m_Socket = Nothing
************m_HeartbeatInfo = Nothing
********End If
********Me.disposedValue = True
****End Sub
*
#Region "IDisposable Support"
****'Visual Basic adds this code in order to properly implement the disposable mode.
****Public Sub Dispose () Implements IDisposable.Dispose
********'Do not change this code. Place the cleanup code in Dispose (ByVal disposing As Boolean) above.
********Dispose (True)
********GC.SuppressFinalize (Me)
****End Sub
#End Region
**
End Class
Hi couttsj, thanks for sharing. I haven't tested your code yet, but in my opinion, the word "Heart Beat" is a good start. I've always wanted to use VB6 to develop an IM system, but never started to do it, because there are many problems that aren't resolved, or didn't find a suitable solution. E.g:
1. How to ensure reliable delivery of online real-time messages?
2. How to ensure reliable delivery of offline messages?
3. How do I ensure "timeliness"(timing) and "consistency" of IM real-time messages?
4. Should I use "push" or "pull" for online state synchronization in IM single chat and group chat?
5. IM group chat message is so complicated, how to ensure that data is not lost and not duplicated?
6. How to design IM intelligent "Heart-Beat" algorithm?
7. How to save network traffic when a large number of IM clients login?
8. What is the load balancing scheme of the cluster-based IM access layer?
9. How to achieve IM multi-point login and message roaming?
10. Why are large IM software using UDP protocol instead of TCP protocol?
Maybe I still need to spend a lot of time learning computer communication knowledge in order to develop a real practical IM software.
In addition, I found Digirev's multi-user Chat Example (Winsock) yesterday, unfortunately he didn't provide the latest IM source code (he had promised to provide it, but for some reason, suddenly stopped the project). http://www.vbforums.com/showthread.p...mple-(Winsock)
Last edited by dreammanor; Sep 26th, 2017 at 07:44 PM.
You are already aware of my attempt at chat software, so I won't bore you with that. I was more interested in a secure messaging system (email), so it was only used as a stepping stone towards my ultimate goal. Addressing your last question (#10), UDP is a lot less resource hungry than TCP. That is why it is used for DNS. The downside is that it is one way traffic only. Since a connection is never established, you never really know if it made it to the other end, and because there is no connection, there is no way to verify that the other end is still active (other than wait for a response). In DNS service, this sometimes leads to abuse by large organizations. To ensure delivery, they will send multiple requests from multiple servers. To overcome this, I designed a filter to drop the excess requests. Here is one example;
09/16/2017
Total queries processed - 8770
Queries forwarded to DNS - 2353
Unsupported Domain Queries - 1485
Unsupported Type Queries - 2505
Duplicate Queries - 2427
It also demonstrates another problem with UDP. Because there is no connection, the originating IP address can be spoofed. There is no way of knowing if the Unsupported Domain Queries were from spoofed addresses or not. TCP overcomes this by using a 3 way handshake to establish the connection.
Just as an aside, the Unsupported Type Queries above are mainly the result of the use of extended DNS. Extended DNS requires periodic polling to see if the destination has changed it's use of EDNS. You would think that they would record places that don't use EDNS and drop all this excess traffic. Amazon is by far the worst offender.
Hi couttsj, thank you for your detailed answers. I am not only very concerned about chat software, but also very interested in mail system. IMO, a lot of knowledge and technology in the mail system can also be applied to the chat system. My understanding of UDP and TCP is still very simple, and I am learning them. Perhaps the combination of UPD and TCP is a good choice in the chat system.
Hi couttsj, thank you for your detailed answers. I am not only very concerned about chat software, but also very interested in mail system. IMO, a lot of knowledge and technology in the mail system can also be applied to the chat system. My understanding of UDP and TCP is still very simple, and I am learning them. Perhaps the combination of UPD and TCP is a good choice in the chat system.
Although both UDP and TCP on separate ports has in the past been used for some remote operating software, it has lost favour in recent times. I do not know the reasons behind this, but I suspect it has something to do with the security issues I outlined above. Because of the limitation in packet size with UDP, it was only used as a timing tool. Problems with NAT routers may also be an issue, as UDP has a much lower inactivity time out before it is dropped from the routing table.