diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs
index befe45c397..67cdb32819 100644
--- a/Libraries/Opc.Ua.Client/Session.cs
+++ b/Libraries/Opc.Ua.Client/Session.cs
@@ -799,28 +799,41 @@ namespace Opc.Ua.Client
}
///
- /// Creates a new communication session with a server using a reverse connection.
+ /// Creates a new session with a server using the specified channel by invoking the CreateSession service
///
/// The configuration for the client application.
- /// The client endpoint for the reverse connect.
+ /// The channel for the server.
/// The endpoint for the server.
- /// If set to true the discovery endpoint is used to update the endpoint description before connecting.
- /// If set to true then the domain in the certificate must match the endpoint used.
- /// The name to assign to the session.
- /// The timeout period for the session.
- /// The user identity to associate with the session.
- /// The preferred locales.
- /// The new session object.
- public static async Task Create(
+ /// The certificate to use for the client.
+ /// The list of available endpoints returned by server in GetEndpoints() response.
+ /// The value of profileUris used in GetEndpoints() request.
+ ///
+ public static Session Create(
+ ApplicationConfiguration configuration,
+ ITransportChannel channel,
+ ConfiguredEndpoint endpoint,
+ X509Certificate2 clientCertificate,
+ EndpointDescriptionCollection availableEndpoints = null,
+ StringCollection discoveryProfileUris = null)
+ {
+ return new Session(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris);
+ }
+
+ ///
+ /// Creates a secure channel to the specified endpoint.
+ ///
+ /// The application configuration.
+ /// The client endpoint for the reverse connect.
+ /// A configured endpoint to connect to.
+ /// Update configuration based on server prior connect.
+ /// Check that the certificate specifies a valid domain (computer) name.
+ /// A representing the asynchronous operation.
+ public static async Task CreateChannelAsync(
ApplicationConfiguration configuration,
ITransportWaitingConnection connection,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
- bool checkDomain,
- string sessionName,
- uint sessionTimeout,
- IUserIdentity identity,
- IList preferredLocales)
+ bool checkDomain)
{
endpoint.UpdateBeforeConnect = updateBeforeConnect;
@@ -888,6 +901,36 @@ namespace Opc.Ua.Client
messageContext);
}
+ return channel;
+ }
+
+ ///
+ /// Creates a new communication session with a server using a reverse connection.
+ ///
+ /// The configuration for the client application.
+ /// The client endpoint for the reverse connect.
+ /// The endpoint for the server.
+ /// If set to true the discovery endpoint is used to update the endpoint description before connecting.
+ /// If set to true then the domain in the certificate must match the endpoint used.
+ /// The name to assign to the session.
+ /// The timeout period for the session.
+ /// The user identity to associate with the session.
+ /// The preferred locales.
+ /// The new session object.
+ public static async Task Create(
+ ApplicationConfiguration configuration,
+ ITransportWaitingConnection connection,
+ ConfiguredEndpoint endpoint,
+ bool updateBeforeConnect,
+ bool checkDomain,
+ string sessionName,
+ uint sessionTimeout,
+ IUserIdentity identity,
+ IList preferredLocales)
+ {
+ // initialize the channel which will be created with the server.
+ ITransportChannel channel = await Session.CreateChannelAsync(configuration, connection, endpoint, updateBeforeConnect, checkDomain).ConfigureAwait(false);
+
// create the session object.
Session session = new Session(channel, configuration, endpoint, null);
@@ -1074,6 +1117,45 @@ namespace Opc.Ua.Client
return session;
}
+
+ ///
+ /// Recreates a session based on a specified template using the provided channel.
+ ///
+ /// The Session object to use as template
+ /// The waiting reverse connection.
+ /// The new session object.
+ public static Session Recreate(Session template, ITransportChannel transportChannel)
+ {
+ var messageContext = template.m_configuration.CreateMessageContext();
+ messageContext.Factory = template.Factory;
+
+ // create the session object.
+ Session session = new Session(transportChannel, template, true);
+
+ try
+ {
+ // open the session.
+ session.Open(
+ template.m_sessionName,
+ (uint)template.m_sessionTimeout,
+ template.m_identity,
+ template.m_preferredLocales,
+ template.m_checkDomain);
+
+ // create the subscriptions.
+ foreach (Subscription subscription in session.Subscriptions)
+ {
+ subscription.Create();
+ }
+ }
+ catch (Exception e)
+ {
+ session.Dispose();
+ throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", template.m_sessionName);
+ }
+
+ return session;
+ }
#endregion
#region Delegates and Events
@@ -1100,13 +1182,13 @@ namespace Opc.Ua.Client
///
public void Reconnect()
{
- Reconnect(null);
+ Reconnect(null, null);
}
///
/// Reconnects to the server after a network failure using a waiting connection.
///
- public void Reconnect(ITransportWaitingConnection connection)
+ public void Reconnect(ITransportWaitingConnection connection, ITransportChannel transportChannel = null)
{
try
{
@@ -1204,6 +1286,10 @@ namespace Opc.Ua.Client
TransportChannel = channel;
}
}
+ else if (transportChannel != null)
+ {
+ TransportChannel = transportChannel;
+ }
else
{
// check if the channel supports reconnect.
@@ -3206,13 +3292,23 @@ namespace Opc.Ua.Client
///
public override StatusCode Close()
{
- return Close(m_keepAliveInterval);
+ return Close(m_keepAliveInterval, true);
+ }
+
+ ///
+ /// Close the session with the server and optionally closes the channel.
+ ///
+ ///
+ ///
+ public StatusCode Close(bool closeChannel)
+ {
+ return Close(m_keepAliveInterval, closeChannel);
}
///
/// Disconnects from the server and frees any network resources with the specified timeout.
///
- public virtual StatusCode Close(int timeout)
+ public virtual StatusCode Close(int timeout, bool closeChannel = true)
{
// check if already called.
if (Disposed)
@@ -3255,7 +3351,10 @@ namespace Opc.Ua.Client
CloseSession(null, m_deleteSubscriptionsOnClose);
this.OperationTimeout = existingTimeout;
- CloseChannel();
+ if (closeChannel)
+ {
+ CloseChannel();
+ }
// raised notification indicating the session is closed.
SessionCreated(null, null);
@@ -3278,7 +3377,11 @@ namespace Opc.Ua.Client
}
// clean up.
- Dispose();
+ if (closeChannel)
+ {
+ Dispose();
+ }
+
return result;
}
#endregion
@@ -3469,7 +3572,6 @@ namespace Opc.Ua.Client
Utils.LogInfo("No subscriptions. Transfersubscription skipped.");
}
-
}
return true;
diff --git a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
index 568ea2c8cc..de474751d3 100644
--- a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
+++ b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
@@ -30,15 +30,7 @@ namespace Opc.Ua
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
- m_channel = channel;
- m_useTransportChannel = true;
-
- UaChannelBase uaChannel = channel as UaChannelBase;
-
- if (uaChannel != null)
- {
- m_useTransportChannel = uaChannel.m_uaBypassChannel != null || uaChannel.UseBinaryEncoding;
- }
+ InitializeChannel(channel);
}
#endregion
@@ -245,6 +237,23 @@ namespace Opc.Ua
#endregion
#region Public Methods
+ ///
+ /// Attach the channel to an already created client.
+ ///
+ /// Channel to be used by the client
+ public void AttachChannel(ITransportChannel channel)
+ {
+ InitializeChannel(channel);
+ }
+
+ ///
+ /// Detach the channel.
+ ///
+ public void DetachChannel()
+ {
+ m_channel = null;
+ }
+
///
/// Closes the channel.
///
@@ -282,6 +291,23 @@ namespace Opc.Ua
#endregion
#region Protected Methods
+ ///
+ /// Initializes the channel.
+ ///
+ ///
+ protected void InitializeChannel(ITransportChannel channel)
+ {
+ m_channel = channel;
+ m_useTransportChannel = true;
+
+ UaChannelBase uaChannel = channel as UaChannelBase;
+
+ if (uaChannel != null)
+ {
+ m_useTransportChannel = uaChannel.m_uaBypassChannel != null || uaChannel.UseBinaryEncoding;
+ }
+ }
+
///
/// Closes the channel.
///
diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
index 703dbcd8c0..f78225dba5 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
@@ -207,6 +207,27 @@ namespace Opc.Ua.Client.Tests
return session;
}
+ ///
+ /// Create a channel using the specified endpoint.
+ ///
+ /// The configured endpoint
+ ///
+ public async Task CreateChannelAsync(ConfiguredEndpoint endpoint)
+ {
+ return await Session.CreateChannelAsync(Config, null, endpoint, true, false).ConfigureAwait(false);
+ }
+
+ ///
+ /// Create a session using the specified channel.
+ ///
+ /// The channel to use
+ /// The configured endpoint
+ ///
+ public Session CreateSession(ITransportChannel channel, ConfiguredEndpoint endpoint)
+ {
+ return Session.Create(Config, channel, endpoint, null);
+ }
+
///
/// Get configured endpoint from url with security profile.
///
diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
index 9740694423..a077068bb0 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
@@ -242,6 +242,34 @@ namespace Opc.Ua.Client.Tests
session.Dispose();
}
+ [Test, Order(240)]
+ public async Task ConnectMultipleSessionsAsync()
+ {
+ var endpoint = await ClientFixture.GetEndpointAsync(this.ServerUrl, SecurityPolicies.Basic256Sha256, this.Endpoints);
+ Assert.NotNull(endpoint);
+
+ var channel = await ClientFixture.CreateChannelAsync(endpoint).ConfigureAwait(false);
+ Assert.NotNull(channel);
+
+ var session1 = ClientFixture.CreateSession(channel, endpoint);
+ session1.Open("Session1", null);
+
+ var session2 = ClientFixture.CreateSession(channel, endpoint);
+ session2.Open("Session2", null);
+
+ session1.Close(closeChannel: false);
+ session1.DetachChannel();
+ session1.Dispose();
+
+ _ = session2.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType));
+
+ session2.Close(closeChannel: false);
+ session2.DetachChannel();
+ session2.Dispose();
+
+ channel.Dispose();
+ }
+
[Test, Order(300)]
public new void GetOperationLimits()
{