OPC UA交互模型
在OPC UA中有个"服务"的概念,注意这个"服务"和操作系统中的"服务"不是一个概念。在OPC UA中"服务"表示的是实现一个功能的一组方法,如:查找服务器服务集、获得终端服务集、客户端与服务器连接管理服务集、读写数据及元数据服务集等。而服务又以请求的方式去实现。只不过这部分OPC 栈都给我们封装好了,我们在使用时不需要关心,但是会涉及到标题提到的超时时间。有精力的话可以看看源码,比如TCP发送请求:
public IAsyncResult BeginSendRequest(IServiceRequest request, int timeout, AsyncCallback callback, object state)
{
if (request == null) throw new ArgumentNullException("request");
if (timeout <= 0) throw new ArgumentException("Timeout must be greater than zero.", "timeout");
lock (DataLock)
{
bool firstCall = false;
WriteOperation operation = null;
// check if this is the first call.
if (State == TcpChannelState.Closed)
{
if (m_queuedOperations == null)
{
firstCall = true;
m_queuedOperations = new List<QueuedOperation>();
}
}
// queue operations until connect completes.
if (m_queuedOperations != null)
{
operation = BeginOperation(timeout, callback, state);
m_queuedOperations.Add(new QueuedOperation(operation, timeout, request));
if (firstCall)
{
BeginConnect(this.m_url, timeout, OnConnectOnDemandComplete, null);
}
return operation;
}
if (State != TcpChannelState.Open)
{
throw new ServiceResultException(StatusCodes.BadConnectionClosed);
}
// Utils.Trace("Channel {0}: BeginSendRequest()", ChannelId);
if (m_reconnecting)
{
throw ServiceResultException.Create(StatusCodes.BadRequestInterrupted, "Attempting to reconnect to the server.");
}
// send request.
operation = BeginOperation(timeout, callback, state);
SendRequest(operation, timeout, request);
return operation;
}
}
废话不多说了,来说正题-超时。
1.请求超时(OperationTimeout)
请求超时是在应用配置(ApplicationConfiguration)-传输配额配置(TransportQuotas)-操作超时(OperationTimeout)属性中指定的。
它表示的是请求超时,由客户端配置。如果超时时间到期,客户端通讯协议栈返回API调用,或者发送超时状态回调。但需要注意的是,客户端UA栈设置的超时时间也会发送给服务器,用于探测不在需要返回给客户端的调用。
2.会话默认超时时间(DefaultSessionTimeout)
会话默认超时可以在应用配置(ApplicationConfiguration)-客户端配置(ClientConfiguration)-默认会话超时(DefaultSessionTimeout)属性中指定。
会话默认超时只的是在当前客户端中创建的会话的默认超时。
3.会话超时(sessionTimeout)
会话超时是在创建会话时指定。
public static Session Create(
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
string sessionName,
uint sessionTimeout, //会话超时
IUserIdentity identity,
IList<string> preferredLocales)
{
return Create(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales);
}
会话超时和默认会话超时时间其实指的是一个东西,在创建会话时指定时如果指定了会话超时时间就是用指定的会话超时时间,如果在创建会话时没有指定会话超时时间(为0),默认会话超时时间就会起作用。源代码中是这样写的
if (sessionTimeout == 0)
{
sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout;
}
讲了这么多,还没说这个会话超时具体是什么意义。对于这个会话超时,官方是这么解释的:如果一个客户端在约定的时间内没有发出一个请求,会话将会被服务器自动终止。如果想了解具体是怎么干的,还是需要去看看元代码,如果仅仅是开发使用了解这么多久足够了。
4.修订会话超时(revisedSessionTimeout )
这个是服务端的响应返回,我们做客户端开发时不需要太关心,但是最好还是需要了解下。官方的解释为:服务器分配的会话超时,如果客户端请求的超时在服务器定义的有效范围内,就是用该超时。 这理解起来可能有些晦涩,其实是这样的:在创建会话时客户端可以指定一个会话超时时间,服务器也有个会话超时时间,这就有了个约定客户端指定的会话超时时间必须在服务器规定的会话超时时间范围内,否则就会取服务端会话超时上下限。经测试,Kepserver6.4集成的UA 服务端会话超时范围为15000-60000.也就是说如果你在创建会话时超时时间指定为的值为15000-60000时那么会话超时时间为客户端设定值,如果创建会话时超时时间设定为<15000的值那么实际超时时间为15000,如果创建会话时超时时间设定为>60000,那么实际会话超时时间为60000.实际超时时间会由 修订会话超时(revisedSessionTimeout )这个参数返回。它的实现为:
public virtual ResponseHeader CreateSession(
RequestHeader requestHeader,
ApplicationDescription clientDescription,
string serverUri,
string endpointUrl,
string sessionName,
byte[] clientNonce,
byte[] clientCertificate,
double requestedSessionTimeout,
uint maxResponseMessageSize,
out NodeId sessionId,
out NodeId authenticationToken,
out double revisedSessionTimeout,
out byte[] serverNonce,
out byte[] serverCertificate,
out EndpointDescriptionCollection serverEndpoints,
out SignedSoftwareCertificateCollection serverSoftwareCertificates,
out SignatureData serverSignature,
out uint maxRequestMessageSize)
{
CreateSessionRequest request = new CreateSessionRequest();
CreateSessionResponse response = null;
request.RequestHeader = requestHeader;
request.ClientDescription = clientDescription;
request.ServerUri = serverUri;
request.EndpointUrl = endpointUrl;
request.SessionName = sessionName;
request.ClientNonce = clientNonce;
request.ClientCertificate = clientCertificate;
request.RequestedSessionTimeout = requestedSessionTimeout;
request.MaxResponseMessageSize = maxResponseMessageSize;
UpdateRequestHeader(request, requestHeader == null, "CreateSession");
try
{
if (UseTransportChannel)
{
IServiceResponse genericResponse = TransportChannel.SendRequest(request);
if (genericResponse == null)
{
throw new ServiceResultException(StatusCodes.BadUnknownResponse);
}
ValidateResponse(genericResponse.ResponseHeader);
response = (CreateSessionResponse)genericResponse;
}
else
{
CreateSessionResponseMessage responseMessage = InnerChannel.CreateSession(new CreateSessionMessage(request));
if (responseMessage == null || responseMessage.CreateSessionResponse == null)
{
throw new ServiceResultException(StatusCodes.BadUnknownResponse);
}
response = responseMessage.CreateSessionResponse;
ValidateResponse(response.ResponseHeader);
}
sessionId = response.SessionId;
authenticationToken = response.AuthenticationToken;
revisedSessionTimeout = response.RevisedSessionTimeout;
serverNonce = response.ServerNonce;
serverCertificate = response.ServerCertificate;
serverEndpoints = response.ServerEndpoints;
serverSoftwareCertificates = response.ServerSoftwareCertificates;
serverSignature = response.ServerSignature;
maxRequestMessageSize = response.MaxRequestMessageSize;
}
finally
{
RequestCompleted(request, response, "CreateSession");
}
return response.ResponseHeader;
}
5.订阅发布时间间隔(PublishingInterval)
订阅发布时间间隔在订阅对象属性中指定:
/// <summary>
/// The publishing interval.
/// </summary>
[DataMember(Order = 2)]
public int PublishingInterval
{
get { return m_publishingInterval; }
set { m_publishingInterval = value; }
}
订阅发布时间间隔控制订阅最少多少时间通知我们一次。
6.订阅采样时间(SamplingInterval)
订阅采样时间在订阅条目对象中指定:
/// <summary>
/// The sampling interval.
/// </summary>
[DataMember(Order = 9)]
public int SamplingInterval
{
get { return m_samplingInterval; }
set
{
if (m_samplingInterval != value)
{
m_attributesModified = true;
}
m_samplingInterval = value;
}
}
订阅采样时间指的是订阅的条目的采样时间。
订阅发布时间和订阅采样时间这么讲理解起来可能有点儿模糊。所以我去找了张图一起来学习一下吧。
看了这张图是不是就很清楚了。原来订阅条目采样后并不是直接发布出来,而是要进自己的队列。这个队列大小在我们初始化订阅条目时也是可以指定的。
/// <summary>
/// The length of the queue used to buffer values.
/// </summary>
[DataMember(Order = 11)]
public uint QueueSize
{
get { return m_queueSize; }
set
{
if (m_queueSize != value)
{
m_attributesModified = true;
}
m_queueSize = value;
}
}
说明:以上所有代码来自OPC 基金会官方 OPC UA开源库。欢迎大家一起学习、讨论:QQ群-633204942