Files
Modules/EDMIAPI/Client/Channel.vb
Developer01 3eb6314ba8 Verbesserungen: Thread-Sicherheit, Logging, WCF-Updates
Die Änderungen umfassen:
- Einführung von `ReaderWriterLockSlim` für Thread-Sicherheit.
- Optimierung von `IsOnline` mit atomaren Operationen.
- Verbesserte Reconnect-Logik und Fehlerbehandlung.
- Neue Methoden für Datenbankoperationen (`GetDatatableByName` etc.).
- Gemeinsame `ChannelFactory` zur Ressourcenschonung.
- Logging-Verbesserungen mit präziseren Meldungen und Symbolen.
- Aktualisierte WCF-Bindings für höhere Verbindungszahlen und kürzere Timeouts.
- Aktualisierte Service-Referenzen mit asynchronen Methoden.
- Bereinigung von veralteten Methoden und Eigenschaften.
- Assembly-Version auf `2.8.0.0` aktualisiert.
2026-10-13 12:06:50 +02:00

158 lines
6.6 KiB
VB.net

Imports System.ServiceModel
Imports System.Threading
Imports System.Xml
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
Imports DigitalData.Modules.Logging
Public Class Channel
Inherits BaseClass
Private Shared ReadOnly _FactoryLock As New Object()
Private Shared _SharedFactory As ChannelFactory(Of IEDMIServiceChannel) = Nothing
Private Shared _SharedFactoryEndpoint As String = String.Empty
Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel)
Public Event Reconnect As EventHandler
Private Shared _ActiveConnections As Integer = 0
Public Shared ReadOnly Property ActiveConnections As Integer
Get
Return _ActiveConnections
End Get
End Property
Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding
Dim oMaxConnections = Math.Max(500, Environment.ProcessorCount * 50)
Return New NetTcpBinding() With {
.MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE,
.MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE,
.MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE,
.ListenBacklog = oMaxConnections,
.MaxConnections = oMaxConnections,
.TransferMode = TransferMode.Streamed,
.SendTimeout = TimeSpan.FromSeconds(60),
.ReceiveTimeout = TimeSpan.FromMinutes(10),
.OpenTimeout = TimeSpan.FromSeconds(60),
.CloseTimeout = TimeSpan.FromSeconds(30),
.Security = New NetTcpSecurity() With {
.Mode = SecurityMode.Transport,
.Transport = New TcpTransportSecurity() With {
.ClientCredentialType = AuthenticationMode
}
},
.ReaderQuotas = New XmlDictionaryReaderQuotas() With {
.MaxArrayLength = Constants.ChannelSettings.MAX_ARRAY_LENGTH,
.MaxStringContentLength = Constants.ChannelSettings.MAX_STRING_CONTENT_LENGTH
}
}
End Function
Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct)
MyBase.New(pLogConfig)
ChannelFactory = GetOrCreateSharedFactory(pLogConfig, pServerAddress)
End Sub
''' <summary>
''' Gibt eine gemeinsame ChannelFactory zurück.
''' Ein Connection-Pool für alle Client-Instanzen desselben Endpoints.
''' </summary>
Private Shared Function GetOrCreateSharedFactory(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct) As ChannelFactory(Of IEDMIServiceChannel)
Dim oEndpoint = $"net.tcp://{pServerAddress.Host}:{pServerAddress.Port}/DigitalData/Services/Main"
' ← Volatile.Read: garantiert aktuellen Wert auch ohne Lock
Dim oCurrentFactory = Volatile.Read(_SharedFactory)
If oCurrentFactory IsNot Nothing AndAlso
_SharedFactoryEndpoint = oEndpoint AndAlso
(oCurrentFactory.State = CommunicationState.Opened OrElse
oCurrentFactory.State = CommunicationState.Created) Then
Return oCurrentFactory
End If
SyncLock _FactoryLock
' Double-check nach Lock
If _SharedFactory IsNot Nothing AndAlso
_SharedFactoryEndpoint = oEndpoint AndAlso
(_SharedFactory.State = CommunicationState.Opened OrElse
_SharedFactory.State = CommunicationState.Created) Then
Return _SharedFactory
End If
If _SharedFactory IsNot Nothing Then
If _SharedFactory.State = CommunicationState.Faulted OrElse
_SharedFactory.State = CommunicationState.Closing Then
Try
_SharedFactory.Abort()
Catch
End Try
End If
_SharedFactory = Nothing
End If
Dim oLogger = pLogConfig.GetLogger()
Dim oBinding = GetBinding()
Dim oAddress = New EndpointAddress(oEndpoint)
oLogger.Debug("Creating shared ChannelFactory | Endpoint=[{0}] | MaxConnections=[{1}] | SendTimeout=[{2}] | OpenTimeout=[{3}]",
oEndpoint, oBinding.MaxConnections, oBinding.SendTimeout, oBinding.OpenTimeout)
_SharedFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress)
_SharedFactoryEndpoint = oEndpoint
Return _SharedFactory
End SyncLock
End Function
''' <summary>
''' Closes a channel gracefully, falls back to Abort on error
''' </summary>
Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel)
If pChannel Is Nothing Then Return
Try
If pChannel.State = CommunicationState.Opened Then
pChannel.Close()
ElseIf pChannel.State = CommunicationState.Faulted OrElse
pChannel.State = CommunicationState.Closing Then
pChannel.Abort()
End If
Catch ex As Exception
Try
pChannel.Abort()
Catch
End Try
End Try
End Sub
''' <summary>
''' Creates a channel and adds a Faulted-Handler
''' </summary>
Public Function GetChannel() As IEDMIServiceChannel
Try
Logger.Debug("Creating channel from shared factory.")
Dim oChannel = ChannelFactory.CreateChannel()
AddHandler oChannel.Opened, Sub()
Dim oCount = Interlocked.Increment(_ActiveConnections)
Logger.Debug("Channel opened. Active connections: [{0}]", oCount)
End Sub
AddHandler oChannel.Closed, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Debug("Channel closed. Active connections: [{0}]", oCount)
End Sub
AddHandler oChannel.Faulted, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Warn("⚠ Channel faulted. Active connections: [{0}]", oCount)
RaiseEvent Reconnect(Me, Nothing)
End Sub
Logger.Debug("Channel created. State=[{0}]", oChannel.State)
Return oChannel
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
End Class