demo version
This commit is contained in:
parent
fbb282a801
commit
672d6daa8e
125 changed files with 17918 additions and 1481 deletions
|
@ -0,0 +1,403 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Nerian Vision GmbH
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef VISIONTRANSFER_DATABLOCKPROTOCOL_H
|
||||
#define VISIONTRANSFER_DATABLOCKPROTOCOL_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
|
||||
#include "visiontransfer/alignedallocator.h"
|
||||
#include "visiontransfer/exceptions.h"
|
||||
|
||||
namespace visiontransfer {
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* \brief A protocol for transmitting large blocks of data over a network.
|
||||
*
|
||||
* The protocol slices the large data block into several smaller chunks
|
||||
* that can be transmitted over a network. A user defined header is
|
||||
* always transmitted before this large data block.
|
||||
*
|
||||
* There are two different implementations for UDP and TCP. In UDP mode,
|
||||
* packet loss is handled by performing a packet re-transmission. In TCP
|
||||
* mode, data does not have to be received with the same packet size as
|
||||
* it is sent out.
|
||||
*
|
||||
* This class is intended to be used by ImageProtocol and should normally
|
||||
* not be used directly.
|
||||
*/
|
||||
|
||||
class DataBlockProtocol {
|
||||
public:
|
||||
enum ProtocolType {
|
||||
PROTOCOL_TCP,
|
||||
PROTOCOL_UDP
|
||||
};
|
||||
|
||||
//
|
||||
static const int MAX_DATA_BLOCKS = 8;
|
||||
|
||||
// Constants that are also used in other places.
|
||||
static const int MAX_TCP_BYTES_TRANSFER = 0xFFFF; //64K - 1
|
||||
static const int MAX_UDP_RECEPTION = 0x4000; //16K
|
||||
static const int MAX_OUTSTANDING_BYTES = 2*MAX_TCP_BYTES_TRANSFER;
|
||||
|
||||
#pragma pack(push,1)
|
||||
// Extends previous one-channel 6-byte raw header buffer
|
||||
// Legacy transfers can be detected via non-zero netTransferSizeDummy
|
||||
struct HeaderPreamble {
|
||||
uint16_t netHeaderSize;
|
||||
int32_t netTransferSizeDummy; // layout compatibility, legacy detection
|
||||
uint32_t netTransferSizes[MAX_DATA_BLOCKS]; // per-block total size
|
||||
};
|
||||
struct SegmentHeaderUDP {
|
||||
uint32_t segmentOffset;
|
||||
};
|
||||
struct SegmentHeaderTCP {
|
||||
uint32_t fragmentSize;
|
||||
uint32_t segmentOffset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* \brief Creates a new instance
|
||||
*
|
||||
* \param server If set to true, this object will be a communication server.
|
||||
* \param protType The network transport protocol that is used.
|
||||
* \param maxUdpPacketSize Maximum allowed size of a UDP packet when sending data.
|
||||
*/
|
||||
|
||||
DataBlockProtocol(bool server, ProtocolType protType, int maxUdpPacketSize);
|
||||
|
||||
/**
|
||||
* \brief Returns the size of the overhead data that is required for
|
||||
* transferring a single network message.
|
||||
*/
|
||||
int getProtocolOverhead() const {
|
||||
return protType == PROTOCOL_UDP ? sizeof(int) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the maximum payload size that can be received
|
||||
*/
|
||||
int getMaxReceptionSize() const;
|
||||
|
||||
/**
|
||||
* \brief Resets all transfer related internal variables
|
||||
*/
|
||||
void resetTransfer();
|
||||
|
||||
/**
|
||||
* \brief Sets a user-defined header that shall be transmitted with
|
||||
* the next transfer
|
||||
*
|
||||
* \param data Pointer to the data of the header that should be
|
||||
* transferred.
|
||||
* \param headerSize Size of the data in \c data.
|
||||
* \param transferSize Total size of the payload for the next transfer.
|
||||
*
|
||||
* This method must be called before setTransferData(). A call before
|
||||
* the start of each transfer is necessary. There must be at least
|
||||
* 6 additional bytes of reserved memory after the end and before the
|
||||
* beginning of \c data.
|
||||
*/
|
||||
void setTransferHeader(unsigned char* data, int headerSize, int blocks);
|
||||
|
||||
/**
|
||||
* \brief Sets the per-block transfer size
|
||||
*
|
||||
* \param bytes Size of the data pointed to with the matching setTransferData()
|
||||
*
|
||||
* Replaces the old single-buffer total size that was prepended to the
|
||||
* second-level header alongside the header size.
|
||||
*/
|
||||
void setTransferBytes(int block, long bytes);
|
||||
|
||||
/**
|
||||
* \brief Sets the payload data for the next transfer.
|
||||
*
|
||||
* \param data Pointer to the data that should be transferred.
|
||||
* \param validBytes The number of bytes that are currently
|
||||
* valid in \c data.
|
||||
*
|
||||
* Part of \c data will be overwritten. There must be at least 4 additional
|
||||
* allocated bytes at the end of \c data.
|
||||
*
|
||||
* If \c validBytes is set to a value smaller than the total transfer
|
||||
* size, only a partial transfer is performed. Subsequent calls to
|
||||
* setTransferValidBytes() are then necessary.
|
||||
*/
|
||||
void setTransferData(int block, unsigned char* data, int validBytes = 0x7FFFFFFF);
|
||||
|
||||
/**
|
||||
* \brief Updates the number of valid bytes in a partial transfer.
|
||||
*
|
||||
* \param validBytes The number of already valid bytes in the previously
|
||||
* set data pointer.
|
||||
*
|
||||
* This method has to be called whenever new data is available in a
|
||||
* partial transfer. \see setTransferData()
|
||||
*/
|
||||
void setTransferValidBytes(int block, int validBytes);
|
||||
|
||||
/**
|
||||
* \brief Gets the next network message for the current transfer.
|
||||
*
|
||||
* \param length The length of the network message.
|
||||
* \return Pointer to the network message data.
|
||||
*
|
||||
* If the transfer has already been completed or if there are currently
|
||||
* no more valid bytes to be transmitted, a null pointer is returned.
|
||||
*/
|
||||
const unsigned char* getTransferMessage(int& length);
|
||||
|
||||
/**
|
||||
* \brief Returns true if the current transfer has been completed.
|
||||
*/
|
||||
bool transferComplete();
|
||||
|
||||
/**
|
||||
* \brief Gets a buffer for receiving the next network message.
|
||||
*
|
||||
* \param maxLength The expected maximum length that is required for
|
||||
* receiving a network message.
|
||||
*
|
||||
* The returned buffer is a subsection of the internal receive buffer.
|
||||
*/
|
||||
unsigned char* getNextReceiveBuffer(int maxLength);
|
||||
|
||||
/**
|
||||
* \brief Resets the message reception.
|
||||
*
|
||||
* \param dropped If true, then this reset is rated as an error and
|
||||
* internal counter for dropped transfers is increased.
|
||||
*/
|
||||
void resetReception(bool dropped);
|
||||
|
||||
/**
|
||||
* \brief Handles a received network message.
|
||||
*
|
||||
* \param length Length of the received network message.
|
||||
* \param transferComplete Set to true if a new transfer is complete after
|
||||
* receiving the current packet
|
||||
*
|
||||
* Please see ImageProtocol::processReceivedMessage() for further details.
|
||||
*/
|
||||
void processReceivedMessage(int length, bool& transferComplete);
|
||||
|
||||
/**
|
||||
* \brief Returns the data that has been received for the current transfer.
|
||||
*
|
||||
* \param length Will be set to the number of bytes that have been received.
|
||||
* \return Pointer to the buffer containing the received data.
|
||||
*
|
||||
* The received data is valid until receiving the first network
|
||||
* message for a new transfer.
|
||||
*/
|
||||
unsigned char* getReceivedData(int& length);
|
||||
|
||||
/**
|
||||
* \brief Returns the header data that has been received for the
|
||||
* current transfer.
|
||||
*
|
||||
* \param length Will be set to the length of the header data in
|
||||
* bytes.
|
||||
* \return Pointer to the buffer containing the received header data.
|
||||
*
|
||||
* The received header data is valid until receiving the first network
|
||||
* message for a new transfer.
|
||||
*/
|
||||
unsigned char* getReceivedHeader(int& length);
|
||||
|
||||
/**
|
||||
* \brief Returns the internal counter of dropped transfers during
|
||||
* reception.
|
||||
*/
|
||||
int getDroppedReceptions() const {
|
||||
return droppedReceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns true if the last network message has established a
|
||||
* new connection from a client
|
||||
*
|
||||
* For TCP this method always returns false as connections are
|
||||
* handled by the transport protocol.
|
||||
*/
|
||||
bool newClientConnected();
|
||||
|
||||
/**
|
||||
* \brief Returns true if a remote connection is established.
|
||||
*
|
||||
* For TCP this method always returns true as connections are
|
||||
* handled by the transport protocol.
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
/**
|
||||
* \brief If a control message is pending to be transmitted, then
|
||||
* the message data will be returned by this method.
|
||||
*
|
||||
* \param length Will be set to the length of the message.
|
||||
* \return Pointer to the message data or NULL if no message is pending.
|
||||
*
|
||||
* Control messages are only used if the UDP transfer protocol is
|
||||
* selected. For TCP this method always returns a null pointer.
|
||||
*/
|
||||
const unsigned char* getNextControlMessage(int& length);
|
||||
|
||||
unsigned char* getBlockReceiveBuffer(int block) {
|
||||
if (block >= numReceptionBlocks) {
|
||||
throw ProtocolException("Tried to get receive buffer beyond initialized block range");
|
||||
}
|
||||
return &blockReceiveBuffers[block][0];
|
||||
}
|
||||
int getBlockValidSize(int block) {
|
||||
if (block >= numReceptionBlocks) {
|
||||
throw ProtocolException("Tried to get valid buffer index beyond initialized block range");
|
||||
}
|
||||
return blockValidSize[block];
|
||||
}
|
||||
bool isBlockDone(int block) {
|
||||
if (block >= numReceptionBlocks) {
|
||||
throw ProtocolException("Tried to get completion status of uninitialized block");
|
||||
}
|
||||
return blockValidSize[block] >= blockReceiveSize[block];
|
||||
}
|
||||
bool allBlocksDone() {
|
||||
for (int i=0; i<numReceptionBlocks; ++i) {
|
||||
if (!isBlockDone(i)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool anyPayloadReceived() {
|
||||
for (int i=0; i<numReceptionBlocks; ++i) {
|
||||
if (blockReceiveOffsets[i] > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string statusReport();
|
||||
|
||||
bool wasHeaderReceived() const {
|
||||
return headerReceived;
|
||||
}
|
||||
|
||||
private:
|
||||
// The pimpl idiom is not necessary here, as this class is usually not
|
||||
// used directly
|
||||
|
||||
struct MissingReceiveSegment {
|
||||
int offset;
|
||||
int length;
|
||||
bool isEof;
|
||||
unsigned char subsequentData[4];
|
||||
};
|
||||
|
||||
static constexpr int HEARTBEAT_INTERVAL_MS = 1000;
|
||||
static constexpr int RECONNECT_TIMEOUT_MS = 1000;
|
||||
|
||||
static constexpr unsigned char CONNECTION_MESSAGE = 0x01;
|
||||
static constexpr unsigned char CONFIRM_MESSAGE = 0x02;
|
||||
static constexpr unsigned char HEADER_MESSAGE = 0x03;
|
||||
static constexpr unsigned char RESEND_MESSAGE = 0x04;
|
||||
static constexpr unsigned char EOF_MESSAGE = 0x05;
|
||||
static constexpr unsigned char HEARTBEAT_MESSAGE = 0x06;
|
||||
|
||||
bool isServer;
|
||||
ProtocolType protType;
|
||||
int maxPayloadSize;
|
||||
int minPayloadSize;
|
||||
|
||||
// Transfer related variables
|
||||
bool transferDone;
|
||||
unsigned char* rawDataArr[MAX_DATA_BLOCKS];
|
||||
int rawDataArrStrideHackOrig[MAX_DATA_BLOCKS];
|
||||
int rawDataArrStrideHackRepl[MAX_DATA_BLOCKS];
|
||||
int rawValidBytes[MAX_DATA_BLOCKS];
|
||||
int transferOffset[MAX_DATA_BLOCKS];
|
||||
int transferSize[MAX_DATA_BLOCKS];
|
||||
char overwrittenTransferData[sizeof(SegmentHeaderTCP)];
|
||||
int overwrittenTransferIndex;
|
||||
int overwrittenTransferBlock;
|
||||
unsigned char* transferHeaderData;
|
||||
int transferHeaderSize;
|
||||
int totalBytesCompleted;
|
||||
int totalTransferSize;
|
||||
int numTransferBlocks;
|
||||
int lastTransmittedBlock;
|
||||
|
||||
// Reliability related variables
|
||||
std::deque<MissingReceiveSegment> missingReceiveSegments;
|
||||
std::deque<std::pair<int, int> > missingTransferSegments;
|
||||
bool waitingForMissingSegments;
|
||||
int totalReceiveSize;
|
||||
|
||||
unsigned char controlMessageBuffer[1024];
|
||||
|
||||
// Connection related variables
|
||||
bool connectionConfirmed;
|
||||
bool confirmationMessagePending;
|
||||
bool eofMessagePending;
|
||||
bool clientConnectionPending;
|
||||
bool resendMessagePending;
|
||||
std::chrono::steady_clock::time_point lastRemoteHostActivity;
|
||||
std::chrono::steady_clock::time_point lastSentHeartbeat;
|
||||
std::chrono::steady_clock::time_point lastReceivedHeartbeat;
|
||||
|
||||
// Reception related variables
|
||||
std::vector<unsigned char, AlignedAllocator<unsigned char> > receiveBuffer;
|
||||
std::vector<unsigned char, AlignedAllocator<unsigned char> > blockReceiveBuffers[MAX_DATA_BLOCKS];
|
||||
int blockReceiveOffsets[MAX_DATA_BLOCKS];
|
||||
int blockReceiveSize[MAX_DATA_BLOCKS];
|
||||
int blockValidSize[MAX_DATA_BLOCKS];
|
||||
std::vector<unsigned char> receivedHeader;
|
||||
bool finishedReception;
|
||||
int droppedReceptions;
|
||||
int completedReceptions;
|
||||
double lostSegmentRate;
|
||||
int lostSegmentBytes;
|
||||
unsigned char unprocessedMsgPart[MAX_OUTSTANDING_BYTES];
|
||||
int unprocessedMsgLength;
|
||||
bool headerReceived;
|
||||
bool legacyTransfer;
|
||||
int numReceptionBlocks;
|
||||
int receiveOffset;
|
||||
|
||||
const unsigned char* extractPayload(const unsigned char* data, int& length, bool& error);
|
||||
bool processControlMessage(int length);
|
||||
void restoreTransferBuffer();
|
||||
bool generateResendRequest(int& length);
|
||||
void getNextTransferSegment(int& block, int& offset, int& length);
|
||||
void parseResendMessage(int length);
|
||||
void parseEofMessage(int length);
|
||||
void integrateMissingUdpSegments(int block, int lastSegmentOffset, int lastSegmentSize);
|
||||
void processReceivedUdpMessage(int length, bool& transferComplete);
|
||||
void processReceivedTcpMessage(int length, bool& transferComplete);
|
||||
void resizeReceiveBuffer();
|
||||
int parseReceivedHeader(int length, int offset);
|
||||
void zeroStructures();
|
||||
void splitRawOffset(int rawSegmentOffset, int& dataBlockID, int& segmentOffset);
|
||||
int mergeRawOffset(int dataBlockID, int segmentOffset, int reserved=0);
|
||||
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue