mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-20 08:41:51 +08:00
Enable client support for multiple sessions on a single channel (#1659)
* Client API changes to support multiple sessions on a single channel * Tested against freeopcua:python:server and ProSys simulation server
This commit is contained in:
@@ -799,28 +799,41 @@ namespace Opc.Ua.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration for the client application.</param>
|
||||
/// <param name="connection">The client endpoint for the reverse connect.</param>
|
||||
/// <param name="channel">The channel for the server.</param>
|
||||
/// <param name="endpoint">The endpoint for the server.</param>
|
||||
/// <param name="updateBeforeConnect">If set to <c>true</c> the discovery endpoint is used to update the endpoint description before connecting.</param>
|
||||
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
|
||||
/// <param name="sessionName">The name to assign to the session.</param>
|
||||
/// <param name="sessionTimeout">The timeout period for the session.</param>
|
||||
/// <param name="identity">The user identity to associate with the session.</param>
|
||||
/// <param name="preferredLocales">The preferred locales.</param>
|
||||
/// <returns>The new session object.</returns>
|
||||
public static async Task<Session> Create(
|
||||
/// <param name="clientCertificate">The certificate to use for the client.</param>
|
||||
/// <param name="availableEndpoints">The list of available endpoints returned by server in GetEndpoints() response.</param>
|
||||
/// <param name="discoveryProfileUris">The value of profileUris used in GetEndpoints() request.</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a secure channel to the specified endpoint.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
/// <param name="connection">The client endpoint for the reverse connect.</param>
|
||||
/// <param name="endpoint">A configured endpoint to connect to.</param>
|
||||
/// <param name="updateBeforeConnect">Update configuration based on server prior connect.</param>
|
||||
/// <param name="checkDomain">Check that the certificate specifies a valid domain (computer) name.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task<ITransportChannel> CreateChannelAsync(
|
||||
ApplicationConfiguration configuration,
|
||||
ITransportWaitingConnection connection,
|
||||
ConfiguredEndpoint endpoint,
|
||||
bool updateBeforeConnect,
|
||||
bool checkDomain,
|
||||
string sessionName,
|
||||
uint sessionTimeout,
|
||||
IUserIdentity identity,
|
||||
IList<string> preferredLocales)
|
||||
bool checkDomain)
|
||||
{
|
||||
endpoint.UpdateBeforeConnect = updateBeforeConnect;
|
||||
|
||||
@@ -888,6 +901,36 @@ namespace Opc.Ua.Client
|
||||
messageContext);
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new communication session with a server using a reverse connection.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration for the client application.</param>
|
||||
/// <param name="connection">The client endpoint for the reverse connect.</param>
|
||||
/// <param name="endpoint">The endpoint for the server.</param>
|
||||
/// <param name="updateBeforeConnect">If set to <c>true</c> the discovery endpoint is used to update the endpoint description before connecting.</param>
|
||||
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
|
||||
/// <param name="sessionName">The name to assign to the session.</param>
|
||||
/// <param name="sessionTimeout">The timeout period for the session.</param>
|
||||
/// <param name="identity">The user identity to associate with the session.</param>
|
||||
/// <param name="preferredLocales">The preferred locales.</param>
|
||||
/// <returns>The new session object.</returns>
|
||||
public static async Task<Session> Create(
|
||||
ApplicationConfiguration configuration,
|
||||
ITransportWaitingConnection connection,
|
||||
ConfiguredEndpoint endpoint,
|
||||
bool updateBeforeConnect,
|
||||
bool checkDomain,
|
||||
string sessionName,
|
||||
uint sessionTimeout,
|
||||
IUserIdentity identity,
|
||||
IList<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates a session based on a specified template using the provided channel.
|
||||
/// </summary>
|
||||
/// <param name="template">The Session object to use as template</param>
|
||||
/// <param name="transportChannel">The waiting reverse connection.</param>
|
||||
/// <returns>The new session object.</returns>
|
||||
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
|
||||
/// </summary>
|
||||
public void Reconnect()
|
||||
{
|
||||
Reconnect(null);
|
||||
Reconnect(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconnects to the server after a network failure using a waiting connection.
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
public override StatusCode Close()
|
||||
{
|
||||
return Close(m_keepAliveInterval);
|
||||
return Close(m_keepAliveInterval, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the session with the server and optionally closes the channel.
|
||||
/// </summary>
|
||||
/// <param name="closeChannel"></param>
|
||||
/// <returns></returns>
|
||||
public StatusCode Close(bool closeChannel)
|
||||
{
|
||||
return Close(m_keepAliveInterval, closeChannel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server and frees any network resources with the specified timeout.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Attach the channel to an already created client.
|
||||
/// </summary>
|
||||
/// <param name="channel">Channel to be used by the client</param>
|
||||
public void AttachChannel(ITransportChannel channel)
|
||||
{
|
||||
InitializeChannel(channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detach the channel.
|
||||
/// </summary>
|
||||
public void DetachChannel()
|
||||
{
|
||||
m_channel = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the channel.
|
||||
/// </summary>
|
||||
@@ -282,6 +291,23 @@ namespace Opc.Ua
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
/// <summary>
|
||||
/// Initializes the channel.
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the channel.
|
||||
/// </summary>
|
||||
|
||||
@@ -207,6 +207,27 @@ namespace Opc.Ua.Client.Tests
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a channel using the specified endpoint.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The configured endpoint</param>
|
||||
/// <returns></returns>
|
||||
public async Task<ITransportChannel> CreateChannelAsync(ConfiguredEndpoint endpoint)
|
||||
{
|
||||
return await Session.CreateChannelAsync(Config, null, endpoint, true, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a session using the specified channel.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel to use</param>
|
||||
/// <param name="endpoint">The configured endpoint</param>
|
||||
/// <returns></returns>
|
||||
public Session CreateSession(ITransportChannel channel, ConfiguredEndpoint endpoint)
|
||||
{
|
||||
return Session.Create(Config, channel, endpoint, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get configured endpoint from url with security profile.
|
||||
/// </summary>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user