Rename pvAccessApp to src, adjust Makefiles
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# This is a Makefile fragment, see ../Makefile
|
||||
|
||||
SRC_DIRS += $(PVACCESS_SRC)/remote
|
||||
|
||||
INC += remote.h
|
||||
INC += blockingUDP.h
|
||||
INC += beaconHandler.h
|
||||
INC += blockingTCP.h
|
||||
INC += channelSearchManager.h
|
||||
INC += simpleChannelSearchManagerImpl.h
|
||||
INC += transportRegistry.h
|
||||
INC += serializationHelper.h
|
||||
INC += codec.h
|
||||
|
||||
LIBSRCS += blockingUDPTransport.cpp
|
||||
LIBSRCS += blockingUDPConnector.cpp
|
||||
LIBSRCS += beaconHandler.cpp
|
||||
LIBSRCS += blockingTCPConnector.cpp
|
||||
LIBSRCS += simpleChannelSearchManagerImpl.cpp
|
||||
LIBSRCS += abstractResponseHandler.cpp
|
||||
LIBSRCS += blockingTCPAcceptor.cpp
|
||||
LIBSRCS += transportRegistry.cpp
|
||||
LIBSRCS += serializationHelper.cpp
|
||||
LIBSRCS += codec.cpp
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/remote.h>
|
||||
#include <pv/hexDump.h>
|
||||
|
||||
#include <pv/byteBuffer.h>
|
||||
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using std::ostringstream;
|
||||
using std::hex;
|
||||
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
void AbstractResponseHandler::handleResponse(osiSockAddr* responseFrom,
|
||||
Transport::shared_pointer const & /*transport*/, int8 version, int8 command,
|
||||
size_t payloadSize, ByteBuffer* payloadBuffer) {
|
||||
if(_debug) {
|
||||
char ipAddrStr[48];
|
||||
ipAddrToDottedIP(&responseFrom->ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
|
||||
ostringstream prologue;
|
||||
prologue<<"Message [0x"<<hex<<(int)command<<", v0x"<<hex;
|
||||
prologue<<(int)version<<"] received from "<<ipAddrStr;
|
||||
|
||||
hexDump(prologue.str(), _description,
|
||||
(const int8*)payloadBuffer->getArray(),
|
||||
payloadBuffer->getPosition(), static_cast<int>(payloadSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/beaconHandler.h>
|
||||
#include <pv/transportRegistry.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace epics::pvData;
|
||||
using namespace epics::pvAccess;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
BeaconHandler::BeaconHandler(Context::shared_pointer const & context,
|
||||
const osiSockAddr* responseFrom) :
|
||||
_context(Context::weak_pointer(context)),
|
||||
_responseFrom(*responseFrom),
|
||||
_mutex(),
|
||||
_serverStartupTime(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BeaconHandler::BeaconHandler(const osiSockAddr* responseFrom) :
|
||||
_responseFrom(*responseFrom),
|
||||
_mutex(),
|
||||
_serverStartupTime(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BeaconHandler::~BeaconHandler()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void BeaconHandler::beaconNotify(osiSockAddr* /*from*/, int8 remoteTransportRevision,
|
||||
TimeStamp* timestamp, TimeStamp* startupTime, int16 sequentalID,
|
||||
PVFieldPtr /*data*/)
|
||||
{
|
||||
bool networkChanged = updateBeacon(remoteTransportRevision, timestamp, startupTime, sequentalID);
|
||||
if (networkChanged)
|
||||
changedTransport();
|
||||
}
|
||||
|
||||
bool BeaconHandler::updateBeacon(int8 /*remoteTransportRevision*/, TimeStamp* /*timestamp*/,
|
||||
TimeStamp* startupTime, int16 /*sequentalID*/)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
// first beacon notification check
|
||||
if (_serverStartupTime.getSecondsPastEpoch() == 0)
|
||||
{
|
||||
_serverStartupTime = *startupTime;
|
||||
|
||||
// new server up..
|
||||
_context.lock()->newServerDetected();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool networkChange = !(_serverStartupTime == *startupTime);
|
||||
if (networkChange)
|
||||
{
|
||||
// update startup time
|
||||
_serverStartupTime = *startupTime;
|
||||
|
||||
_context.lock()->newServerDetected();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BeaconHandler::changedTransport()
|
||||
{
|
||||
// TODO why only TCP, actually TCP does not need this
|
||||
auto_ptr<TransportRegistry::transportVector_t> transports =
|
||||
_context.lock()->getTransportRegistry()->get("TCP", &_responseFrom);
|
||||
if (!transports.get())
|
||||
return;
|
||||
|
||||
// notify all
|
||||
for (TransportRegistry::transportVector_t::iterator iter = transports->begin();
|
||||
iter != transports->end();
|
||||
iter++)
|
||||
{
|
||||
(*iter)->changedTransport();
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef BEACONHANDLER_H
|
||||
#define BEACONHANDLER_H
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define beaconHandlerEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <pv/timeStamp.h>
|
||||
#include <pv/lock.h>
|
||||
|
||||
#ifdef beaconHandlerEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef beaconHandlerEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/remote.h>
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
/**
|
||||
* BeaconHandler
|
||||
*/
|
||||
class BeaconHandler
|
||||
{
|
||||
public:
|
||||
POINTER_DEFINITIONS(BeaconHandler);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param transport transport to be used to send beacons.
|
||||
* @param context PVA context.
|
||||
*/
|
||||
BeaconHandler(Context::shared_pointer const & context, const osiSockAddr* responseFrom);
|
||||
/**
|
||||
* Test Constructor (for testing)
|
||||
* @param transport transport to be used to send beacons.
|
||||
*/
|
||||
BeaconHandler(const osiSockAddr* responseFrom);
|
||||
virtual ~BeaconHandler();
|
||||
/**
|
||||
* Update beacon period and do analitical checks (server restared, routing problems, etc.)
|
||||
* @param from who is notifying.
|
||||
* @param remoteTransportRevision encoded (major, minor) revision.
|
||||
* @param timestamp time when beacon was received.
|
||||
* @param startupTime server (reported) startup time.
|
||||
* @param sequentalID sequential ID.
|
||||
* @param data server status data, can be <code>NULL</code>.
|
||||
*/
|
||||
void beaconNotify(osiSockAddr* from,
|
||||
epics::pvData::int8 remoteTransportRevision,
|
||||
epics::pvData::TimeStamp* timestamp,
|
||||
epics::pvData::TimeStamp* startupTime,
|
||||
epics::pvData::int16 sequentalID,
|
||||
epics::pvData::PVFieldPtr data);
|
||||
private:
|
||||
/**
|
||||
* Context instance.
|
||||
*/
|
||||
Context::weak_pointer _context;
|
||||
/**
|
||||
* Remote address.
|
||||
*/
|
||||
const osiSockAddr _responseFrom;
|
||||
/**
|
||||
* Mutex
|
||||
*/
|
||||
epics::pvData::Mutex _mutex;
|
||||
/**
|
||||
* Server startup timestamp.
|
||||
*/
|
||||
epics::pvData::TimeStamp _serverStartupTime;
|
||||
/**
|
||||
* Update beacon.
|
||||
* @param remoteTransportRevision encoded (major, minor) revision.
|
||||
* @param timestamp time when beacon was received.
|
||||
* @param sequentalID sequential ID.
|
||||
* @return network change (server restarted) detected.
|
||||
*/
|
||||
bool updateBeacon(epics::pvData::int8 remoteTransportRevision,
|
||||
epics::pvData::TimeStamp* timestamp,
|
||||
epics::pvData::TimeStamp* startupTime,
|
||||
epics::pvData::int16 sequentalID);
|
||||
/**
|
||||
* Changed transport (server restarted) notify.
|
||||
*/
|
||||
void changedTransport();
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif /* INTROSPECTIONREGISTRY_H */
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef BLOCKINGTCP_H_
|
||||
#define BLOCKINGTCP_H_
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define blockingTCPEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <osdSock.h>
|
||||
#include <osiSock.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsThread.h>
|
||||
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/pvType.h>
|
||||
#include <pv/lock.h>
|
||||
#include <pv/timer.h>
|
||||
#include <pv/event.h>
|
||||
|
||||
#ifdef blockingTCPEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef blockingTCPEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/remote.h>
|
||||
#include <pv/transportRegistry.h>
|
||||
#include <pv/introspectionRegistry.h>
|
||||
#include <pv/namedLockPattern.h>
|
||||
#include <pv/inetAddressUtil.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
/**
|
||||
* Channel Access TCP connector.
|
||||
* @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a>
|
||||
* @version $Id: BlockingTCPConnector.java,v 1.1 2010/05/03 14:45:47 mrkraimer Exp $
|
||||
*/
|
||||
class BlockingTCPConnector : public Connector {
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingTCPConnector);
|
||||
|
||||
BlockingTCPConnector(Context::shared_pointer const & context, int receiveBufferSize,
|
||||
float beaconInterval);
|
||||
|
||||
virtual ~BlockingTCPConnector();
|
||||
|
||||
virtual Transport::shared_pointer connect(TransportClient::shared_pointer const & client,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler, osiSockAddr& address,
|
||||
epics::pvData::int8 transportRevision, epics::pvData::int16 priority);
|
||||
private:
|
||||
/**
|
||||
* Lock timeout
|
||||
*/
|
||||
static const int LOCK_TIMEOUT = 20*1000; // 20s
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*/
|
||||
Context::weak_pointer _context;
|
||||
|
||||
/**
|
||||
* named lock
|
||||
*/
|
||||
NamedLockPattern<const osiSockAddr*, comp_osiSockAddrPtr> _namedLocker;
|
||||
|
||||
/**
|
||||
* Receive buffer size.
|
||||
*/
|
||||
int _receiveBufferSize;
|
||||
|
||||
/**
|
||||
* Beacon interval.
|
||||
*/
|
||||
float _beaconInterval;
|
||||
|
||||
/**
|
||||
* Tries to connect to the given address.
|
||||
* @param[in] address
|
||||
* @param[in] tries
|
||||
* @return the SOCKET
|
||||
* @throws IOException
|
||||
*/
|
||||
SOCKET tryConnect(osiSockAddr& address, int tries);
|
||||
|
||||
};
|
||||
|
||||
class ResponseHandlerFactory
|
||||
{
|
||||
public:
|
||||
POINTER_DEFINITIONS(ResponseHandlerFactory);
|
||||
|
||||
virtual ~ResponseHandlerFactory() {};
|
||||
|
||||
virtual std::auto_ptr<ResponseHandler> createResponseHandler() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Channel Access Server TCP acceptor.
|
||||
* @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a>
|
||||
* @version $Id: BlockingTCPAcceptor.java,v 1.1 2010/05/03 14:45:42 mrkraimer Exp $
|
||||
*/
|
||||
class BlockingTCPAcceptor {
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingTCPAcceptor);
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param port
|
||||
* @param receiveBufferSize
|
||||
* @throws PVAException
|
||||
*/
|
||||
BlockingTCPAcceptor(Context::shared_pointer const & context,
|
||||
ResponseHandlerFactory::shared_pointer const & responseHandlerFactory,
|
||||
int port, int receiveBufferSize);
|
||||
|
||||
virtual ~BlockingTCPAcceptor();
|
||||
|
||||
void handleEvents();
|
||||
|
||||
/**
|
||||
* Bind socket address.
|
||||
* @return bind socket address, <code>null</code> if not binded.
|
||||
*/
|
||||
const osiSockAddr* getBindAddress() {
|
||||
return &_bindAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy acceptor (stop listening).
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Context instance.
|
||||
*/
|
||||
Context::shared_pointer _context;
|
||||
|
||||
/**
|
||||
* ResponseHandler factory.
|
||||
*/
|
||||
ResponseHandlerFactory::shared_pointer _responseHandlerFactory;
|
||||
|
||||
/**
|
||||
* Bind server socket address.
|
||||
*/
|
||||
osiSockAddr _bindAddress;
|
||||
|
||||
/**
|
||||
* Server socket channel.
|
||||
*/
|
||||
SOCKET _serverSocketChannel;
|
||||
|
||||
/**
|
||||
* Receive buffer size.
|
||||
*/
|
||||
int _receiveBufferSize;
|
||||
|
||||
/**
|
||||
* Destroyed flag.
|
||||
*/
|
||||
bool _destroyed;
|
||||
|
||||
epics::pvData::Mutex _mutex;
|
||||
|
||||
epicsThreadId _threadId;
|
||||
|
||||
/**
|
||||
* Initialize connection acception.
|
||||
* @return port where server is listening
|
||||
*/
|
||||
int initialize(unsigned short port);
|
||||
|
||||
/**
|
||||
* Validate connection by sending a validation message request.
|
||||
* @return <code>true</code> on success.
|
||||
*/
|
||||
bool validateConnection(Transport::shared_pointer const & transport, const char* address);
|
||||
|
||||
static void handleEventsRunner(void* param);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* BLOCKINGTCP_H_ */
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/blockingTCP.h>
|
||||
#include "codec.h"
|
||||
#include <pv/remote.h>
|
||||
#include <pv/logger.h>
|
||||
|
||||
#include <pv/epicsException.h>
|
||||
|
||||
#include <osiSock.h>
|
||||
#include <epicsThread.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using std::ostringstream;
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
BlockingTCPAcceptor::BlockingTCPAcceptor(
|
||||
Context::shared_pointer const & context,
|
||||
ResponseHandlerFactory::shared_pointer const & responseHandlerFactory,
|
||||
int port,
|
||||
int receiveBufferSize) :
|
||||
_context(context),
|
||||
_responseHandlerFactory(responseHandlerFactory),
|
||||
_bindAddress(),
|
||||
_serverSocketChannel(INVALID_SOCKET),
|
||||
_receiveBufferSize(receiveBufferSize),
|
||||
_destroyed(false),
|
||||
_threadId(0)
|
||||
{
|
||||
initialize(port);
|
||||
}
|
||||
|
||||
BlockingTCPAcceptor::~BlockingTCPAcceptor() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
int BlockingTCPAcceptor::initialize(unsigned short port) {
|
||||
// specified bind address
|
||||
_bindAddress.ia.sin_family = AF_INET;
|
||||
_bindAddress.ia.sin_port = htons(port);
|
||||
_bindAddress.ia.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
char strBuffer[64];
|
||||
char ipAddrStr[48];
|
||||
ipAddrToDottedIP(&_bindAddress.ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
|
||||
int tryCount = 0;
|
||||
while(tryCount<2) {
|
||||
|
||||
LOG(logLevelDebug, "Creating acceptor to %s.", ipAddrStr);
|
||||
|
||||
_serverSocketChannel = epicsSocketCreate(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(_serverSocketChannel==INVALID_SOCKET) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
ostringstream temp;
|
||||
temp<<"Socket create error: "<<strBuffer;
|
||||
LOG(logLevelError, "%s", temp.str().c_str());
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
else {
|
||||
|
||||
//epicsSocketEnableAddressReuseDuringTimeWaitState(_serverSocketChannel);
|
||||
|
||||
// try to bind
|
||||
int retval = ::bind(_serverSocketChannel, &_bindAddress.sa, sizeof(sockaddr));
|
||||
if(retval<0) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Socket bind error: %s", strBuffer);
|
||||
if(_bindAddress.ia.sin_port!=0) {
|
||||
// failed to bind to specified bind address,
|
||||
// try to get port dynamically, but only once
|
||||
LOG(
|
||||
logLevelDebug,
|
||||
"Configured TCP port %d is unavailable, trying to assign it dynamically.",
|
||||
port);
|
||||
_bindAddress.ia.sin_port = htons(0);
|
||||
}
|
||||
else {
|
||||
epicsSocketDestroy(_serverSocketChannel);
|
||||
break; // exit while loop
|
||||
}
|
||||
}
|
||||
else { // if(retval<0)
|
||||
// bind succeeded
|
||||
|
||||
// update bind address, if dynamically port selection was used
|
||||
if(ntohs(_bindAddress.ia.sin_port)==0) {
|
||||
osiSocklen_t sockLen = sizeof(sockaddr);
|
||||
// read the actual socket info
|
||||
retval = ::getsockname(_serverSocketChannel, &_bindAddress.sa, &sockLen);
|
||||
if(retval<0) {
|
||||
// error obtaining port number
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "getsockname error: %s", strBuffer);
|
||||
}
|
||||
else {
|
||||
LOG(
|
||||
logLevelInfo,
|
||||
"Using dynamically assigned TCP port %d.",
|
||||
ntohs(_bindAddress.ia.sin_port));
|
||||
}
|
||||
}
|
||||
|
||||
retval = ::listen(_serverSocketChannel, 1024);
|
||||
if(retval<0) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
ostringstream temp;
|
||||
temp<<"Socket listen error: "<<strBuffer;
|
||||
LOG(logLevelError, "%s", temp.str().c_str());
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
|
||||
_threadId
|
||||
= epicsThreadCreate(
|
||||
"TCP-acceptor",
|
||||
epicsThreadPriorityMedium,
|
||||
epicsThreadGetStackSize(
|
||||
epicsThreadStackMedium),
|
||||
BlockingTCPAcceptor::handleEventsRunner,
|
||||
this);
|
||||
|
||||
// all OK, return
|
||||
return ntohs(_bindAddress.ia.sin_port);
|
||||
} // successful bind
|
||||
} // successfully obtained socket
|
||||
tryCount++;
|
||||
} // while
|
||||
|
||||
ostringstream temp;
|
||||
temp<<"Failed to create acceptor to "<<ipAddrStr;
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
|
||||
void BlockingTCPAcceptor::handleEvents() {
|
||||
// rise level if port is assigned dynamically
|
||||
char ipAddrStr[48];
|
||||
ipAddrToDottedIP(&_bindAddress.ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
LOG(logLevelDebug, "Accepting connections at %s.", ipAddrStr);
|
||||
|
||||
bool socketOpen = true;
|
||||
char strBuffer[64];
|
||||
|
||||
while(socketOpen) {
|
||||
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
if (_destroyed)
|
||||
break;
|
||||
}
|
||||
|
||||
osiSockAddr address;
|
||||
osiSocklen_t len = sizeof(sockaddr);
|
||||
|
||||
SOCKET newClient = epicsSocketAccept(_serverSocketChannel, &address.sa, &len);
|
||||
if(newClient!=INVALID_SOCKET) {
|
||||
// accept succeeded
|
||||
ipAddrToDottedIP(&address.ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
LOG(logLevelDebug, "Accepted connection from PVA client: %s", ipAddrStr);
|
||||
|
||||
// enable TCP_NODELAY (disable Nagle's algorithm)
|
||||
int optval = 1; // true
|
||||
int retval = ::setsockopt(newClient, IPPROTO_TCP, TCP_NODELAY, (char *)&optval, sizeof(int));
|
||||
if(retval<0) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Error setting TCP_NODELAY: %s", strBuffer);
|
||||
}
|
||||
|
||||
// enable TCP_KEEPALIVE
|
||||
retval = ::setsockopt(newClient, SOL_SOCKET, SO_KEEPALIVE, (char *)&optval, sizeof(int));
|
||||
if(retval<0) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Error setting SO_KEEPALIVE: %s", strBuffer);
|
||||
}
|
||||
|
||||
// TODO tune buffer sizes?!
|
||||
|
||||
// get TCP send buffer size
|
||||
osiSocklen_t intLen = sizeof(int);
|
||||
int _socketSendBufferSize;
|
||||
retval = getsockopt(newClient, SOL_SOCKET, SO_SNDBUF, (char *)&_socketSendBufferSize, &intLen);
|
||||
if(retval<0) {
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Error getting SO_SNDBUF: %s", strBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transport, it registers itself to the registry.
|
||||
*/
|
||||
std::auto_ptr<ResponseHandler> responseHandler = _responseHandlerFactory->createResponseHandler();
|
||||
detail::BlockingServerTCPTransportCodec::shared_pointer transport =
|
||||
detail::BlockingServerTCPTransportCodec::create(
|
||||
_context,
|
||||
newClient,
|
||||
responseHandler,
|
||||
_socketSendBufferSize,
|
||||
_receiveBufferSize);
|
||||
|
||||
// validate connection
|
||||
if(!validateConnection(transport, ipAddrStr)) {
|
||||
transport->close();
|
||||
LOG(
|
||||
logLevelDebug,
|
||||
"Connection to PVA client %s failed to be validated, closing it.",
|
||||
ipAddrStr);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(logLevelDebug, "Serving to PVA client: %s", ipAddrStr);
|
||||
|
||||
}// accept succeeded
|
||||
else
|
||||
socketOpen = false;
|
||||
} // while
|
||||
}
|
||||
|
||||
bool BlockingTCPAcceptor::validateConnection(Transport::shared_pointer const & transport, const char* address) {
|
||||
try {
|
||||
transport->verify(0);
|
||||
return true;
|
||||
} catch(...) {
|
||||
LOG(logLevelDebug, "Validation of %s failed.", address);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BlockingTCPAcceptor::handleEventsRunner(void* param) {
|
||||
((BlockingTCPAcceptor*)param)->handleEvents();
|
||||
}
|
||||
|
||||
void BlockingTCPAcceptor::destroy() {
|
||||
Lock guard(_mutex);
|
||||
if(_destroyed) return;
|
||||
_destroyed = true;
|
||||
|
||||
if(_serverSocketChannel!=INVALID_SOCKET) {
|
||||
char ipAddrStr[48];
|
||||
ipAddrToDottedIP(&_bindAddress.ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
LOG(logLevelDebug, "Stopped accepting connections at %s.", ipAddrStr);
|
||||
|
||||
epicsSocketDestroy(_serverSocketChannel);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/blockingTCP.h>
|
||||
#include <pv/remote.h>
|
||||
#include <pv/namedLockPattern.h>
|
||||
#include <pv/logger.h>
|
||||
#include <pv/codec.h>
|
||||
|
||||
#include <epicsThread.h>
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sstream>
|
||||
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
BlockingTCPConnector::BlockingTCPConnector(
|
||||
Context::shared_pointer const & context,
|
||||
int receiveBufferSize,
|
||||
float beaconInterval) :
|
||||
_context(context),
|
||||
_namedLocker(),
|
||||
_receiveBufferSize(receiveBufferSize),
|
||||
_beaconInterval(beaconInterval)
|
||||
{
|
||||
}
|
||||
|
||||
BlockingTCPConnector::~BlockingTCPConnector() {
|
||||
}
|
||||
|
||||
SOCKET BlockingTCPConnector::tryConnect(osiSockAddr& address, int tries) {
|
||||
|
||||
char strBuffer[64];
|
||||
ipAddrToDottedIP(&address.ia, strBuffer, sizeof(strBuffer));
|
||||
|
||||
for(int tryCount = 0; tryCount<tries; tryCount++) {
|
||||
|
||||
LOG(logLevelDebug,
|
||||
"Opening socket to PVA server %s, attempt %d.",
|
||||
strBuffer, tryCount+1);
|
||||
|
||||
SOCKET socket = epicsSocketCreate(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (socket == INVALID_SOCKET)
|
||||
{
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelWarn, "Socket create error: %s", strBuffer);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
else {
|
||||
if(::connect(socket, &address.sa, sizeof(sockaddr))==0) {
|
||||
return socket;
|
||||
}
|
||||
else {
|
||||
epicsSocketDestroy (socket);
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Socket connect error: %s", strBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
Transport::shared_pointer BlockingTCPConnector::connect(TransportClient::shared_pointer const & client,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler, osiSockAddr& address,
|
||||
int8 transportRevision, int16 priority) {
|
||||
|
||||
SOCKET socket = INVALID_SOCKET;
|
||||
|
||||
char ipAddrStr[64];
|
||||
ipAddrToDottedIP(&address.ia, ipAddrStr, sizeof(ipAddrStr));
|
||||
|
||||
Context::shared_pointer context = _context.lock();
|
||||
|
||||
// first try to check cache w/o named lock...
|
||||
Transport::shared_pointer transport = context->getTransportRegistry()->get("TCP", &address, priority);
|
||||
if(transport.get()) {
|
||||
LOG(logLevelDebug,
|
||||
"Reusing existing connection to PVA server: %s",
|
||||
ipAddrStr);
|
||||
if (transport->acquire(client))
|
||||
return transport;
|
||||
}
|
||||
|
||||
bool lockAcquired = _namedLocker.acquireSynchronizationObject(&address, LOCK_TIMEOUT);
|
||||
if(lockAcquired) {
|
||||
try {
|
||||
// ... transport created during waiting in lock
|
||||
transport = context->getTransportRegistry()->get("TCP", &address, priority);
|
||||
if(transport.get()) {
|
||||
LOG(logLevelDebug,
|
||||
"Reusing existing connection to PVA server: %s",
|
||||
ipAddrStr);
|
||||
if (transport->acquire(client))
|
||||
return transport;
|
||||
}
|
||||
|
||||
LOG(logLevelDebug, "Connecting to PVA server: %s", ipAddrStr);
|
||||
|
||||
socket = tryConnect(address, 3);
|
||||
|
||||
// verify
|
||||
if(socket==INVALID_SOCKET) {
|
||||
LOG(logLevelDebug,
|
||||
"Connection to PVA server %s failed.", ipAddrStr);
|
||||
std::ostringstream temp;
|
||||
temp<<"Failed to verify TCP connection to '"<<ipAddrStr<<"'.";
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
|
||||
LOG(logLevelDebug, "Socket connected to PVA server: %s.", ipAddrStr);
|
||||
|
||||
// enable TCP_NODELAY (disable Nagle's algorithm)
|
||||
int optval = 1; // true
|
||||
int retval = ::setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
|
||||
(char *)&optval, sizeof(int));
|
||||
if(retval<0) {
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelWarn, "Error setting TCP_NODELAY: %s", errStr);
|
||||
}
|
||||
|
||||
// enable TCP_KEEPALIVE
|
||||
retval = ::setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(char *)&optval, sizeof(int));
|
||||
if(retval<0)
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelWarn, "Error setting SO_KEEPALIVE: %s", errStr);
|
||||
}
|
||||
|
||||
// TODO tune buffer sizes?! Win32 defaults are 8k, which is OK
|
||||
|
||||
// create transport
|
||||
// TODO introduce factory
|
||||
// get TCP send buffer size
|
||||
osiSocklen_t intLen = sizeof(int);
|
||||
int _socketSendBufferSize;
|
||||
retval = getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&_socketSendBufferSize, &intLen);
|
||||
if(retval<0) {
|
||||
char strBuffer[64];
|
||||
epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer));
|
||||
LOG(logLevelDebug, "Error getting SO_SNDBUF: %s", strBuffer);
|
||||
}
|
||||
|
||||
transport = detail::BlockingClientTCPTransportCodec::create(
|
||||
context, socket, responseHandler, _receiveBufferSize, _socketSendBufferSize,
|
||||
client, transportRevision, _beaconInterval, priority);
|
||||
|
||||
// verify
|
||||
if(!transport->verify(3000)) {
|
||||
LOG(
|
||||
logLevelDebug,
|
||||
"Connection to PVA server %s failed to be validated, closing it.",
|
||||
ipAddrStr);
|
||||
|
||||
std::ostringstream temp;
|
||||
temp<<"Failed to verify TCP connection to '"<<ipAddrStr<<"'.";
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
|
||||
// TODO send security token
|
||||
|
||||
LOG(logLevelDebug, "Connected to PVA server: %s", ipAddrStr);
|
||||
|
||||
_namedLocker.releaseSynchronizationObject(&address);
|
||||
return transport;
|
||||
} catch(std::exception&) {
|
||||
if(transport.get())
|
||||
transport->close();
|
||||
else if(socket!=INVALID_SOCKET) epicsSocketDestroy(socket);
|
||||
_namedLocker.releaseSynchronizationObject(&address);
|
||||
throw;
|
||||
} catch(...) {
|
||||
if(transport.get())
|
||||
transport->close();
|
||||
else if(socket!=INVALID_SOCKET) epicsSocketDestroy(socket);
|
||||
_namedLocker.releaseSynchronizationObject(&address);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::ostringstream temp;
|
||||
temp<<"Failed to obtain synchronization lock for '"<<ipAddrStr;
|
||||
temp<<"', possible deadlock.";
|
||||
THROW_BASE_EXCEPTION(temp.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef BLOCKINGUDP_H_
|
||||
#define BLOCKINGUDP_H_
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define blockingUDPEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <osdSock.h>
|
||||
#include <osiSock.h>
|
||||
#include <epicsThread.h>
|
||||
|
||||
#include <pv/noDefaultMethods.h>
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/lock.h>
|
||||
#include <pv/event.h>
|
||||
#include <pv/pvIntrospect.h>
|
||||
|
||||
#ifdef blockingUDPEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef blockingUDPEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/remote.h>
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/inetAddressUtil.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
class BlockingUDPTransport : public epics::pvData::NoDefaultMethods,
|
||||
public Transport,
|
||||
public TransportSendControl,
|
||||
public std::tr1::enable_shared_from_this<BlockingUDPTransport>
|
||||
{
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingUDPTransport);
|
||||
|
||||
private:
|
||||
BlockingUDPTransport(std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
SOCKET channel, osiSockAddr& bindAddress,
|
||||
short remoteTransportRevision);
|
||||
public:
|
||||
static shared_pointer create(std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
SOCKET channel, osiSockAddr& bindAddress,
|
||||
short remoteTransportRevision)
|
||||
{
|
||||
shared_pointer thisPointer(
|
||||
new BlockingUDPTransport(responseHandler, channel, bindAddress, remoteTransportRevision)
|
||||
);
|
||||
return thisPointer;
|
||||
}
|
||||
|
||||
virtual ~BlockingUDPTransport();
|
||||
|
||||
virtual bool isClosed() {
|
||||
return _closed.get();
|
||||
}
|
||||
|
||||
virtual const osiSockAddr* getRemoteAddress() const {
|
||||
// always connected
|
||||
return &_bindAddress;
|
||||
}
|
||||
|
||||
virtual epics::pvData::String getType() const {
|
||||
return epics::pvData::String("UDP");
|
||||
}
|
||||
|
||||
virtual std::size_t getReceiveBufferSize() const {
|
||||
return _receiveBuffer->getSize();
|
||||
}
|
||||
|
||||
virtual std::size_t getSocketReceiveBufferSize() const;
|
||||
|
||||
virtual epics::pvData::int16 getPriority() const {
|
||||
return PVA_DEFAULT_PRIORITY;
|
||||
}
|
||||
|
||||
virtual epics::pvData::int8 getRevision() const {
|
||||
return PVA_PROTOCOL_REVISION;
|
||||
}
|
||||
|
||||
virtual void setRemoteRevision(epics::pvData::int8 /*revision*/) {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void setRemoteTransportReceiveBufferSize(
|
||||
std::size_t /*receiveBufferSize*/) {
|
||||
// noop for UDP (limited by 64k; MAX_UDP_SEND for PVA)
|
||||
}
|
||||
|
||||
virtual void setRemoteTransportSocketReceiveBufferSize(
|
||||
std::size_t /*socketReceiveBufferSize*/) {
|
||||
// noop for UDP (limited by 64k; MAX_UDP_SEND for PVA)
|
||||
}
|
||||
|
||||
virtual void aliveNotification() {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void changedTransport() {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual bool verify(epics::pvData::int32 /*timeoutMs*/) {
|
||||
// noop
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void verified() {
|
||||
// noop
|
||||
}
|
||||
|
||||
// NOTE: this is not yet used for UDP
|
||||
virtual void setByteOrder(int byteOrder) {
|
||||
// called from receive thread... or before processing
|
||||
_receiveBuffer->setEndianess(byteOrder);
|
||||
|
||||
// sync?!
|
||||
_sendBuffer->setEndianess(byteOrder);
|
||||
}
|
||||
|
||||
virtual void enqueueSendRequest(TransportSender::shared_pointer const & sender);
|
||||
|
||||
virtual void flushSendQueue();
|
||||
|
||||
void start();
|
||||
|
||||
virtual void close();
|
||||
|
||||
virtual void ensureData(std::size_t /*size*/) {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void alignData(std::size_t alignment) {
|
||||
_receiveBuffer->align(alignment);
|
||||
}
|
||||
|
||||
virtual bool directSerialize(epics::pvData::ByteBuffer* /*existingBuffer*/, const char* /*toSerialize*/,
|
||||
std::size_t /*elementCount*/, std::size_t /*elementSize*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool directDeserialize(epics::pvData::ByteBuffer* /*existingBuffer*/, char* /*deserializeTo*/,
|
||||
std::size_t /*elementCount*/, std::size_t /*elementSize*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void startMessage(epics::pvData::int8 command, std::size_t ensureCapacity);
|
||||
virtual void endMessage();
|
||||
|
||||
virtual void flush(bool /*lastMessageCompleted*/) {
|
||||
// noop since all UDP requests are sent immediately
|
||||
}
|
||||
|
||||
virtual void setRecipient(const osiSockAddr& sendTo) {
|
||||
_sendToEnabled = true;
|
||||
_sendTo = sendTo;
|
||||
}
|
||||
|
||||
virtual void flushSerializeBuffer() {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void ensureBuffer(std::size_t /*size*/) {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void alignBuffer(std::size_t alignment) {
|
||||
_sendBuffer->align(alignment);
|
||||
}
|
||||
|
||||
virtual void cachedSerialize(
|
||||
const std::tr1::shared_ptr<const epics::pvData::Field>& field, epics::pvData::ByteBuffer* buffer)
|
||||
{
|
||||
// no cache
|
||||
field->serialize(buffer, this);
|
||||
}
|
||||
|
||||
virtual std::tr1::shared_ptr<const epics::pvData::Field>
|
||||
cachedDeserialize(epics::pvData::ByteBuffer* buffer)
|
||||
{
|
||||
// no cache
|
||||
// TODO
|
||||
return epics::pvData::getFieldCreate()->deserialize(buffer, this);
|
||||
}
|
||||
|
||||
virtual bool acquire(std::tr1::shared_ptr<TransportClient> const & /*client*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void release(pvAccessID /*clientId*/) {}
|
||||
|
||||
/**
|
||||
* Set ignore list.
|
||||
* @param addresses list of ignored addresses.
|
||||
*/
|
||||
void setIgnoredAddresses(InetAddrVector* addresses) {
|
||||
if (addresses)
|
||||
{
|
||||
if (!_ignoredAddresses) _ignoredAddresses = new InetAddrVector;
|
||||
*_ignoredAddresses = *addresses;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_ignoredAddresses) { delete _ignoredAddresses; _ignoredAddresses = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of ignored addresses.
|
||||
* @return ignored addresses.
|
||||
*/
|
||||
InetAddrVector* getIgnoredAddresses() const {
|
||||
return _ignoredAddresses;
|
||||
}
|
||||
|
||||
bool send(epics::pvData::ByteBuffer* buffer, const osiSockAddr& address);
|
||||
|
||||
bool send(epics::pvData::ByteBuffer* buffer);
|
||||
|
||||
/**
|
||||
* Get list of send addresses.
|
||||
* @return send addresses.
|
||||
*/
|
||||
InetAddrVector* getSendAddresses() {
|
||||
return _sendAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bind address.
|
||||
* @return bind address.
|
||||
*/
|
||||
const osiSockAddr* getBindAddress() const {
|
||||
return &_bindAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list of send addresses.
|
||||
* @param addresses list of send addresses, non-<code>null</code>.
|
||||
*/
|
||||
void setBroadcastAddresses(InetAddrVector* addresses) {
|
||||
if (addresses)
|
||||
{
|
||||
if (!_sendAddresses) _sendAddresses = new InetAddrVector;
|
||||
*_sendAddresses = *addresses;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_sendAddresses) { delete _sendAddresses; _sendAddresses = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
AtomicBoolean _closed;
|
||||
|
||||
/**
|
||||
* Response handler.
|
||||
*/
|
||||
std::auto_ptr<ResponseHandler> _responseHandler;
|
||||
|
||||
virtual void processRead();
|
||||
|
||||
private:
|
||||
static void threadRunner(void* param);
|
||||
|
||||
bool processBuffer(Transport::shared_pointer const & transport, osiSockAddr& fromAddress, epics::pvData::ByteBuffer* receiveBuffer);
|
||||
|
||||
void close(bool waitForThreadToComplete);
|
||||
|
||||
// Context only used for logging in this class
|
||||
|
||||
/**
|
||||
* Corresponding channel.
|
||||
*/
|
||||
SOCKET _channel;
|
||||
|
||||
/**
|
||||
* Bind address.
|
||||
*/
|
||||
osiSockAddr _bindAddress;
|
||||
|
||||
/**
|
||||
* Send addresses.
|
||||
*/
|
||||
InetAddrVector* _sendAddresses;
|
||||
|
||||
/**
|
||||
* Ignore addresses.
|
||||
*/
|
||||
InetAddrVector* _ignoredAddresses;
|
||||
|
||||
/**
|
||||
* Send address.
|
||||
*/
|
||||
osiSockAddr _sendTo;
|
||||
bool _sendToEnabled;
|
||||
|
||||
/**
|
||||
* Receive buffer.
|
||||
*/
|
||||
std::auto_ptr<epics::pvData::ByteBuffer> _receiveBuffer;
|
||||
|
||||
/**
|
||||
* Send buffer.
|
||||
*/
|
||||
std::auto_ptr<epics::pvData::ByteBuffer> _sendBuffer;
|
||||
|
||||
/**
|
||||
* Last message start position.
|
||||
*/
|
||||
int _lastMessageStartPosition;
|
||||
|
||||
/**
|
||||
* Used for process sync.
|
||||
*/
|
||||
epics::pvData::Mutex _mutex;
|
||||
epics::pvData::Mutex _sendMutex;
|
||||
epics::pvData::Event _shutdownEvent;
|
||||
|
||||
/**
|
||||
* Thread ID
|
||||
*/
|
||||
epicsThreadId _threadId;
|
||||
|
||||
};
|
||||
|
||||
class BlockingUDPConnector :
|
||||
public Connector,
|
||||
private epics::pvData::NoDefaultMethods {
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingUDPConnector);
|
||||
|
||||
BlockingUDPConnector(
|
||||
bool reuseSocket,
|
||||
bool broadcast) :
|
||||
_reuseSocket(reuseSocket),
|
||||
_broadcast(broadcast) {
|
||||
}
|
||||
|
||||
virtual ~BlockingUDPConnector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: transport client is ignored for broadcast (UDP).
|
||||
*/
|
||||
virtual Transport::shared_pointer connect(TransportClient::shared_pointer const & client,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler, osiSockAddr& bindAddress,
|
||||
epics::pvData::int8 transportRevision, epics::pvData::int16 priority);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Reuse socket flag.
|
||||
*/
|
||||
bool _reuseSocket;
|
||||
|
||||
/**
|
||||
* Broadcast flag.
|
||||
*/
|
||||
bool _broadcast;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* BLOCKINGUDP_H_ */
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/blockingUDP.h>
|
||||
#include <pv/remote.h>
|
||||
#include <pv/logger.h>
|
||||
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
Transport::shared_pointer BlockingUDPConnector::connect(TransportClient::shared_pointer const & /*client*/,
|
||||
auto_ptr<ResponseHandler>& responseHandler, osiSockAddr& bindAddress,
|
||||
int8 transportRevision, int16 /*priority*/) {
|
||||
|
||||
LOG(logLevelDebug, "Creating datagram socket to: %s",
|
||||
inetAddressToString(bindAddress).c_str());
|
||||
|
||||
SOCKET socket = epicsSocketCreate(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(socket==INVALID_SOCKET) {
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError, "Error creating socket: %s", errStr);
|
||||
return Transport::shared_pointer();
|
||||
}
|
||||
|
||||
int optval = _broadcast ? 1 : 0;
|
||||
int retval = ::setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval));
|
||||
if(retval<0)
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError, "Error setting SO_BROADCAST: %s", errStr);
|
||||
epicsSocketDestroy (socket);
|
||||
return Transport::shared_pointer();
|
||||
}
|
||||
|
||||
/*
|
||||
IPv4 multicast addresses are defined by the leading address bits of 1110, originating from the classful network design of the early Internet when this group of addresses was designated as Class D. The Classless Inter-Domain Routing (CIDR) prefix of this group is 224.0.0.0/4. The group includes the addresses from 224.0.0.0 to 239.255.255.255. Address assignments from within this range are specified in RFC 5771, an Internet Engineering Task Force (IETF) Best Current Practice document (BCP 51).*/
|
||||
|
||||
|
||||
// set SO_REUSEADDR or SO_REUSEPORT, OS dependant
|
||||
if (_reuseSocket)
|
||||
epicsSocketEnableAddressUseForDatagramFanout(socket);
|
||||
|
||||
retval = ::bind(socket, (sockaddr*)&(bindAddress.sa), sizeof(sockaddr));
|
||||
if(retval<0) {
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError, "Error binding socket: %s", errStr);
|
||||
epicsSocketDestroy (socket);
|
||||
return Transport::shared_pointer();
|
||||
}
|
||||
|
||||
// sockets are blocking by default
|
||||
Transport::shared_pointer transport = BlockingUDPTransport::create(responseHandler, socket, bindAddress, transportRevision);
|
||||
return transport;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/blockingUDP.h>
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/inetAddressUtil.h>
|
||||
#include <pv/logger.h>
|
||||
#include <pv/likely.h>
|
||||
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/lock.h>
|
||||
|
||||
#include <osdSock.h>
|
||||
#include <osiSock.h>
|
||||
#include <epicsThread.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <sys/types.h>
|
||||
|
||||
using namespace epics::pvData;
|
||||
using namespace std;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
#ifdef __vxworks
|
||||
inline int sendto(int s, const char *buf, size_t len, int flags, const struct sockaddr *to, int tolen)
|
||||
{
|
||||
return ::sendto(s, const_cast<char*>(buf), len, flags, const_cast<struct sockaddr *>(to), tolen);
|
||||
}
|
||||
#endif
|
||||
|
||||
PVACCESS_REFCOUNT_MONITOR_DEFINE(blockingUDPTransport);
|
||||
|
||||
BlockingUDPTransport::BlockingUDPTransport(
|
||||
auto_ptr<ResponseHandler>& responseHandler, SOCKET channel,
|
||||
osiSockAddr& bindAddress,
|
||||
short /*remoteTransportRevision*/) :
|
||||
_closed(),
|
||||
_responseHandler(responseHandler),
|
||||
_channel(channel),
|
||||
_bindAddress(bindAddress),
|
||||
_sendAddresses(0),
|
||||
_ignoredAddresses(0),
|
||||
_sendToEnabled(false),
|
||||
_receiveBuffer(new ByteBuffer(MAX_UDP_RECV)),
|
||||
_sendBuffer(new ByteBuffer(MAX_UDP_RECV)),
|
||||
_lastMessageStartPosition(0),
|
||||
_threadId(0)
|
||||
{
|
||||
PVACCESS_REFCOUNT_MONITOR_CONSTRUCT(blockingUDPTransport);
|
||||
|
||||
// set receive timeout so that we do not have problems at shutdown (recvfrom would block)
|
||||
struct timeval timeout;
|
||||
memset(&timeout, 0, sizeof(struct timeval));
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
if (unlikely(::setsockopt (_channel, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0))
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError,
|
||||
"Failed to set SO_RCVTIMEO for UDP socket %s: %s.",
|
||||
inetAddressToString(_bindAddress).c_str(), errStr);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
BlockingUDPTransport::~BlockingUDPTransport() {
|
||||
PVACCESS_REFCOUNT_MONITOR_DESTRUCT(blockingUDPTransport);
|
||||
|
||||
close(true); // close the socket and stop the thread.
|
||||
|
||||
// TODO use auto_ptr class members
|
||||
|
||||
if (_sendAddresses) delete _sendAddresses;
|
||||
if (_ignoredAddresses) delete _ignoredAddresses;
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::start() {
|
||||
|
||||
String threadName = "UDP-receive "+inetAddressToString(_bindAddress);
|
||||
LOG(logLevelDebug, "Starting thread: %s",threadName.c_str());
|
||||
|
||||
_threadId = epicsThreadCreate(threadName.c_str(),
|
||||
epicsThreadPriorityMedium,
|
||||
epicsThreadGetStackSize(epicsThreadStackSmall),
|
||||
BlockingUDPTransport::threadRunner, this);
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::close() {
|
||||
close(true);
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::close(bool waitForThreadToComplete) {
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
if(_closed.get()) return;
|
||||
_closed.set();
|
||||
|
||||
LOG(logLevelDebug,
|
||||
"UDP socket %s closed.",
|
||||
inetAddressToString(_bindAddress).c_str());
|
||||
|
||||
epicsSocketSystemCallInterruptMechanismQueryInfo info =
|
||||
epicsSocketSystemCallInterruptMechanismQuery ();
|
||||
switch ( info ) {
|
||||
case esscimqi_socketCloseRequired:
|
||||
epicsSocketDestroy ( _channel );
|
||||
break;
|
||||
case esscimqi_socketBothShutdownRequired:
|
||||
{
|
||||
int status = ::shutdown ( _channel, SHUT_RDWR );
|
||||
if ( status ) {
|
||||
char sockErrBuf[64];
|
||||
epicsSocketConvertErrnoToString (
|
||||
sockErrBuf, sizeof ( sockErrBuf ) );
|
||||
LOG(logLevelDebug,
|
||||
"UDP socket %s failed to shutdown: %s.",
|
||||
inetAddressToString(_bindAddress).c_str(), sockErrBuf);
|
||||
}
|
||||
epicsSocketDestroy ( _channel );
|
||||
}
|
||||
break;
|
||||
case esscimqi_socketSigAlarmRequired:
|
||||
// TODO (not supported anymore anyway)
|
||||
default:
|
||||
epicsSocketDestroy(_channel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO send yourself a packet
|
||||
|
||||
// wait for send thread to exit cleanly
|
||||
if (waitForThreadToComplete)
|
||||
{
|
||||
if (!_shutdownEvent.wait(5.0))
|
||||
{
|
||||
LOG(logLevelError,
|
||||
"Receive thread for UDP socket %s has not exited.",
|
||||
inetAddressToString(_bindAddress).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::enqueueSendRequest(TransportSender::shared_pointer const & sender) {
|
||||
Lock lock(_sendMutex);
|
||||
|
||||
_sendToEnabled = false;
|
||||
_sendBuffer->clear();
|
||||
sender->lock();
|
||||
try {
|
||||
sender->send(_sendBuffer.get(), this);
|
||||
sender->unlock();
|
||||
endMessage();
|
||||
if(!_sendToEnabled)
|
||||
send(_sendBuffer.get());
|
||||
else
|
||||
send(_sendBuffer.get(), _sendTo);
|
||||
} catch(...) {
|
||||
sender->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BlockingUDPTransport::flushSendQueue()
|
||||
{
|
||||
// noop (note different sent addresses are possible)
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::startMessage(int8 command, size_t /*ensureCapacity*/) {
|
||||
_lastMessageStartPosition = _sendBuffer->getPosition();
|
||||
_sendBuffer->putByte(PVA_MAGIC);
|
||||
_sendBuffer->putByte(PVA_VERSION);
|
||||
_sendBuffer->putByte((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00); // data + 7-bit endianess
|
||||
_sendBuffer->putByte(command); // command
|
||||
_sendBuffer->putInt(0); // temporary zero payload
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::endMessage() {
|
||||
//we always (for now) send by packet, so no need for this here...
|
||||
//alignBuffer(PVA_ALIGNMENT);
|
||||
_sendBuffer->putInt(
|
||||
_lastMessageStartPosition+(sizeof(int16)+2),
|
||||
_sendBuffer->getPosition()-_lastMessageStartPosition-PVA_MESSAGE_HEADER_SIZE);
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::processRead() {
|
||||
// This function is always called from only one thread - this
|
||||
// object's own thread.
|
||||
|
||||
osiSockAddr fromAddress;
|
||||
osiSocklen_t addrStructSize = sizeof(sockaddr);
|
||||
Transport::shared_pointer thisTransport = shared_from_this();
|
||||
|
||||
try {
|
||||
|
||||
while(!_closed.get())
|
||||
{
|
||||
// we poll to prevent blocking indefinitely
|
||||
|
||||
// data ready to be read
|
||||
_receiveBuffer->clear();
|
||||
|
||||
int bytesRead = recvfrom(_channel, (char*)_receiveBuffer->getArray(),
|
||||
_receiveBuffer->getRemaining(), 0, (sockaddr*)&fromAddress,
|
||||
&addrStructSize);
|
||||
|
||||
if(likely(bytesRead>0)) {
|
||||
// successfully got datagram
|
||||
bool ignore = false;
|
||||
if(likely(_ignoredAddresses!=0))
|
||||
{
|
||||
for(size_t i = 0; i <_ignoredAddresses->size(); i++)
|
||||
{
|
||||
if((*_ignoredAddresses)[i].ia.sin_addr.s_addr==fromAddress.ia.sin_addr.s_addr)
|
||||
{
|
||||
ignore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(likely(!ignore)) {
|
||||
_receiveBuffer->setPosition(bytesRead);
|
||||
|
||||
_receiveBuffer->flip();
|
||||
|
||||
processBuffer(thisTransport, fromAddress, _receiveBuffer.get());
|
||||
}
|
||||
}
|
||||
else if (unlikely(bytesRead == -1)) {
|
||||
|
||||
int socketError = SOCKERRNO;
|
||||
|
||||
// interrupted or timeout
|
||||
if (socketError == SOCK_EINTR ||
|
||||
socketError == EAGAIN || // no alias in libCom
|
||||
// windows times out with this
|
||||
socketError == SOCK_ETIMEDOUT ||
|
||||
socketError == SOCK_EWOULDBLOCK)
|
||||
continue;
|
||||
|
||||
if (socketError == SOCK_ECONNREFUSED || // avoid spurious ECONNREFUSED in Linux
|
||||
socketError == SOCK_ECONNRESET) // or ECONNRESET in Windows
|
||||
continue;
|
||||
|
||||
// log a 'recvfrom' error
|
||||
if(!_closed.get())
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError, "Socket recvfrom error: %s", errStr);
|
||||
}
|
||||
|
||||
close(false);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
} catch(...) {
|
||||
// TODO: catch all exceptions, and act accordingly
|
||||
close(false);
|
||||
}
|
||||
|
||||
String threadName = "UDP-receive "+inetAddressToString(_bindAddress);
|
||||
/*
|
||||
char threadName[40];
|
||||
epicsThreadGetName(_threadId, threadName, 40);
|
||||
*/
|
||||
LOG(logLevelDebug, "Thread '%s' exiting", threadName.c_str());
|
||||
|
||||
_shutdownEvent.signal();
|
||||
}
|
||||
|
||||
bool BlockingUDPTransport::processBuffer(Transport::shared_pointer const & thisTransport, osiSockAddr& fromAddress, ByteBuffer* receiveBuffer) {
|
||||
|
||||
// handle response(s)
|
||||
while(likely((int)receiveBuffer->getRemaining()>=PVA_MESSAGE_HEADER_SIZE)) {
|
||||
//
|
||||
// read header
|
||||
//
|
||||
|
||||
// first byte is PVA_MAGIC
|
||||
int8 magic = receiveBuffer->getByte();
|
||||
if(unlikely(magic != PVA_MAGIC))
|
||||
return false;
|
||||
|
||||
// second byte version
|
||||
int8 version = receiveBuffer->getByte();
|
||||
|
||||
// only data for UDP
|
||||
int8 flags = receiveBuffer->getByte();
|
||||
if (flags < 0)
|
||||
{
|
||||
// 7-bit set
|
||||
receiveBuffer->setEndianess(EPICS_ENDIAN_BIG);
|
||||
}
|
||||
else
|
||||
{
|
||||
receiveBuffer->setEndianess(EPICS_ENDIAN_LITTLE);
|
||||
}
|
||||
|
||||
// command ID and paylaod
|
||||
int8 command = receiveBuffer->getByte();
|
||||
// TODO check this cast (size_t must be 32-bit)
|
||||
size_t payloadSize = receiveBuffer->getInt();
|
||||
size_t nextRequestPosition = receiveBuffer->getPosition() + payloadSize;
|
||||
|
||||
// payload size check
|
||||
if(unlikely(nextRequestPosition>receiveBuffer->getLimit())) return false;
|
||||
|
||||
// handle
|
||||
_responseHandler->handleResponse(&fromAddress, thisTransport,
|
||||
version, command, payloadSize,
|
||||
_receiveBuffer.get());
|
||||
|
||||
// set position (e.g. in case handler did not read all)
|
||||
receiveBuffer->setPosition(nextRequestPosition);
|
||||
}
|
||||
|
||||
//all ok
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockingUDPTransport::send(ByteBuffer* buffer, const osiSockAddr& address) {
|
||||
|
||||
buffer->flip();
|
||||
int retval = sendto(_channel, buffer->getArray(),
|
||||
buffer->getLimit(), 0, &(address.sa), sizeof(sockaddr));
|
||||
if(unlikely(retval<0))
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelDebug, "Socket sendto error: %s", errStr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockingUDPTransport::send(ByteBuffer* buffer) {
|
||||
if(!_sendAddresses) return false;
|
||||
|
||||
buffer->flip();
|
||||
|
||||
bool allOK = true;
|
||||
for(size_t i = 0; i<_sendAddresses->size(); i++) {
|
||||
int retval = sendto(_channel, buffer->getArray(),
|
||||
buffer->getLimit(), 0, &((*_sendAddresses)[i].sa),
|
||||
sizeof(sockaddr));
|
||||
if(unlikely(retval<0))
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelDebug, "Socket sendto error: %s", errStr);
|
||||
allOK = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allOK;
|
||||
}
|
||||
|
||||
size_t BlockingUDPTransport::getSocketReceiveBufferSize() const {
|
||||
// Get value of the SO_RCVBUF option for this DatagramSocket,
|
||||
// that is the buffer size used by the platform for input on
|
||||
// this DatagramSocket.
|
||||
|
||||
int sockBufSize = -1;
|
||||
osiSocklen_t intLen = sizeof(int);
|
||||
|
||||
int retval = getsockopt(_channel, SOL_SOCKET, SO_RCVBUF, (char *)&sockBufSize, &intLen);
|
||||
if(unlikely(retval<0))
|
||||
{
|
||||
char errStr[64];
|
||||
epicsSocketConvertErrnoToString(errStr, sizeof(errStr));
|
||||
LOG(logLevelError, "Socket getsockopt SO_RCVBUF error: %s", errStr);
|
||||
}
|
||||
|
||||
return (size_t)sockBufSize;
|
||||
}
|
||||
|
||||
void BlockingUDPTransport::threadRunner(void* param) {
|
||||
((BlockingUDPTransport*)param)->processRead();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef CHANNELSEARCHMANAGER_H
|
||||
#define CHANNELSEARCHMANAGER_H
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define channelSearchManagerEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <pv/remote.h>
|
||||
|
||||
#ifdef channelSearchManagerEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef channelSearchManagerEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
class SearchInstance {
|
||||
public:
|
||||
POINTER_DEFINITIONS(SearchInstance);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~SearchInstance() {};
|
||||
|
||||
virtual pvAccessID getSearchInstanceID() = 0;
|
||||
|
||||
virtual epics::pvData::String getSearchInstanceName() = 0;
|
||||
|
||||
virtual int32_t& getUserValue() = 0;
|
||||
|
||||
/**
|
||||
* Search response from server (channel found).
|
||||
* @param minorRevision server minor PVA revision.
|
||||
* @param serverAddress server address.
|
||||
*/
|
||||
// TODO make serverAddress an URI or similar
|
||||
virtual void searchResponse(int8_t minorRevision, osiSockAddr* serverAddress) = 0;
|
||||
};
|
||||
|
||||
class ChannelSearchManager {
|
||||
public:
|
||||
POINTER_DEFINITIONS(ChannelSearchManager);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~ChannelSearchManager() {};
|
||||
|
||||
/**
|
||||
* Get number of registered channels.
|
||||
* @return number of registered channels.
|
||||
*/
|
||||
virtual int32_t registeredCount() = 0;
|
||||
|
||||
/**
|
||||
* Register channel.
|
||||
* @param channel
|
||||
*/
|
||||
virtual void registerSearchInstance(SearchInstance::shared_pointer const & channel) = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Unregister channel.
|
||||
* @param channel
|
||||
*/
|
||||
virtual void unregisterSearchInstance(SearchInstance::shared_pointer const & channel) = 0;
|
||||
|
||||
/**
|
||||
* Search response from server (channel found).
|
||||
* @param cid client channel ID.
|
||||
* @param seqNo search sequence number.
|
||||
* @param minorRevision server minor PVA revision.
|
||||
* @param serverAddress server address.
|
||||
*/
|
||||
virtual void searchResponse(pvAccessID cid, int32_t seqNo, int8_t minorRevision, osiSockAddr* serverAddress) = 0;
|
||||
|
||||
/**
|
||||
* New server detected.
|
||||
* Boost searching of all channels.
|
||||
*/
|
||||
virtual void newServerDetected() = 0;
|
||||
|
||||
/**
|
||||
* Cancel.
|
||||
*/
|
||||
virtual void cancel() = 0;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,802 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef CODEC_H_
|
||||
#define CODEC_H_
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define abstractCodecEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <osdSock.h>
|
||||
#include <osiSock.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsThread.h>
|
||||
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/pvType.h>
|
||||
#include <pv/lock.h>
|
||||
#include <pv/timer.h>
|
||||
#include <pv/event.h>
|
||||
#include <pv/likely.h>
|
||||
|
||||
#ifdef abstractCodecEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef abstractCodecEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/remote.h>
|
||||
#include <pv/transportRegistry.h>
|
||||
#include <pv/introspectionRegistry.h>
|
||||
#include <pv/namedLockPattern.h>
|
||||
#include <pv/inetAddressUtil.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
namespace detail {
|
||||
|
||||
// TODO replace mutex with atomic (CAS) operations
|
||||
template<typename T>
|
||||
class AtomicValue
|
||||
{
|
||||
public:
|
||||
AtomicValue(): _value(0) {};
|
||||
|
||||
T getAndSet(T value)
|
||||
{
|
||||
mutex.lock();
|
||||
T tmp = _value; _value = value;
|
||||
mutex.unlock();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
T get() { mutex.lock(); T tmp = _value; mutex.unlock(); return tmp; }
|
||||
|
||||
private:
|
||||
T _value;
|
||||
epics::pvData::Mutex mutex;
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class queue {
|
||||
public:
|
||||
|
||||
queue(void) { }
|
||||
//TODO
|
||||
/*queue(queue const &T) = delete;
|
||||
queue(queue &&T) = delete;
|
||||
queue& operator=(const queue &T) = delete;
|
||||
*/
|
||||
~queue(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool empty(void)
|
||||
{
|
||||
epics::pvData::Lock lock(_queueMutex);
|
||||
return _queue.empty();
|
||||
}
|
||||
|
||||
void clean()
|
||||
{
|
||||
epics::pvData::Lock lock(_queueMutex);
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
|
||||
void wakeup()
|
||||
{
|
||||
if (!_wakeup.getAndSet(true))
|
||||
{
|
||||
_queueEvent.signal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void put(T const & elem)
|
||||
{
|
||||
{
|
||||
epics::pvData::Lock lock(_queueMutex);
|
||||
_queue.push_back(elem);
|
||||
}
|
||||
|
||||
_queueEvent.signal();
|
||||
}
|
||||
|
||||
|
||||
T take(int timeOut)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
||||
bool isEmpty = empty();
|
||||
|
||||
if (isEmpty)
|
||||
{
|
||||
|
||||
if (timeOut < 0) {
|
||||
return T();
|
||||
}
|
||||
|
||||
while (isEmpty)
|
||||
{
|
||||
|
||||
if (timeOut == 0) {
|
||||
_queueEvent.wait();
|
||||
}
|
||||
else {
|
||||
_queueEvent.wait(timeOut);
|
||||
}
|
||||
|
||||
isEmpty = empty();
|
||||
if (isEmpty)
|
||||
{
|
||||
if (timeOut > 0) { // TODO spurious wakeup, but not critical
|
||||
return T();
|
||||
}
|
||||
else // if (timeout == 0) cannot be negative
|
||||
{
|
||||
if (_wakeup.getAndSet(false)) {
|
||||
return T();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
epics::pvData::Lock lock(_queueMutex);
|
||||
T sender = _queue.front();
|
||||
_queue.pop_front();
|
||||
return sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::deque<T> _queue;
|
||||
epics::pvData::Event _queueEvent;
|
||||
epics::pvData::Mutex _queueMutex;
|
||||
AtomicValue<bool> _wakeup;
|
||||
epics::pvData::Mutex _stdMutex;
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass io_exception: public std::runtime_error {
|
||||
public:
|
||||
explicit io_exception(const std::string &s): std::runtime_error(s) {}
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass invalid_data_stream_exception: public std::runtime_error {
|
||||
public:
|
||||
explicit invalid_data_stream_exception(
|
||||
const std::string &s): std::runtime_error(s) {}
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass connection_closed_exception: public std::runtime_error {
|
||||
public:
|
||||
explicit connection_closed_exception(const std::string &s): std::runtime_error(s) {}
|
||||
};
|
||||
|
||||
|
||||
enum ReadMode { NORMAL, SPLIT, SEGMENTED };
|
||||
|
||||
enum WriteMode { PROCESS_SEND_QUEUE, WAIT_FOR_READY_SIGNAL };
|
||||
|
||||
|
||||
class epicsShareClass AbstractCodec :
|
||||
public TransportSendControl,
|
||||
public Transport
|
||||
{
|
||||
public:
|
||||
|
||||
static const std::size_t MAX_MESSAGE_PROCESS;
|
||||
static const std::size_t MAX_MESSAGE_SEND;
|
||||
static const std::size_t MAX_ENSURE_SIZE;
|
||||
static const std::size_t MAX_ENSURE_DATA_SIZE;
|
||||
static const std::size_t MAX_ENSURE_BUFFER_SIZE;
|
||||
static const std::size_t MAX_ENSURE_DATA_BUFFER_SIZE;
|
||||
|
||||
AbstractCodec(
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> const & receiveBuffer,
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> const & sendBuffer,
|
||||
int32_t socketSendBufferSize,
|
||||
bool blockingProcessQueue);
|
||||
|
||||
virtual void processControlMessage() = 0;
|
||||
virtual void processApplicationMessage() = 0;
|
||||
virtual const osiSockAddr* getLastReadBufferSocketAddress() = 0;
|
||||
virtual void invalidDataStreamHandler() = 0;
|
||||
virtual void readPollOne()=0;
|
||||
virtual void writePollOne() = 0;
|
||||
virtual void scheduleSend() = 0;
|
||||
virtual void sendCompleted() = 0;
|
||||
virtual bool terminated() = 0;
|
||||
virtual int write(epics::pvData::ByteBuffer* src) = 0;
|
||||
virtual int read(epics::pvData::ByteBuffer* dst) = 0;
|
||||
virtual bool isOpen() = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
|
||||
virtual ~AbstractCodec()
|
||||
{
|
||||
}
|
||||
|
||||
void alignBuffer(std::size_t alignment);
|
||||
void ensureData(std::size_t size);
|
||||
void alignData(std::size_t alignment);
|
||||
void startMessage(
|
||||
epics::pvData::int8 command,
|
||||
std::size_t ensureCapacity);
|
||||
void putControlMessage(
|
||||
epics::pvData::int8 command,
|
||||
epics::pvData::int32 data);
|
||||
void endMessage();
|
||||
void ensureBuffer(std::size_t size);
|
||||
void flushSerializeBuffer();
|
||||
void flush(bool lastMessageCompleted);
|
||||
void processWrite();
|
||||
void processRead();
|
||||
void processSendQueue();
|
||||
void clearSendQueue();
|
||||
void enqueueSendRequest(TransportSender::shared_pointer const & sender);
|
||||
void enqueueSendRequest(TransportSender::shared_pointer const & sender,
|
||||
std::size_t requiredBufferSize);
|
||||
void setSenderThread();
|
||||
void setRecipient(osiSockAddr const & sendTo);
|
||||
void setByteOrder(int byteOrder);
|
||||
|
||||
static std::size_t alignedValue(std::size_t value, std::size_t alignment);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void sendBufferFull(int tries) = 0;
|
||||
void send(epics::pvData::ByteBuffer *buffer);
|
||||
|
||||
|
||||
ReadMode _readMode;
|
||||
int8_t _version;
|
||||
int8_t _flags;
|
||||
int8_t _command;
|
||||
int32_t _payloadSize; // TODO why not size_t?
|
||||
epics::pvData::int32 _remoteTransportSocketReceiveBufferSize;
|
||||
int64_t _totalBytesSent;
|
||||
bool _blockingProcessQueue;
|
||||
//TODO initialize union
|
||||
osiSockAddr _sendTo;
|
||||
epicsThreadId _senderThread;
|
||||
WriteMode _writeMode;
|
||||
bool _writeOpReady;
|
||||
bool _lowLatency;
|
||||
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> _socketBuffer;
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> _sendBuffer;
|
||||
|
||||
queue<TransportSender::shared_pointer> _sendQueue;
|
||||
|
||||
private:
|
||||
|
||||
void processHeader();
|
||||
void processReadNormal();
|
||||
void postProcessApplicationMessage();
|
||||
void processReadSegmented();
|
||||
bool readToBuffer(std::size_t requiredBytes, bool persistent);
|
||||
void endMessage(bool hasMoreSegments);
|
||||
void processSender(
|
||||
epics::pvAccess::TransportSender::shared_pointer const & sender);
|
||||
|
||||
std::size_t _storedPayloadSize;
|
||||
std::size_t _storedPosition;
|
||||
std::size_t _storedLimit;
|
||||
std::size_t _startPosition;
|
||||
|
||||
std::size_t _maxSendPayloadSize;
|
||||
std::size_t _lastMessageStartPosition;
|
||||
std::size_t _lastSegmentedMessageType;
|
||||
int8_t _lastSegmentedMessageCommand;
|
||||
std::size_t _nextMessagePayloadOffset;
|
||||
|
||||
epics::pvData::int8 _byteOrderFlag;
|
||||
int32_t _socketSendBufferSize;
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass BlockingAbstractCodec:
|
||||
public AbstractCodec,
|
||||
public std::tr1::enable_shared_from_this<BlockingAbstractCodec>
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
POINTER_DEFINITIONS(BlockingAbstractCodec);
|
||||
|
||||
BlockingAbstractCodec(
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> const & receiveBuffer,
|
||||
std::tr1::shared_ptr<epics::pvData::ByteBuffer> const & sendBuffer,
|
||||
int32_t socketSendBufferSize):
|
||||
AbstractCodec(receiveBuffer, sendBuffer, socketSendBufferSize, true),
|
||||
_readThread(0), _sendThread(0) { _isOpen.getAndSet(true);}
|
||||
|
||||
void readPollOne();
|
||||
void writePollOne();
|
||||
void scheduleSend() {}
|
||||
void sendCompleted() {}
|
||||
void close();
|
||||
bool terminated();
|
||||
bool isOpen();
|
||||
void start();
|
||||
|
||||
static void receiveThread(void* param);
|
||||
static void sendThread(void* param);
|
||||
|
||||
protected:
|
||||
void sendBufferFull(int tries);
|
||||
virtual void internalDestroy() = 0;
|
||||
|
||||
/**
|
||||
* Called to any resources just before closing transport
|
||||
* @param[in] force flag indicating if forced (e.g. forced
|
||||
* disconnect) is required
|
||||
*/
|
||||
virtual void internalClose(bool force);
|
||||
|
||||
/**
|
||||
* Called to any resources just after closing transport and without any locks held on transport
|
||||
* @param[in] force flag indicating if forced (e.g. forced
|
||||
* disconnect) is required
|
||||
*/
|
||||
virtual void internalPostClose(bool force);
|
||||
|
||||
private:
|
||||
AtomicValue<bool> _isOpen;
|
||||
volatile epicsThreadId _readThread;
|
||||
volatile epicsThreadId _sendThread;
|
||||
epics::pvData::Event _shutdownEvent;
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass BlockingSocketAbstractCodec:
|
||||
public BlockingAbstractCodec
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
BlockingSocketAbstractCodec(
|
||||
SOCKET channel,
|
||||
int32_t sendBufferSize,
|
||||
int32_t receiveBufferSize);
|
||||
|
||||
int read(epics::pvData::ByteBuffer* dst);
|
||||
int write(epics::pvData::ByteBuffer* src);
|
||||
const osiSockAddr* getLastReadBufferSocketAddress() { return &_socketAddress; }
|
||||
void invalidDataStreamHandler();
|
||||
std::size_t getSocketReceiveBufferSize() const;
|
||||
|
||||
protected:
|
||||
|
||||
void internalDestroy();
|
||||
|
||||
SOCKET _channel;
|
||||
osiSockAddr _socketAddress;
|
||||
};
|
||||
|
||||
|
||||
class BlockingTCPTransportCodec :
|
||||
public BlockingSocketAbstractCodec
|
||||
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
epics::pvData::String getType() const {
|
||||
return epics::pvData::String("TCP");
|
||||
}
|
||||
|
||||
|
||||
void internalDestroy() {
|
||||
BlockingSocketAbstractCodec::internalDestroy();
|
||||
Transport::shared_pointer thisSharedPtr = this->shared_from_this();
|
||||
_context->getTransportRegistry()->remove(thisSharedPtr);
|
||||
}
|
||||
|
||||
|
||||
void changedTransport() {}
|
||||
|
||||
|
||||
void processControlMessage() {
|
||||
if (_command == 2)
|
||||
{
|
||||
// check 7-th bit
|
||||
setByteOrder(_flags < 0 ? EPICS_ENDIAN_BIG : EPICS_ENDIAN_LITTLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void processApplicationMessage() {
|
||||
_responseHandler->handleResponse(&_socketAddress, shared_from_this(),
|
||||
_version, _command, _payloadSize, _socketBuffer.get());
|
||||
}
|
||||
|
||||
|
||||
const osiSockAddr* getRemoteAddress() const {
|
||||
return &_socketAddress;
|
||||
}
|
||||
|
||||
|
||||
epics::pvData::int8 getRevision() const {
|
||||
return PVA_PROTOCOL_REVISION;
|
||||
}
|
||||
|
||||
|
||||
std::size_t getReceiveBufferSize() const {
|
||||
return _socketBuffer->getSize();
|
||||
}
|
||||
|
||||
|
||||
epics::pvData::int16 getPriority() const {
|
||||
return _priority;
|
||||
}
|
||||
|
||||
|
||||
void setRemoteRevision(epics::pvData::int8 revision) {
|
||||
_remoteTransportRevision = revision;
|
||||
}
|
||||
|
||||
|
||||
void setRemoteTransportReceiveBufferSize(
|
||||
std::size_t remoteTransportReceiveBufferSize) {
|
||||
_remoteTransportReceiveBufferSize = remoteTransportReceiveBufferSize;
|
||||
}
|
||||
|
||||
|
||||
void setRemoteTransportSocketReceiveBufferSize(
|
||||
std::size_t socketReceiveBufferSize) {
|
||||
_remoteTransportSocketReceiveBufferSize = socketReceiveBufferSize;
|
||||
}
|
||||
|
||||
|
||||
std::tr1::shared_ptr<const epics::pvData::Field>
|
||||
cachedDeserialize(epics::pvData::ByteBuffer* buffer)
|
||||
{
|
||||
return _incomingIR.deserialize(buffer, this);
|
||||
}
|
||||
|
||||
|
||||
void cachedSerialize(
|
||||
const std::tr1::shared_ptr<const epics::pvData::Field>& field,
|
||||
epics::pvData::ByteBuffer* buffer)
|
||||
{
|
||||
_outgoingIR.serialize(field, buffer, this);
|
||||
}
|
||||
|
||||
|
||||
bool directSerialize(
|
||||
epics::pvData::ByteBuffer * /*existingBuffer*/,
|
||||
const char* /*toSerialize*/,
|
||||
std::size_t /*elementCount*/, std::size_t /*elementSize*/)
|
||||
{
|
||||
// TODO !!!!
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool directDeserialize(epics::pvData::ByteBuffer * /*existingBuffer*/,
|
||||
char* /*deserializeTo*/,
|
||||
std::size_t /*elementCount*/, std::size_t /*elementSize*/) {
|
||||
// TODO !!!
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void flushSendQueue() { };
|
||||
|
||||
|
||||
bool isClosed() {
|
||||
return !isOpen();
|
||||
}
|
||||
|
||||
|
||||
void activate() {
|
||||
Transport::shared_pointer thisSharedPtr = shared_from_this();
|
||||
_context->getTransportRegistry()->put(thisSharedPtr);
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
BlockingTCPTransportCodec(
|
||||
Context::shared_pointer const & context,
|
||||
SOCKET channel,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
int32_t sendBufferSize,
|
||||
int32_t receiveBufferSize,
|
||||
epics::pvData::int16 priority
|
||||
):
|
||||
BlockingSocketAbstractCodec(channel, sendBufferSize, receiveBufferSize),
|
||||
_context(context), _responseHandler(responseHandler),
|
||||
_remoteTransportReceiveBufferSize(MAX_TCP_RECV),
|
||||
_remoteTransportRevision(0), _priority(priority)
|
||||
{
|
||||
}
|
||||
|
||||
Context::shared_pointer _context;
|
||||
|
||||
IntrospectionRegistry _incomingIR;
|
||||
IntrospectionRegistry _outgoingIR;
|
||||
|
||||
private:
|
||||
|
||||
std::auto_ptr<ResponseHandler> _responseHandler;
|
||||
size_t _remoteTransportReceiveBufferSize;
|
||||
epics::pvData::int8 _remoteTransportRevision;
|
||||
epics::pvData::int16 _priority;
|
||||
};
|
||||
|
||||
|
||||
class epicsShareClass BlockingServerTCPTransportCodec :
|
||||
public BlockingTCPTransportCodec,
|
||||
public ChannelHostingTransport,
|
||||
public TransportSender {
|
||||
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingServerTCPTransportCodec);
|
||||
|
||||
protected:
|
||||
BlockingServerTCPTransportCodec(
|
||||
Context::shared_pointer const & context,
|
||||
SOCKET channel,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
int32_t sendBufferSize,
|
||||
int32_t receiveBufferSize );
|
||||
|
||||
public:
|
||||
static shared_pointer create(
|
||||
Context::shared_pointer const & context,
|
||||
SOCKET channel,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
int sendBufferSize,
|
||||
int receiveBufferSize)
|
||||
{
|
||||
shared_pointer thisPointer(
|
||||
new BlockingServerTCPTransportCodec(
|
||||
context, channel, responseHandler,
|
||||
sendBufferSize, receiveBufferSize)
|
||||
);
|
||||
thisPointer->activate();
|
||||
return thisPointer;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool acquire(std::tr1::shared_ptr<TransportClient> const & client)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void release(pvAccessID /*clientId*/) {}
|
||||
|
||||
pvAccessID preallocateChannelSID();
|
||||
|
||||
void depreallocateChannelSID(pvAccessID /*sid*/) {
|
||||
// noop
|
||||
}
|
||||
|
||||
void registerChannel(
|
||||
pvAccessID sid,
|
||||
ServerChannel::shared_pointer const & channel);
|
||||
|
||||
void unregisterChannel(pvAccessID sid);
|
||||
|
||||
ServerChannel::shared_pointer getChannel(pvAccessID sid);
|
||||
|
||||
int getChannelCount();
|
||||
|
||||
epics::pvData::PVField::shared_pointer getSecurityToken() {
|
||||
return epics::pvData::PVField::shared_pointer();
|
||||
}
|
||||
|
||||
void lock() {
|
||||
// noop
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
// noop
|
||||
}
|
||||
|
||||
bool verify(epics::pvData::int32 timeoutMs) {
|
||||
TransportSender::shared_pointer transportSender =
|
||||
std::tr1::dynamic_pointer_cast<TransportSender>(shared_from_this());
|
||||
enqueueSendRequest(transportSender);
|
||||
verified();
|
||||
return true;
|
||||
}
|
||||
|
||||
void verified() {
|
||||
}
|
||||
|
||||
void aliveNotification() {
|
||||
// noop on server-side
|
||||
}
|
||||
|
||||
void send(epics::pvData::ByteBuffer* buffer,
|
||||
TransportSendControl* control);
|
||||
|
||||
virtual ~BlockingServerTCPTransportCodec();
|
||||
|
||||
protected:
|
||||
|
||||
void destroyAllChannels();
|
||||
virtual void internalClose(bool force);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Last SID cache.
|
||||
*/
|
||||
pvAccessID _lastChannelSID;
|
||||
|
||||
/**
|
||||
* Channel table (SID -> channel mapping).
|
||||
*/
|
||||
std::map<pvAccessID, ServerChannel::shared_pointer> _channels;
|
||||
|
||||
epics::pvData::Mutex _channelsMutex;
|
||||
|
||||
};
|
||||
|
||||
class epicsShareClass BlockingClientTCPTransportCodec :
|
||||
public BlockingTCPTransportCodec,
|
||||
public TransportSender,
|
||||
public epics::pvData::TimerCallback {
|
||||
|
||||
public:
|
||||
POINTER_DEFINITIONS(BlockingClientTCPTransportCodec);
|
||||
|
||||
protected:
|
||||
BlockingClientTCPTransportCodec(
|
||||
Context::shared_pointer const & context,
|
||||
SOCKET channel,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
int32_t sendBufferSize,
|
||||
int32_t receiveBufferSize,
|
||||
TransportClient::shared_pointer const & client,
|
||||
epics::pvData::int8 remoteTransportRevision,
|
||||
float beaconInterval,
|
||||
int16_t priority);
|
||||
|
||||
public:
|
||||
static shared_pointer create(
|
||||
Context::shared_pointer const & context,
|
||||
SOCKET channel,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler,
|
||||
int32_t sendBufferSize,
|
||||
int32_t receiveBufferSize,
|
||||
TransportClient::shared_pointer const & client,
|
||||
int8_t remoteTransportRevision,
|
||||
float beaconInterval,
|
||||
int16_t priority )
|
||||
{
|
||||
shared_pointer thisPointer(
|
||||
new BlockingClientTCPTransportCodec(
|
||||
context, channel, responseHandler,
|
||||
sendBufferSize, receiveBufferSize,
|
||||
client, remoteTransportRevision,
|
||||
beaconInterval, priority)
|
||||
);
|
||||
thisPointer->activate();
|
||||
return thisPointer;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void start();
|
||||
|
||||
virtual ~BlockingClientTCPTransportCodec();
|
||||
|
||||
virtual void timerStopped() {
|
||||
// noop
|
||||
}
|
||||
|
||||
virtual void callback();
|
||||
|
||||
bool acquire(TransportClient::shared_pointer const & client);
|
||||
|
||||
void release(pvAccessID clientId);
|
||||
|
||||
void changedTransport();
|
||||
|
||||
void lock() {
|
||||
// noop
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
// noop
|
||||
}
|
||||
|
||||
bool verify(epics::pvData::int32 timeoutMs);
|
||||
|
||||
void verified();
|
||||
|
||||
void aliveNotification();
|
||||
|
||||
void send(epics::pvData::ByteBuffer* buffer,
|
||||
TransportSendControl* control);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void internalClose(bool force);
|
||||
virtual void internalPostClose(bool force);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Owners (users) of the transport.
|
||||
*/
|
||||
// TODO consider using TR1 hash map
|
||||
typedef std::map<pvAccessID, TransportClient::weak_pointer> TransportClientMap_t;
|
||||
TransportClientMap_t _owners;
|
||||
|
||||
/**
|
||||
* Connection timeout (no-traffic) flag.
|
||||
*/
|
||||
double _connectionTimeout;
|
||||
|
||||
/**
|
||||
* Unresponsive transport flag.
|
||||
*/
|
||||
bool _unresponsiveTransport;
|
||||
|
||||
/**
|
||||
* Timestamp of last "live" event on this transport.
|
||||
*/
|
||||
epicsTimeStamp _aliveTimestamp;
|
||||
|
||||
bool _verifyOrEcho;
|
||||
|
||||
/**
|
||||
* Unresponsive transport notify.
|
||||
*/
|
||||
void unresponsiveTransport();
|
||||
|
||||
/**
|
||||
* Notifies clients about disconnect.
|
||||
*/
|
||||
void closedNotifyClients();
|
||||
|
||||
/**
|
||||
* Responsive transport notify.
|
||||
*/
|
||||
void responsiveTransport();
|
||||
|
||||
|
||||
epics::pvData::Mutex _mutex;
|
||||
|
||||
bool _verified;
|
||||
epics::pvData::Mutex _verifiedMutex;
|
||||
epics::pvData::Event _verifiedEvent;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CODEC_H_ */
|
||||
@@ -0,0 +1,612 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_H_
|
||||
#define REMOTE_H_
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define remoteEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <osiSock.h>
|
||||
#include <osdSock.h>
|
||||
|
||||
#include <pv/serialize.h>
|
||||
#include <pv/pvType.h>
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/timer.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#ifdef remoteEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef remoteEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/configuration.h>
|
||||
|
||||
/// TODO only here because of the Lockable
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
#define PVACCESS_REFCOUNT_MONITOR_DEFINE(name)
|
||||
#define PVACCESS_REFCOUNT_MONITOR_CONSTRUCT(name)
|
||||
#define PVACCESS_REFCOUNT_MONITOR_DESTRUCT(name)
|
||||
|
||||
class TransportRegistry;
|
||||
|
||||
enum QoS {
|
||||
/**
|
||||
* Default behavior.
|
||||
*/
|
||||
QOS_DEFAULT = 0x00,
|
||||
/**
|
||||
* Require reply (acknowledgment for reliable operation).
|
||||
*/
|
||||
QOS_REPLY_REQUIRED = 0x01,
|
||||
/**
|
||||
* Best-effort option (no reply).
|
||||
*/
|
||||
QOS_BESY_EFFORT = 0x02,
|
||||
/**
|
||||
* Process option.
|
||||
*/
|
||||
QOS_PROCESS = 0x04,
|
||||
/**
|
||||
* Initialize option.
|
||||
*/
|
||||
QOS_INIT = 0x08,
|
||||
/**
|
||||
* Destroy option.
|
||||
*/
|
||||
QOS_DESTROY = 0x10,
|
||||
/**
|
||||
* Share data option.
|
||||
*/
|
||||
QOS_SHARE = 0x20,
|
||||
/**
|
||||
* Get.
|
||||
*/
|
||||
QOS_GET = 0x40,
|
||||
/**
|
||||
* Get-put.
|
||||
*/
|
||||
QOS_GET_PUT = 0x80
|
||||
};
|
||||
|
||||
typedef epics::pvData::int32 pvAccessID;
|
||||
|
||||
enum ApplicationCommands {
|
||||
CMD_BEACON = 0,
|
||||
CMD_CONNECTION_VALIDATION = 1,
|
||||
CMD_ECHO = 2,
|
||||
CMD_SEARCH = 3,
|
||||
CMD_SEARCH_RESPONSE = 4,
|
||||
CMD_INTROSPECTION_SEARCH = 5,
|
||||
CMD_INTROSPECTION_SEARCH_RESPONSE = 6,
|
||||
CMD_CREATE_CHANNEL = 7,
|
||||
CMD_DESTROY_CHANNEL = 8,
|
||||
CMD_RESERVED0 = 9,
|
||||
CMD_GET = 10,
|
||||
CMD_PUT = 11,
|
||||
CMD_PUT_GET = 12,
|
||||
CMD_MONITOR = 13,
|
||||
CMD_ARRAY = 14,
|
||||
CMD_CANCEL_REQUEST = 15,
|
||||
CMD_PROCESS = 16,
|
||||
CMD_GET_FIELD = 17,
|
||||
CMD_MESSAGE = 18,
|
||||
CMD_MULTIPLE_DATA = 19,
|
||||
CMD_RPC = 20
|
||||
};
|
||||
|
||||
enum ControlCommands {
|
||||
CMD_SET_MARKER = 0,
|
||||
CMD_ACK_MARKER = 1,
|
||||
CMD_SET_ENDIANESS = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface defining transport send control.
|
||||
*/
|
||||
class TransportSendControl : public epics::pvData::SerializableControl {
|
||||
public:
|
||||
POINTER_DEFINITIONS(TransportSendControl);
|
||||
|
||||
virtual ~TransportSendControl() {}
|
||||
|
||||
virtual void startMessage(epics::pvData::int8 command, std::size_t ensureCapacity) = 0;
|
||||
virtual void endMessage() = 0;
|
||||
|
||||
virtual void flush(bool lastMessageCompleted) = 0;
|
||||
|
||||
virtual void setRecipient(osiSockAddr const & sendTo) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface defining transport sender (instance sending data over transport).
|
||||
*/
|
||||
class TransportSender : public Lockable {
|
||||
public:
|
||||
POINTER_DEFINITIONS(TransportSender);
|
||||
|
||||
virtual ~TransportSender() {}
|
||||
|
||||
/**
|
||||
* Called by transport.
|
||||
* By this call transport gives callee ownership over the buffer.
|
||||
* Calls on <code>TransportSendControl</code> instance must be made from
|
||||
* calling thread. Moreover, ownership is valid only for the time of call
|
||||
* of this method.
|
||||
* NOTE: these limitations allow efficient implementation.
|
||||
*/
|
||||
virtual void send(epics::pvData::ByteBuffer* buffer, TransportSendControl* control) = 0;
|
||||
};
|
||||
|
||||
class TransportClient;
|
||||
|
||||
/**
|
||||
* Interface defining transport (connection).
|
||||
*/
|
||||
class Transport : public epics::pvData::DeserializableControl {
|
||||
public:
|
||||
POINTER_DEFINITIONS(Transport);
|
||||
|
||||
virtual ~Transport() {}
|
||||
|
||||
/**
|
||||
* Acquires transport.
|
||||
* @param client client (channel) acquiring the transport
|
||||
* @return <code>true</code> if transport was granted, <code>false</code> otherwise.
|
||||
*/
|
||||
//virtual bool acquire(TransportClient::shared_pointer const & client) = 0;
|
||||
virtual bool acquire(std::tr1::shared_ptr<TransportClient> const & client) = 0;
|
||||
|
||||
/**
|
||||
* Releases transport.
|
||||
* @param client client (channel) releasing the transport
|
||||
*/
|
||||
virtual void release(pvAccessID clientId) = 0;
|
||||
//virtual void release(TransportClient::shared_pointer const & client) = 0;
|
||||
|
||||
/**
|
||||
* Get protocol type (tcp, udp, ssl, etc.).
|
||||
* @return protocol type.
|
||||
*/
|
||||
virtual epics::pvData::String getType() const = 0;
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
* @return remote address, can be null.
|
||||
*/
|
||||
virtual const osiSockAddr* getRemoteAddress() const = 0;
|
||||
|
||||
// TODO getContext?
|
||||
|
||||
/**
|
||||
* Transport protocol minor revision.
|
||||
* @return protocol minor revision.
|
||||
*/
|
||||
virtual epics::pvData::int8 getRevision() const = 0;
|
||||
|
||||
/**
|
||||
* Get receive buffer size.
|
||||
* @return receive buffer size.
|
||||
*/
|
||||
virtual std::size_t getReceiveBufferSize() const = 0;
|
||||
|
||||
/**
|
||||
* Get socket receive buffer size.
|
||||
* @return socket receive buffer size.
|
||||
*/
|
||||
virtual std::size_t getSocketReceiveBufferSize() const = 0;
|
||||
|
||||
/**
|
||||
* Transport priority.
|
||||
* @return protocol priority.
|
||||
*/
|
||||
virtual epics::pvData::int16 getPriority() const = 0;
|
||||
|
||||
/**
|
||||
* Set remote transport protocol revision.
|
||||
* @param revision protocol revision.
|
||||
*/
|
||||
virtual void setRemoteRevision(epics::pvData::int8 revision) = 0;
|
||||
|
||||
/**
|
||||
* Set remote transport receive buffer size.
|
||||
* @param receiveBufferSize receive buffer size.
|
||||
*/
|
||||
virtual void setRemoteTransportReceiveBufferSize(std::size_t receiveBufferSize) = 0;
|
||||
|
||||
/**
|
||||
* Set remote transport socket receive buffer size.
|
||||
* @param socketReceiveBufferSize remote socket receive buffer size.
|
||||
*/
|
||||
virtual void setRemoteTransportSocketReceiveBufferSize(std::size_t socketReceiveBufferSize) = 0;
|
||||
|
||||
/**
|
||||
* Set byte order.
|
||||
* @param byteOrder byte order to set.
|
||||
*/
|
||||
// TODO enum
|
||||
virtual void setByteOrder(int byteOrder) = 0;
|
||||
|
||||
/**
|
||||
* Notification that transport has changed.
|
||||
*/
|
||||
virtual void changedTransport() = 0;
|
||||
|
||||
/**
|
||||
* Enqueue send request.
|
||||
* @param sender
|
||||
*/
|
||||
virtual void enqueueSendRequest(TransportSender::shared_pointer const & sender) = 0;
|
||||
|
||||
/**
|
||||
* Flush send queue (sent messages).
|
||||
*/
|
||||
virtual void flushSendQueue() = 0;
|
||||
|
||||
/**
|
||||
* Notify transport that it is has been verified.
|
||||
*/
|
||||
virtual void verified() = 0;
|
||||
|
||||
/**
|
||||
* Waits (if needed) until transport is verified, i.e. verified() method is being called.
|
||||
* @param timeoutMs timeout to wait for verification, infinite if 0.
|
||||
*/
|
||||
virtual bool verify(epics::pvData::int32 timeoutMs) = 0;
|
||||
|
||||
/**
|
||||
* Notification transport that is still alive.
|
||||
*/
|
||||
virtual void aliveNotification() = 0;
|
||||
|
||||
/**
|
||||
* Close transport.
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* Check connection status.
|
||||
* @return <code>true</code> if connected.
|
||||
*/
|
||||
virtual bool isClosed() = 0;
|
||||
};
|
||||
|
||||
class Channel;
|
||||
|
||||
/**
|
||||
* Not public IF, used by Transports, etc.
|
||||
*/
|
||||
class Context {
|
||||
public:
|
||||
POINTER_DEFINITIONS(Context);
|
||||
|
||||
virtual ~Context() {}
|
||||
|
||||
virtual epics::pvData::Timer::shared_pointer getTimer() = 0;
|
||||
|
||||
//virtual TransportRegistry::shared_pointer getTransportRegistry() = 0;
|
||||
virtual std::tr1::shared_ptr<TransportRegistry> getTransportRegistry() = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
virtual Configuration::shared_pointer getConfiguration() = 0;
|
||||
|
||||
|
||||
|
||||
///
|
||||
/// due to ClientContextImpl
|
||||
///
|
||||
|
||||
virtual void newServerDetected() = 0;
|
||||
|
||||
virtual std::tr1::shared_ptr<Channel> getChannel(pvAccessID id) = 0;
|
||||
virtual Transport::shared_pointer getSearchTransport() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface defining response handler.
|
||||
*/
|
||||
class ResponseHandler {
|
||||
public:
|
||||
POINTER_DEFINITIONS(ResponseHandler);
|
||||
|
||||
virtual ~ResponseHandler() {}
|
||||
|
||||
/**
|
||||
* Handle response.
|
||||
* @param[in] responseFrom remote address of the responder, <code>0</code> if unknown.
|
||||
* @param[in] transport response source transport.
|
||||
* @param[in] version message version.
|
||||
* @param[in] payloadSize size of this message data available in the <code>payloadBuffer</code>.
|
||||
* @param[in] payloadBuffer message payload data.
|
||||
* Note that this might not be the only message in the buffer.
|
||||
* Code must not manipulate buffer.
|
||||
*/
|
||||
virtual void
|
||||
handleResponse(osiSockAddr* responseFrom, Transport::shared_pointer const & transport,
|
||||
epics::pvData::int8 version, epics::pvData::int8 command, std::size_t payloadSize,
|
||||
epics::pvData::ByteBuffer* payloadBuffer) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base (abstract) channel access response handler.
|
||||
*/
|
||||
class AbstractResponseHandler : public ResponseHandler {
|
||||
public:
|
||||
/**
|
||||
* @param description
|
||||
*/
|
||||
AbstractResponseHandler(Context* context, epics::pvData::String description) :
|
||||
_description(description),
|
||||
_debug(context->getConfiguration()->getPropertyAsBoolean(PVACCESS_DEBUG, false)) {
|
||||
}
|
||||
|
||||
virtual ~AbstractResponseHandler() {}
|
||||
|
||||
virtual void handleResponse(osiSockAddr* responseFrom, Transport::shared_pointer const & transport,
|
||||
epics::pvData::int8 version, epics::pvData::int8 command, std::size_t payloadSize,
|
||||
epics::pvData::ByteBuffer* payloadBuffer);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Response hanlder description.
|
||||
*/
|
||||
epics::pvData::String _description;
|
||||
|
||||
/**
|
||||
* Debug flag.
|
||||
*/
|
||||
bool _debug;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client (user) of the transport.
|
||||
*/
|
||||
class TransportClient {
|
||||
public:
|
||||
POINTER_DEFINITIONS(TransportClient);
|
||||
|
||||
virtual ~TransportClient() {
|
||||
}
|
||||
|
||||
// ID used to allow fast/efficient lookup
|
||||
virtual pvAccessID getID() = 0;
|
||||
|
||||
/**
|
||||
* Notification of unresponsive transport (e.g. no heartbeat detected) .
|
||||
*/
|
||||
virtual void transportUnresponsive() = 0;
|
||||
|
||||
/**
|
||||
* Notification of responsive transport (e.g. heartbeat detected again),
|
||||
* called to discard <code>transportUnresponsive</code> notification.
|
||||
* @param transport responsive transport.
|
||||
*/
|
||||
virtual void transportResponsive(Transport::shared_pointer const & transport) = 0;
|
||||
|
||||
/**
|
||||
* Notification of network change (server restarted).
|
||||
*/
|
||||
virtual void transportChanged() = 0;
|
||||
|
||||
/**
|
||||
* Notification of forcefully closed transport.
|
||||
*/
|
||||
virtual void transportClosed() = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface defining socket connector (Connector-Transport pattern).
|
||||
*/
|
||||
class Connector {
|
||||
public:
|
||||
virtual ~Connector() {}
|
||||
|
||||
/**
|
||||
* Connect.
|
||||
* @param[in] client client requesting connection (transport).
|
||||
* @param[in] address address of the server.
|
||||
* @param[in] responseHandler reponse handler.
|
||||
* @param[in] transportRevision transport revision to be used.
|
||||
* @param[in] priority process priority.
|
||||
* @return transport instance.
|
||||
*/
|
||||
virtual Transport::shared_pointer connect(TransportClient::shared_pointer const & client,
|
||||
std::auto_ptr<ResponseHandler>& responseHandler, osiSockAddr& address,
|
||||
epics::pvData::int8 transportRevision, epics::pvData::int16 priority) = 0;
|
||||
|
||||
};
|
||||
|
||||
class ServerChannel {
|
||||
public:
|
||||
POINTER_DEFINITIONS(ServerChannel);
|
||||
|
||||
virtual ~ServerChannel() {}
|
||||
/**
|
||||
* Get channel SID.
|
||||
* @return channel SID.
|
||||
*/
|
||||
virtual pvAccessID getSID() const = 0;
|
||||
|
||||
/**
|
||||
* Destroy server channel.
|
||||
* This method MUST BE called if overriden.
|
||||
*/
|
||||
virtual void destroy() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface defining a transport that hosts server channels.
|
||||
*/
|
||||
class ChannelHostingTransport {
|
||||
public:
|
||||
POINTER_DEFINITIONS(ChannelHostingTransport);
|
||||
|
||||
virtual ~ChannelHostingTransport() {}
|
||||
|
||||
/**
|
||||
* Get security token.
|
||||
* @return security token, can be <code>null</code>.
|
||||
*/
|
||||
virtual epics::pvData::PVField::shared_pointer getSecurityToken() = 0;
|
||||
|
||||
/**
|
||||
* Preallocate new channel SID.
|
||||
* @return new channel server id (SID).
|
||||
*/
|
||||
virtual pvAccessID preallocateChannelSID() = 0;
|
||||
|
||||
/**
|
||||
* De-preallocate new channel SID.
|
||||
* @param sid preallocated channel SID.
|
||||
*/
|
||||
virtual void depreallocateChannelSID(pvAccessID sid) = 0;
|
||||
|
||||
/**
|
||||
* Register a new channel.
|
||||
* @param sid preallocated channel SID.
|
||||
* @param channel channel to register.
|
||||
*/
|
||||
virtual void registerChannel(pvAccessID sid, ServerChannel::shared_pointer const & channel) =0;
|
||||
|
||||
/**
|
||||
* Unregister a new channel (and deallocates its handle).
|
||||
* @param sid SID
|
||||
*/
|
||||
virtual void unregisterChannel(pvAccessID sid) = 0;
|
||||
|
||||
/**
|
||||
* Get channel by its SID.
|
||||
* @param sid channel SID
|
||||
* @return channel with given SID, <code>null</code> otherwise
|
||||
*/
|
||||
virtual ServerChannel::shared_pointer getChannel(pvAccessID sid) = 0;
|
||||
|
||||
/**
|
||||
* Get channel count.
|
||||
* @return channel count.
|
||||
*/
|
||||
virtual int getChannelCount() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A request that expects an response.
|
||||
* Responses identified by its I/O ID.
|
||||
*/
|
||||
class ResponseRequest {
|
||||
public:
|
||||
POINTER_DEFINITIONS(ResponseRequest);
|
||||
|
||||
virtual ~ResponseRequest() {}
|
||||
|
||||
/**
|
||||
* Get I/O ID.
|
||||
* @return ioid
|
||||
*/
|
||||
virtual pvAccessID getIOID() const = 0;
|
||||
|
||||
/**
|
||||
* Timeout notification.
|
||||
*/
|
||||
virtual void timeout() = 0;
|
||||
|
||||
/**
|
||||
* Cancel response request (always to be called to complete/destroy).
|
||||
*/
|
||||
virtual void cancel() = 0;
|
||||
|
||||
/**
|
||||
* Report status to clients (e.g. disconnected).
|
||||
* @param status to report.
|
||||
*/
|
||||
virtual void reportStatus(epics::pvData::Status const & status) = 0;
|
||||
|
||||
/**
|
||||
* Get request requester.
|
||||
* @return request requester.
|
||||
*/
|
||||
virtual std::tr1::shared_ptr<epics::pvData::Requester> getRequester() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*/
|
||||
class DataResponse : public ResponseRequest {
|
||||
public:
|
||||
POINTER_DEFINITIONS(DataResponse);
|
||||
|
||||
virtual ~DataResponse() {}
|
||||
|
||||
/**
|
||||
* Notification response.
|
||||
* @param transport
|
||||
* @param version
|
||||
* @param payloadBuffer
|
||||
*/
|
||||
virtual void response(Transport::shared_pointer const & transport, epics::pvData::int8 version, epics::pvData::ByteBuffer* payloadBuffer) = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A request that expects an response multiple responses.
|
||||
* Responses identified by its I/O ID.
|
||||
* This interface needs to be extended (to provide method called on response).
|
||||
*/
|
||||
class SubscriptionRequest /*: public ResponseRequest*/ {
|
||||
public:
|
||||
POINTER_DEFINITIONS(SubscriptionRequest);
|
||||
|
||||
virtual ~SubscriptionRequest() {}
|
||||
|
||||
/**
|
||||
* Update (e.g. after some time of unresponsiveness) - report current value.
|
||||
*/
|
||||
virtual void updateSubscription() = 0;
|
||||
|
||||
/**
|
||||
* Rescubscribe (e.g. when server was restarted)
|
||||
* @param transport new transport to be used.
|
||||
*/
|
||||
virtual void resubscribeSubscription(Transport::shared_pointer const & transport) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct AtomicBoolean_null_deleter
|
||||
{
|
||||
void operator()(void const *) const {}
|
||||
};
|
||||
|
||||
// standard performance on set/clear, use of tr1::shared_ptr lock-free counter for get
|
||||
// alternative is to use boost::atomic
|
||||
class AtomicBoolean
|
||||
{
|
||||
public:
|
||||
AtomicBoolean() : counter(static_cast<void*>(0), AtomicBoolean_null_deleter()) {};
|
||||
|
||||
void set() { mutex.lock(); setp = counter; mutex.unlock(); }
|
||||
void clear() { mutex.lock(); setp.reset(); mutex.unlock(); }
|
||||
|
||||
bool get() const { return counter.use_count() == 2; }
|
||||
private:
|
||||
std::tr1::shared_ptr<void> counter;
|
||||
std::tr1::shared_ptr<void> setp;
|
||||
epics::pvData::Mutex mutex;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* REMOTE_H_ */
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/serializationHelper.h>
|
||||
#include <pv/introspectionRegistry.h>
|
||||
#include <pv/convert.h>
|
||||
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
PVDataCreatePtr SerializationHelper::_pvDataCreate(getPVDataCreate());
|
||||
|
||||
PVStructure::shared_pointer SerializationHelper::deserializePVRequest(ByteBuffer* buffer, DeserializableControl* control)
|
||||
{
|
||||
// for now ordinary structure, later can be changed
|
||||
return deserializeStructureFull(buffer, control);
|
||||
}
|
||||
|
||||
PVStructure::shared_pointer SerializationHelper::deserializeStructureAndCreatePVStructure(ByteBuffer* buffer, DeserializableControl* control)
|
||||
{
|
||||
return deserializeStructureAndCreatePVStructure(buffer, control, PVStructure::shared_pointer());
|
||||
}
|
||||
|
||||
PVStructure::shared_pointer SerializationHelper::deserializeStructureAndCreatePVStructure(ByteBuffer* buffer, DeserializableControl* control, PVStructure::shared_pointer const & existingStructure)
|
||||
{
|
||||
FieldConstPtr field = control->cachedDeserialize(buffer);
|
||||
if (field.get() == 0)
|
||||
return PVStructure::shared_pointer();
|
||||
|
||||
if (existingStructure.get() != 0 && *(field.get()) == *(existingStructure->getField()))
|
||||
return existingStructure;
|
||||
else
|
||||
return _pvDataCreate->createPVStructure(std::tr1::static_pointer_cast<const Structure>(field));
|
||||
}
|
||||
|
||||
PVStructure::shared_pointer SerializationHelper::deserializeStructureFull(ByteBuffer* buffer, DeserializableControl* control)
|
||||
{
|
||||
PVStructure::shared_pointer pvStructure;
|
||||
FieldConstPtr structureField = control->cachedDeserialize(buffer);
|
||||
if (structureField.get() != 0)
|
||||
{
|
||||
pvStructure = _pvDataCreate->createPVStructure(std::tr1::static_pointer_cast<const Structure>(structureField));
|
||||
pvStructure->deserialize(buffer, control);
|
||||
}
|
||||
return pvStructure;
|
||||
}
|
||||
|
||||
void SerializationHelper::serializeNullField(ByteBuffer* buffer, SerializableControl* control)
|
||||
{
|
||||
control->ensureBuffer(1);
|
||||
buffer->putByte(IntrospectionRegistry::NULL_TYPE_CODE);
|
||||
}
|
||||
|
||||
void SerializationHelper::serializePVRequest(ByteBuffer* buffer, SerializableControl* control, PVStructure::shared_pointer const & pvRequest)
|
||||
{
|
||||
// for now ordinary structure, later can be changed
|
||||
serializeStructureFull(buffer, control, pvRequest);
|
||||
}
|
||||
|
||||
void SerializationHelper::serializeStructureFull(ByteBuffer* buffer, SerializableControl* control, PVStructure::shared_pointer const & pvStructure)
|
||||
{
|
||||
if (pvStructure.get() == 0)
|
||||
{
|
||||
serializeNullField(buffer, control);
|
||||
}
|
||||
else
|
||||
{
|
||||
control->cachedSerialize(pvStructure->getField(), buffer);
|
||||
pvStructure->serialize(buffer, control);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* serializationHelper.h
|
||||
*
|
||||
* Created on: Jul 24, 2012
|
||||
* Author: msekoranja
|
||||
*/
|
||||
|
||||
#ifndef SERIALIZATIONHELPER_H_
|
||||
#define SERIALIZATIONHELPER_H_
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define serializationHelperEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/serialize.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/noDefaultMethods.h>
|
||||
#include <pv/pvIntrospect.h>
|
||||
#include <pv/byteBuffer.h>
|
||||
|
||||
#ifdef serializationHelperEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef serializationHelperEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
class SerializationHelper : public epics::pvData::NoDefaultMethods {
|
||||
public:
|
||||
|
||||
static epics::pvData::PVDataCreatePtr _pvDataCreate;
|
||||
|
||||
/**
|
||||
* Deserialize PVRequest.
|
||||
* @param payloadBuffer data buffer.
|
||||
* @return deserialized PVRequest, can be <code>null</code>.
|
||||
*/
|
||||
static epics::pvData::PVStructure::shared_pointer deserializePVRequest(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control);
|
||||
|
||||
/**
|
||||
* Deserialize Structure and create PVStructure instance.
|
||||
* @param payloadBuffer data buffer.
|
||||
* @param control deserialization control.
|
||||
* @return PVStructure instance, can be <code>null</code>.
|
||||
*/
|
||||
static epics::pvData::PVStructure::shared_pointer deserializeStructureAndCreatePVStructure(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control);
|
||||
|
||||
/**
|
||||
* Deserialize Structure and create PVStructure instance, if necessary.
|
||||
* @param payloadBuffer data buffer.
|
||||
* @param control deserialization control.
|
||||
* @param existingStructure if deserialized Field matches <code>existingStrcuture</code> Field, then
|
||||
* <code>existingStructure</code> instance is returned. <code>null</code> value is allowed.
|
||||
* @return PVStructure instance, can be <code>null</code>.
|
||||
*/
|
||||
static epics::pvData::PVStructure::shared_pointer deserializeStructureAndCreatePVStructure(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control, epics::pvData::PVStructure::shared_pointer const & existingStructure);
|
||||
|
||||
/**
|
||||
* Deserialize optional PVStructrue.
|
||||
* @param payloadBuffer data buffer.
|
||||
* @return deserialized PVStructure, can be <code>null</code>.
|
||||
*/
|
||||
static epics::pvData::PVStructure::shared_pointer deserializeStructureFull(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control);
|
||||
|
||||
static void serializeNullField(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control);
|
||||
|
||||
/**
|
||||
* Serialize PVRequest.
|
||||
* @param buffer data buffer.
|
||||
*/
|
||||
static void serializePVRequest(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, epics::pvData::PVStructure::shared_pointer const & pvRequest);
|
||||
|
||||
/**
|
||||
* Serialize optional PVStructrue.
|
||||
* @param buffer data buffer.
|
||||
*/
|
||||
static void serializeStructureFull(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, epics::pvData::PVStructure::shared_pointer const & pvStructure);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SERIALIZATIONHELPER_H_ */
|
||||
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/simpleChannelSearchManagerImpl.h>
|
||||
#include <pv/pvaConstants.h>
|
||||
#include <pv/blockingUDP.h>
|
||||
#include <pv/serializeHelper.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <pv/timeStamp.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace epics::pvData;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
const int SimpleChannelSearchManagerImpl::DATA_COUNT_POSITION = PVA_MESSAGE_HEADER_SIZE + sizeof(int32)/sizeof(int8) + 1;
|
||||
const int SimpleChannelSearchManagerImpl::PAYLOAD_POSITION = sizeof(int16)/sizeof(int8) + 2;
|
||||
|
||||
// 225ms +/- 25ms random
|
||||
const double SimpleChannelSearchManagerImpl::ATOMIC_PERIOD = 0.225;
|
||||
const int SimpleChannelSearchManagerImpl::PERIOD_JITTER_MS = 25;
|
||||
|
||||
const int SimpleChannelSearchManagerImpl::BOOST_VALUE = 1;
|
||||
// must be power of two (so that search is done)
|
||||
const int SimpleChannelSearchManagerImpl::MAX_COUNT_VALUE = 1 << 7;
|
||||
const int SimpleChannelSearchManagerImpl::MAX_FALLBACK_COUNT_VALUE = (1 << 6) + 1;
|
||||
|
||||
const int SimpleChannelSearchManagerImpl::MAX_FRAMES_AT_ONCE = 10;
|
||||
const int SimpleChannelSearchManagerImpl::DELAY_BETWEEN_FRAMES_MS = 50;
|
||||
|
||||
|
||||
SimpleChannelSearchManagerImpl::shared_pointer
|
||||
SimpleChannelSearchManagerImpl::create(Context::shared_pointer const & context)
|
||||
{
|
||||
SimpleChannelSearchManagerImpl::shared_pointer thisPtr(new SimpleChannelSearchManagerImpl(context));
|
||||
thisPtr->activate();
|
||||
return thisPtr;
|
||||
}
|
||||
|
||||
SimpleChannelSearchManagerImpl::SimpleChannelSearchManagerImpl(Context::shared_pointer const & context) :
|
||||
m_context(context),
|
||||
m_canceled(),
|
||||
m_sequenceNumber(0),
|
||||
m_sendBuffer(MAX_UDP_UNFRAGMENTED_SEND),
|
||||
m_channels(),
|
||||
m_lastTimeSent(),
|
||||
m_mockTransportSendControl(),
|
||||
m_channelMutex(),
|
||||
m_userValueMutex(),
|
||||
m_mutex()
|
||||
{
|
||||
// initialize send buffer
|
||||
initializeSendBuffer();
|
||||
|
||||
|
||||
// initialize random seed with some random value
|
||||
srand ( time(NULL) );
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::activate()
|
||||
{
|
||||
// add some jitter so that all the clients do not send at the same time
|
||||
double period = ATOMIC_PERIOD + (rand() % (2*PERIOD_JITTER_MS+1) - PERIOD_JITTER_MS)/(double)1000;
|
||||
|
||||
Context::shared_pointer context = m_context.lock();
|
||||
if (context.get())
|
||||
context->getTimer()->schedulePeriodic(shared_from_this(), period, period);
|
||||
|
||||
//new Thread(this, "pvAccess immediate-search").start();
|
||||
}
|
||||
|
||||
SimpleChannelSearchManagerImpl::~SimpleChannelSearchManagerImpl()
|
||||
{
|
||||
// shared_from_this() is not allowed from destructor
|
||||
// be sure to call cancel() first
|
||||
// cancel();
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::cancel()
|
||||
{
|
||||
Lock guard(m_mutex);
|
||||
|
||||
if (m_canceled.get())
|
||||
return;
|
||||
m_canceled.set();
|
||||
|
||||
Context::shared_pointer context = m_context.lock();
|
||||
if (context.get())
|
||||
context->getTimer()->cancel(shared_from_this());
|
||||
}
|
||||
|
||||
int32_t SimpleChannelSearchManagerImpl::registeredCount()
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
return static_cast<int32_t>(m_channels.size());
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::registerSearchInstance(SearchInstance::shared_pointer const & channel)
|
||||
{
|
||||
if (m_canceled.get())
|
||||
return;
|
||||
|
||||
bool immediateTrigger;
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
//overrides if already registered
|
||||
m_channels[channel->getSearchInstanceID()] = channel;
|
||||
immediateTrigger = m_channels.size() == 1;
|
||||
|
||||
Lock guard2(m_userValueMutex);
|
||||
int32_t& userValue = channel->getUserValue();
|
||||
userValue = 1;
|
||||
}
|
||||
|
||||
if (immediateTrigger)
|
||||
callback();
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::unregisterSearchInstance(SearchInstance::shared_pointer const & channel)
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
pvAccessID id = channel->getSearchInstanceID();
|
||||
std::map<pvAccessID,SearchInstance::shared_pointer>::iterator channelsIter = m_channels.find(id);
|
||||
if(channelsIter != m_channels.end())
|
||||
m_channels.erase(id);
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::searchResponse(pvAccessID cid, int32_t /*seqNo*/, int8_t minorRevision, osiSockAddr* serverAddress)
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
std::map<pvAccessID,SearchInstance::shared_pointer>::iterator channelsIter = m_channels.find(cid);
|
||||
if(channelsIter == m_channels.end())
|
||||
{
|
||||
guard.unlock();
|
||||
|
||||
// minor hack to enable duplicate reports
|
||||
SearchInstance::shared_pointer si = std::tr1::dynamic_pointer_cast<SearchInstance>(m_context.lock()->getChannel(cid));
|
||||
if (si)
|
||||
si->searchResponse(minorRevision, serverAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
SearchInstance::shared_pointer si = channelsIter->second;
|
||||
|
||||
// remove from search list
|
||||
m_channels.erase(cid);
|
||||
|
||||
guard.unlock();
|
||||
|
||||
// then notify SearchInstance
|
||||
si->searchResponse(minorRevision, serverAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::newServerDetected()
|
||||
{
|
||||
boost();
|
||||
callback();
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::initializeSendBuffer()
|
||||
{
|
||||
// for now OK, since it is only set here
|
||||
m_sequenceNumber++;
|
||||
|
||||
|
||||
// new buffer
|
||||
m_sendBuffer.clear();
|
||||
m_sendBuffer.putByte(PVA_MAGIC);
|
||||
m_sendBuffer.putByte(PVA_VERSION);
|
||||
m_sendBuffer.putByte((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00); // data + 7-bit endianess
|
||||
m_sendBuffer.putByte((int8_t)3); // search
|
||||
m_sendBuffer.putInt(sizeof(int32_t)/sizeof(int8_t) + 1); // "zero" payload
|
||||
m_sendBuffer.putInt(m_sequenceNumber);
|
||||
|
||||
/*
|
||||
final boolean REQUIRE_REPLY = false;
|
||||
sendBuffer.put(REQUIRE_REPLY ? (byte)QoS.REPLY_REQUIRED.getMaskValue() : (byte)QoS.DEFAULT.getMaskValue());
|
||||
*/
|
||||
|
||||
m_sendBuffer.putByte((int8_t)QOS_DEFAULT);
|
||||
m_sendBuffer.putShort((int16_t)0); // count
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::flushSendBuffer()
|
||||
{
|
||||
Lock guard(m_mutex);
|
||||
|
||||
Transport::shared_pointer tt = m_context.lock()->getSearchTransport();
|
||||
BlockingUDPTransport::shared_pointer ut = std::tr1::static_pointer_cast<BlockingUDPTransport>(tt);
|
||||
ut->send(&m_sendBuffer); // TODO
|
||||
initializeSendBuffer();
|
||||
}
|
||||
|
||||
|
||||
bool SimpleChannelSearchManagerImpl::generateSearchRequestMessage(SearchInstance::shared_pointer const & channel,
|
||||
ByteBuffer* requestMessage, TransportSendControl* control)
|
||||
{
|
||||
epics::pvData::int16 dataCount = requestMessage->getShort(DATA_COUNT_POSITION);
|
||||
|
||||
dataCount++;
|
||||
|
||||
/*
|
||||
if(dataCount >= MAX_SEARCH_BATCH_COUNT)
|
||||
return false;
|
||||
*/
|
||||
|
||||
const epics::pvData::String name = channel->getSearchInstanceName();
|
||||
// not nice...
|
||||
const int addedPayloadSize = sizeof(int32)/sizeof(int8) + (1 + sizeof(int32)/sizeof(int8) + name.length());
|
||||
if(((int)requestMessage->getRemaining()) < addedPayloadSize)
|
||||
return false;
|
||||
|
||||
requestMessage->putInt(channel->getSearchInstanceID());
|
||||
SerializeHelper::serializeString(name, requestMessage, control);
|
||||
|
||||
requestMessage->putInt(PAYLOAD_POSITION, requestMessage->getPosition() - PVA_MESSAGE_HEADER_SIZE);
|
||||
requestMessage->putShort(DATA_COUNT_POSITION, dataCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleChannelSearchManagerImpl::generateSearchRequestMessage(SearchInstance::shared_pointer const & channel,
|
||||
bool allowNewFrame, bool flush)
|
||||
{
|
||||
Lock guard(m_mutex);
|
||||
bool success = generateSearchRequestMessage(channel, &m_sendBuffer, &m_mockTransportSendControl);
|
||||
// buffer full, flush
|
||||
if(!success)
|
||||
{
|
||||
flushSendBuffer();
|
||||
if(allowNewFrame)
|
||||
generateSearchRequestMessage(channel, &m_sendBuffer, &m_mockTransportSendControl);
|
||||
if (flush)
|
||||
flushSendBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (flush)
|
||||
flushSendBuffer();
|
||||
|
||||
return flush;
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::boost()
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
Lock guard2(m_userValueMutex);
|
||||
std::map<pvAccessID,SearchInstance::shared_pointer>::iterator channelsIter = m_channels.begin();
|
||||
for(; channelsIter != m_channels.end(); channelsIter++)
|
||||
{
|
||||
int32_t& userValue = channelsIter->second->getUserValue();
|
||||
userValue = BOOST_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::callback()
|
||||
{
|
||||
// high-frequency beacon anomaly trigger guard
|
||||
{
|
||||
Lock guard(m_mutex);
|
||||
|
||||
epics::pvData::TimeStamp now;
|
||||
now.getCurrent();
|
||||
int64_t nowMS = now.getMilliseconds();
|
||||
|
||||
if (nowMS - m_lastTimeSent < 100)
|
||||
return;
|
||||
m_lastTimeSent = nowMS;
|
||||
}
|
||||
|
||||
|
||||
int count = 0;
|
||||
int frameSent = 0;
|
||||
|
||||
vector<SearchInstance::shared_pointer> toSend;
|
||||
{
|
||||
Lock guard(m_channelMutex);
|
||||
toSend.reserve(m_channels.size());
|
||||
std::map<pvAccessID,SearchInstance::shared_pointer>::iterator channelsIter = m_channels.begin();
|
||||
for(; channelsIter != m_channels.end(); channelsIter++)
|
||||
toSend.push_back(channelsIter->second);
|
||||
}
|
||||
|
||||
vector<SearchInstance::shared_pointer>::iterator siter = toSend.begin();
|
||||
for (; siter != toSend.end(); siter++)
|
||||
{
|
||||
m_userValueMutex.lock();
|
||||
int32_t& countValue = (*siter)->getUserValue();
|
||||
bool skip = !isPowerOfTwo(countValue);
|
||||
|
||||
if (countValue == MAX_COUNT_VALUE)
|
||||
countValue = MAX_FALLBACK_COUNT_VALUE;
|
||||
else
|
||||
countValue++;
|
||||
m_userValueMutex.unlock();
|
||||
|
||||
// back-off
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
|
||||
if (generateSearchRequestMessage(*siter, true, false))
|
||||
frameSent++;
|
||||
if (frameSent == MAX_FRAMES_AT_ONCE)
|
||||
{
|
||||
epicsThreadSleep(DELAY_BETWEEN_FRAMES_MS/(double)1000.0);
|
||||
frameSent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
flushSendBuffer();
|
||||
}
|
||||
|
||||
bool SimpleChannelSearchManagerImpl::isPowerOfTwo(int32_t x)
|
||||
{
|
||||
return ((x > 0) && (x & (x - 1)) == 0);
|
||||
}
|
||||
|
||||
void SimpleChannelSearchManagerImpl::timerStopped()
|
||||
{
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLECHANNELSEARCHMANAGERIMPL_H
|
||||
#define SIMPLECHANNELSEARCHMANAGERIMPL_H
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define simpleChannelSearchManagerEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/lock.h>
|
||||
#include <pv/byteBuffer.h>
|
||||
#include <pv/timer.h>
|
||||
|
||||
#ifdef simpleChannelSearchManagerEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef simpleChannelSearchManagerEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <pv/channelSearchManager.h>
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
|
||||
class MockTransportSendControl: public TransportSendControl
|
||||
{
|
||||
public:
|
||||
void endMessage() {}
|
||||
void flush(bool /*lastMessageCompleted*/) {}
|
||||
void setRecipient(const osiSockAddr& /*sendTo*/) {}
|
||||
void startMessage(epics::pvData::int8 /*command*/, std::size_t /*ensureCapacity*/) {}
|
||||
void ensureBuffer(std::size_t /*size*/) {}
|
||||
void alignBuffer(std::size_t /*alignment*/) {}
|
||||
void flushSerializeBuffer() {}
|
||||
void cachedSerialize(const std::tr1::shared_ptr<const epics::pvData::Field>& field, epics::pvData::ByteBuffer* buffer)
|
||||
{
|
||||
// no cache
|
||||
field->serialize(buffer, this);
|
||||
}
|
||||
virtual bool directSerialize(epics::pvData::ByteBuffer* /*existingBuffer*/, const char* /*toSerialize*/,
|
||||
std::size_t /*elementCount*/, std::size_t /*elementSize*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SimpleChannelSearchManagerImpl :
|
||||
public ChannelSearchManager,
|
||||
public epics::pvData::TimerCallback,
|
||||
public std::tr1::enable_shared_from_this<SimpleChannelSearchManagerImpl>
|
||||
{
|
||||
public:
|
||||
POINTER_DEFINITIONS(SimpleChannelSearchManagerImpl);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param context
|
||||
*/
|
||||
static shared_pointer create(Context::shared_pointer const & context);
|
||||
/**
|
||||
* Constructor.
|
||||
* @param context
|
||||
*/
|
||||
virtual ~SimpleChannelSearchManagerImpl();
|
||||
/**
|
||||
* Cancel.
|
||||
*/
|
||||
void cancel();
|
||||
/**
|
||||
* Get number of registered channels.
|
||||
* @return number of registered channels.
|
||||
*/
|
||||
int32_t registeredCount();
|
||||
/**
|
||||
* Register channel.
|
||||
* @param channel to register.
|
||||
*/
|
||||
void registerSearchInstance(SearchInstance::shared_pointer const & channel);
|
||||
/**
|
||||
* Unregister channel.
|
||||
* @param channel to unregister.
|
||||
*/
|
||||
void unregisterSearchInstance(SearchInstance::shared_pointer const & channel);
|
||||
/**
|
||||
* Search response from server (channel found).
|
||||
* @param cid client channel ID.
|
||||
* @param seqNo search sequence number.
|
||||
* @param minorRevision server minor PVA revision.
|
||||
* @param serverAddress server address.
|
||||
*/
|
||||
void searchResponse(pvAccessID cid, int32_t seqNo, int8_t minorRevision, osiSockAddr* serverAddress);
|
||||
/**
|
||||
* New server detected.
|
||||
* Boost searching of all channels.
|
||||
*/
|
||||
void newServerDetected();
|
||||
|
||||
/// Timer callback.
|
||||
void callback();
|
||||
|
||||
/// Timer stooped callback.
|
||||
void timerStopped();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
* @param context
|
||||
*/
|
||||
SimpleChannelSearchManagerImpl(Context::shared_pointer const & context);
|
||||
void activate();
|
||||
|
||||
bool generateSearchRequestMessage(SearchInstance::shared_pointer const & channel, bool allowNewFrame, bool flush);
|
||||
|
||||
static bool generateSearchRequestMessage(SearchInstance::shared_pointer const & channel,
|
||||
epics::pvData::ByteBuffer* byteBuffer, TransportSendControl* control);
|
||||
|
||||
void boost();
|
||||
|
||||
void initializeSendBuffer();
|
||||
void flushSendBuffer();
|
||||
|
||||
static bool isPowerOfTwo(int32_t x);
|
||||
|
||||
/**
|
||||
* Context.
|
||||
*/
|
||||
Context::weak_pointer m_context;
|
||||
|
||||
/**
|
||||
* Canceled flag.
|
||||
*/
|
||||
AtomicBoolean m_canceled;
|
||||
|
||||
/**
|
||||
* Search (datagram) sequence number.
|
||||
*/
|
||||
int32_t m_sequenceNumber;
|
||||
|
||||
/**
|
||||
* Send byte buffer (frame)
|
||||
*/
|
||||
epics::pvData::ByteBuffer m_sendBuffer;
|
||||
|
||||
/**
|
||||
* Set of registered channels.
|
||||
*/
|
||||
std::map<pvAccessID,SearchInstance::shared_pointer> m_channels;
|
||||
|
||||
/**
|
||||
* Time of last frame send.
|
||||
*/
|
||||
int64_t m_lastTimeSent;
|
||||
|
||||
/**
|
||||
* Mock transport send control
|
||||
*/
|
||||
MockTransportSendControl m_mockTransportSendControl;
|
||||
|
||||
/**
|
||||
* This instance mutex.
|
||||
*/
|
||||
epics::pvData::Mutex m_channelMutex;
|
||||
|
||||
/**
|
||||
* User value lock.
|
||||
*/
|
||||
epics::pvData::Mutex m_userValueMutex;
|
||||
|
||||
/**
|
||||
* m_channels mutex.
|
||||
*/
|
||||
epics::pvData::Mutex m_mutex;
|
||||
|
||||
static const int DATA_COUNT_POSITION;
|
||||
static const int PAYLOAD_POSITION;
|
||||
|
||||
static const double ATOMIC_PERIOD;
|
||||
static const int PERIOD_JITTER_MS;
|
||||
|
||||
static const int BOOST_VALUE;
|
||||
static const int MAX_COUNT_VALUE;
|
||||
static const int MAX_FALLBACK_COUNT_VALUE;
|
||||
|
||||
static const int MAX_FRAMES_AT_ONCE;
|
||||
static const int DELAY_BETWEEN_FRAMES_MS;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SIMPLECHANNELSEARCHMANAGERIMPL_H */
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#include <pv/transportRegistry.h>
|
||||
|
||||
using namespace epics::pvData;
|
||||
using namespace std;
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
TransportRegistry::TransportRegistry(): _transports(), _transportCount(0), _mutex()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TransportRegistry::~TransportRegistry()
|
||||
{
|
||||
}
|
||||
|
||||
void TransportRegistry::put(Transport::shared_pointer const & transport)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
//const String type = transport.getType();
|
||||
const int16 priority = transport->getPriority();
|
||||
const osiSockAddr* address = transport->getRemoteAddress();
|
||||
|
||||
transportsMap_t::iterator transportsIter = _transports.find(address);
|
||||
prioritiesMapSharedPtr_t priorities;
|
||||
if(transportsIter == _transports.end())
|
||||
{
|
||||
priorities.reset(new prioritiesMap_t());
|
||||
_transports[address] = priorities;
|
||||
_transportCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
priorities = transportsIter->second;
|
||||
prioritiesMap_t::iterator prioritiesIter = priorities->find(priority);
|
||||
if(prioritiesIter == priorities->end()) //only increase transportCount if not replacing
|
||||
{
|
||||
_transportCount++;
|
||||
}
|
||||
}
|
||||
(*priorities)[priority] = transport;
|
||||
}
|
||||
|
||||
Transport::shared_pointer TransportRegistry::get(String const & /*type*/, const osiSockAddr* address, const int16 priority)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
transportsMap_t::iterator transportsIter = _transports.find(address);
|
||||
if(transportsIter != _transports.end())
|
||||
{
|
||||
prioritiesMapSharedPtr_t priorities = transportsIter->second;
|
||||
prioritiesMap_t::iterator prioritiesIter = priorities->find(priority);
|
||||
if(prioritiesIter != priorities->end())
|
||||
{
|
||||
return prioritiesIter->second;
|
||||
}
|
||||
}
|
||||
return Transport::shared_pointer();
|
||||
}
|
||||
|
||||
auto_ptr<TransportRegistry::transportVector_t> TransportRegistry::get(String const & /*type*/, const osiSockAddr* address)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
transportsMap_t::iterator transportsIter = _transports.find(address);
|
||||
if(transportsIter != _transports.end())
|
||||
{
|
||||
prioritiesMapSharedPtr_t priorities = transportsIter->second;
|
||||
auto_ptr<transportVector_t> transportArray(new transportVector_t(priorities->size()));
|
||||
int32 i = 0;
|
||||
for(prioritiesMap_t::iterator prioritiesIter = priorities->begin();
|
||||
prioritiesIter != priorities->end();
|
||||
prioritiesIter++, i++)
|
||||
{
|
||||
(*transportArray)[i] = prioritiesIter->second;
|
||||
}
|
||||
return transportArray;
|
||||
}
|
||||
return auto_ptr<transportVector_t>();
|
||||
}
|
||||
|
||||
Transport::shared_pointer TransportRegistry::remove(Transport::shared_pointer const & transport)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
const int16 priority = transport->getPriority();
|
||||
const osiSockAddr* address = transport->getRemoteAddress();
|
||||
Transport::shared_pointer retTransport;
|
||||
transportsMap_t::iterator transportsIter = _transports.find(address);
|
||||
if(transportsIter != _transports.end())
|
||||
{
|
||||
prioritiesMapSharedPtr_t priorities = transportsIter->second;
|
||||
prioritiesMap_t::iterator prioritiesIter = priorities->find(priority);
|
||||
if(prioritiesIter != priorities->end())
|
||||
{
|
||||
retTransport = prioritiesIter->second;
|
||||
priorities->erase(prioritiesIter);
|
||||
_transportCount--;
|
||||
if(priorities->size() == 0)
|
||||
{
|
||||
_transports.erase(transportsIter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return retTransport;
|
||||
}
|
||||
|
||||
void TransportRegistry::clear()
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
_transports.clear();
|
||||
_transportCount = 0;
|
||||
}
|
||||
|
||||
int32 TransportRegistry::numberOfActiveTransports()
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
return _transportCount;
|
||||
}
|
||||
|
||||
|
||||
auto_ptr<TransportRegistry::transportVector_t> TransportRegistry::toArray(String const & /*type*/)
|
||||
{
|
||||
// TODO support type
|
||||
return toArray();
|
||||
}
|
||||
|
||||
|
||||
auto_ptr<TransportRegistry::transportVector_t> TransportRegistry::toArray()
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
if (_transportCount == 0)
|
||||
return auto_ptr<transportVector_t>(0);
|
||||
|
||||
auto_ptr<transportVector_t> transportArray(new transportVector_t(_transportCount));
|
||||
|
||||
int32 i = 0;
|
||||
for (transportsMap_t::iterator transportsIter = _transports.begin();
|
||||
transportsIter != _transports.end();
|
||||
transportsIter++)
|
||||
{
|
||||
prioritiesMapSharedPtr_t priorities = transportsIter->second;
|
||||
for (prioritiesMap_t::iterator prioritiesIter = priorities->begin();
|
||||
prioritiesIter != priorities->end();
|
||||
prioritiesIter++, i++)
|
||||
{
|
||||
(*transportArray)[i] = prioritiesIter->second;
|
||||
}
|
||||
}
|
||||
|
||||
return transportArray;
|
||||
}
|
||||
|
||||
void TransportRegistry::toArray(transportVector_t & transportArray)
|
||||
{
|
||||
Lock guard(_mutex);
|
||||
if (_transportCount == 0)
|
||||
return;
|
||||
|
||||
transportArray.reserve(transportArray.size() + _transportCount);
|
||||
|
||||
for (transportsMap_t::iterator transportsIter = _transports.begin();
|
||||
transportsIter != _transports.end();
|
||||
transportsIter++)
|
||||
{
|
||||
prioritiesMapSharedPtr_t priorities = transportsIter->second;
|
||||
for (prioritiesMap_t::iterator prioritiesIter = priorities->begin();
|
||||
prioritiesIter != priorities->end();
|
||||
prioritiesIter++)
|
||||
{
|
||||
transportArray.push_back(prioritiesIter->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright - See the COPYRIGHT that is included with this distribution.
|
||||
* pvAccessCPP is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
*/
|
||||
|
||||
#ifndef TRANSPORTREGISTRY_H
|
||||
#define TRANSPORTREGISTRY_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define transportRegistryEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <pv/lock.h>
|
||||
#include <pv/pvType.h>
|
||||
#include <pv/epicsException.h>
|
||||
#include <pv/remote.h>
|
||||
#include <pv/inetAddressUtil.h>
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#ifdef transportRegistryEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef transportRegistryEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
|
||||
class TransportRegistry {
|
||||
public:
|
||||
typedef std::tr1::shared_ptr<TransportRegistry> shared_pointer;
|
||||
typedef std::tr1::shared_ptr<const TransportRegistry> const_shared_pointer;
|
||||
|
||||
typedef std::vector<Transport::shared_pointer> transportVector_t;
|
||||
|
||||
TransportRegistry();
|
||||
virtual ~TransportRegistry();
|
||||
|
||||
void put(Transport::shared_pointer const & transport);
|
||||
Transport::shared_pointer get(epics::pvData::String const & type, const osiSockAddr* address, const epics::pvData::int16 priority);
|
||||
std::auto_ptr<transportVector_t> get(epics::pvData::String const & type, const osiSockAddr* address);
|
||||
Transport::shared_pointer remove(Transport::shared_pointer const & transport);
|
||||
void clear();
|
||||
epics::pvData::int32 numberOfActiveTransports();
|
||||
|
||||
// TODO note type not supported
|
||||
std::auto_ptr<transportVector_t> toArray(epics::pvData::String const & type);
|
||||
std::auto_ptr<transportVector_t> toArray();
|
||||
// optimized to avoid reallocation, adds to array
|
||||
void toArray(transportVector_t & transportArray);
|
||||
|
||||
private:
|
||||
//TODO if unordered map is used instead of map we can use sockAddrAreIdentical routine from osiSock.h
|
||||
// NOTE: pointers are used to osiSockAddr (to save memory), since it guaranteed that their reference is valid as long as Transport
|
||||
typedef std::map<const epics::pvData::int16,Transport::shared_pointer> prioritiesMap_t;
|
||||
typedef std::tr1::shared_ptr<prioritiesMap_t> prioritiesMapSharedPtr_t;
|
||||
typedef std::map<const osiSockAddr*,prioritiesMapSharedPtr_t,comp_osiSockAddrPtr> transportsMap_t;
|
||||
|
||||
transportsMap_t _transports;
|
||||
epics::pvData::int32 _transportCount;
|
||||
epics::pvData::Mutex _mutex;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif /* INTROSPECTIONREGISTRY_H */
|
||||
Reference in New Issue
Block a user