/* * blockingTCPTransport.cpp * * Created on: Dec 29, 2010 * Author: Miha Vitorovic */ #include "blockingTCP.h" #include "inetAddressUtil.h" /* pvData */ #include #include #include /* EPICSv3 */ #include #include #include #include /* standard */ #include #include #include #include using namespace epics::pvData; using std::max; using std::min; using std::ostringstream; namespace epics { namespace pvAccess { BlockingTCPTransport::BlockingTCPTransport(SOCKET channel, ResponseHandler* responseHandler, int receiveBufferSize, short priority) : _closed(false), _channel(channel), _remoteTransportRevision(0), _remoteTransportReceiveBufferSize(MAX_TCP_RECV), _remoteTransportSocketReceiveBufferSize(MAX_TCP_RECV), _priority(priority), _responseHandler(responseHandler), _totalBytesReceived(0), _totalBytesSent(0), _markerToSend(0), _verified(false), _remoteBufferFreeSpace( LONG_LONG_MAX), _markerPeriodBytes(MARKER_PERIOD), _nextMarkerPosition(_markerPeriodBytes), _sendPending(false), _lastMessageStartPosition(0), _mutex( new Mutex()), _sendQueueMutex(new Mutex()), _verifiedMutex(new Mutex()), _monitorMutex(new Mutex()), _stage(READ_FROM_SOCKET), _lastSegmentedMessageType(0), _lastSegmentedMessageCommand(0), _storedPayloadSize(0), _storedPosition(0), _storedLimit(0), _magicAndVersion(0), _packetType(0), _command(0), _payloadSize(0), _flushRequested(false) { _socketBuffer = new ByteBuffer(max(MAX_TCP_RECV +MAX_ENSURE_DATA_BUFFER_SIZE, receiveBufferSize)); _socketBuffer->setPosition(_socketBuffer->getLimit()); _startPosition = _socketBuffer->getPosition(); // allocate buffer _sendBuffer = new ByteBuffer(_socketBuffer->getSize()); _maxPayloadSize = _sendBuffer->getSize()-2*CA_MESSAGE_HEADER_SIZE; // one for header, one for flow control // get send buffer size socklen_t intLen = sizeof(int); int retval = getsockopt(_channel, SOL_SOCKET, SO_SNDBUF, &_socketSendBufferSize, &intLen); if(retval<0) { _socketSendBufferSize = MAX_TCP_RECV; errlogSevPrintf(errlogMinor, "Unable to retrieve socket send buffer size: %s", strerror(errno)); } socklen_t saSize = sizeof(sockaddr); retval = getpeername(_channel, &(_socketAddress->sa), &saSize); if(retval<0) { errlogSevPrintf(errlogMajor, "Error fetching socket remote address: %s", strerror( errno)); } // prepare buffer clearAndReleaseBuffer(); // TODO: add to registry //context.getTransportRegistry().put(this); } void BlockingTCPTransport::clearAndReleaseBuffer() { // NOTE: take care that nextMarkerPosition is set right // fix position to be correct when buffer is cleared // do not include pre-buffered flow control message; not 100% correct, but OK _nextMarkerPosition -= _sendBuffer->getPosition() -CA_MESSAGE_HEADER_SIZE; _sendQueueMutex->lock(); _flushRequested = false; _sendQueueMutex->unlock(); _sendBuffer->clear(); _sendPending = false; // prepare ACK marker _sendBuffer->putShort(CA_MAGIC_AND_VERSION); _sendBuffer->putByte(1); // control data _sendBuffer->putByte(1); // marker ACK _sendBuffer->putInt(0); } int BlockingTCPTransport::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; socklen_t intLen = sizeof(int); int retval = getsockopt(_channel, SOL_SOCKET, SO_RCVBUF, &sockBufSize, &intLen); if(retval<0) errlogSevPrintf(errlogMajor, "Socket getsockopt SO_RCVBUF error: %s", strerror(errno)); return sockBufSize; } bool BlockingTCPTransport::waitUntilVerified(double timeout) { double internalTimeout = timeout; bool internalVerified = false; _verifiedMutex->lock(); internalVerified = _verified; _verifiedMutex->unlock(); while(!internalVerified&&internalTimeout<=0) { epicsThreadSleep(min(0.1, internalTimeout)); internalTimeout -= 0.1; _verifiedMutex->lock(); internalVerified = _verified; _verifiedMutex->unlock(); } return internalVerified; } void BlockingTCPTransport::flush(bool lastMessageCompleted) { // automatic end endMessage(!lastMessageCompleted); bool moreToSend = true; // TODO closed check !!! while(moreToSend) { moreToSend = !flush(); // all sent, exit if(!moreToSend) break; // TODO solve this sleep in a better way epicsThreadSleep(0.01); } _lastMessageStartPosition = _sendBuffer->getPosition(); // start with last header if(!lastMessageCompleted&&_lastSegmentedMessageType!=0) startMessage( _lastSegmentedMessageCommand, 0); } void BlockingTCPTransport::startMessage(int8 command, int ensureCapacity) { _lastMessageStartPosition = -1; ensureBuffer(CA_MESSAGE_HEADER_SIZE+ensureCapacity); _lastMessageStartPosition = _sendBuffer->getPosition(); _sendBuffer->putShort(CA_MAGIC_AND_VERSION); _sendBuffer->putByte(_lastSegmentedMessageType); // data _sendBuffer->putByte(command); // command _sendBuffer->putInt(0); // temporary zero payload } void BlockingTCPTransport::endMessage() { endMessage(false); } void BlockingTCPTransport::ensureBuffer(int size) { if(_sendBuffer->getRemaining()>=size) return; // too large for buffer... if(_maxPayloadSizegetRemaining()=0) { // set message size _sendBuffer->putInt(_lastMessageStartPosition+sizeof(int16)+2, _sendBuffer->getPosition()-_lastMessageStartPosition -CA_MESSAGE_HEADER_SIZE); int flagsPosition = _lastMessageStartPosition+sizeof(int16); // set segmented bit if(hasMoreSegments) { // first segment if(_lastSegmentedMessageType==0) { int8 type = _sendBuffer->getByte(flagsPosition); // set first segment bit _sendBuffer->putByte(flagsPosition, (int8)(type|0x10)); // first + last segment bit == in-between segment _lastSegmentedMessageType = (int8)(type|0x30); _lastSegmentedMessageCommand = _sendBuffer->getByte( flagsPosition+1); } } else { // last segment if(_lastSegmentedMessageType!=0) { // set last segment bit (by clearing first segment bit) _sendBuffer->putByte(flagsPosition, (int8)(_lastSegmentedMessageType&0xEF)); _lastSegmentedMessageType = 0; } } // manage markers int position = _sendBuffer->getPosition(); int bytesLeft = _sendBuffer->getRemaining(); if(position>=_nextMarkerPosition&&bytesLeft >=CA_MESSAGE_HEADER_SIZE) { _sendBuffer->putShort(CA_MAGIC_AND_VERSION); _sendBuffer->putByte(1); // control data _sendBuffer->putByte(0); // marker _sendBuffer->putInt((int)(_totalBytesSent+position +CA_MESSAGE_HEADER_SIZE)); _nextMarkerPosition = position+_markerPeriodBytes; } } } void BlockingTCPTransport::ensureData(int size) { // enough of data? if(_socketBuffer->getRemaining()>=size) return; // too large for buffer... if(_maxPayloadSizegetPosition()-_storedPosition; // no more data and we have some payload left => read buffer if(_storedPayloadSize>=size) { //System.out.println("storedPayloadSize >= size, remaining:" + socketBuffer.remaining()); // just read up remaining payload // since there is no data on the buffer, read to the beginning of it, at least size bytes processReadCached(true, PROCESS_PAYLOAD, size, false); _storedPosition = _socketBuffer->getPosition(); _storedLimit = _socketBuffer->getLimit(); _socketBuffer->setLimit(min(_storedPosition+_storedPayloadSize, _storedLimit)); } else { // copy remaining bytes, if any int remainingBytes = _socketBuffer->getRemaining(); for(int i = 0; iputByte(i, _socketBuffer->getByte()); // read what is left _socketBuffer->setLimit(_storedLimit); _stage = PROCESS_HEADER; processReadCached(true, NONE, size, false); // copy before position for(int i = remainingBytes-1, j = _socketBuffer->getPosition() -1; i>=0; i--, j--) _socketBuffer->putByte(j, _socketBuffer->getByte(i)); _startPosition = _socketBuffer->getPosition()-remainingBytes; _socketBuffer->setPosition(_startPosition); _storedPosition = _startPosition; //socketBuffer.position(); _storedLimit = _socketBuffer->getLimit(); _socketBuffer->setLimit(min(_storedPosition+_storedPayloadSize, _storedLimit)); // add if missing... if(!_closed&&_socketBuffer->getRemaining()getPosition(); _socketBuffer->setPosition( _socketBuffer->getLimit()); _socketBuffer->setLimit(_socketBuffer->getSize()); } else { // add to bytes read _totalBytesReceived += (_socketBuffer->getPosition() -_startPosition); // copy remaining bytes, if any int remainingBytes = _socketBuffer->getRemaining(); int endPosition = MAX_ENSURE_DATA_BUFFER_SIZE +remainingBytes; for(int i = MAX_ENSURE_DATA_BUFFER_SIZE; i putByte(i, _socketBuffer->getByte()); currentStartPosition = _startPosition = MAX_ENSURE_DATA_BUFFER_SIZE; _socketBuffer->setPosition( MAX_ENSURE_DATA_BUFFER_SIZE+remainingBytes); _socketBuffer->setLimit(_socketBuffer->getSize()); } // read at least requiredBytes bytes int requiredPosition = (currentStartPosition +requiredBytes); while(_socketBuffer->getPosition()getRemaining()); ssize_t bytesRead = recv(_channel, readBuffer, maxToRead, 0); _socketBuffer->put(readBuffer,0,maxToRead); if(bytesRead<0) { // error (disconnect, end-of-stream) detected close(true); if(nestedCall) THROW_BASE_EXCEPTION( "bytesRead < 0"); return; } } _socketBuffer->setLimit(_socketBuffer->getPosition()); _socketBuffer->setPosition(currentStartPosition); // notify liveness aliveNotification(); // exit if(inStage!=NONE) return; _stage = PROCESS_HEADER; } if(_stage==PROCESS_HEADER) { // ensure CAConstants.CA_MESSAGE_HEADER_SIZE bytes of data if(_socketBuffer->getRemaining()getShort(); if((short)(_magicAndVersion&0xFFF0) !=CA_MAGIC_AND_MAJOR_VERSION) { // error... disconnect errlogSevPrintf( errlogMinor, "Invalid header received from client %s, disconnecting...", inetAddressToString(_socketAddress).c_str()); close(true); return; } // data vs. control packet _packetType = _socketBuffer->getByte(); // command _command = _socketBuffer->getByte(); // read payload size _payloadSize = _socketBuffer->getInt(); // data int8 type = (int8)(_packetType&0x0F); if(type==0) { _stage = PROCESS_PAYLOAD; } else if(type==1) { if(_command==0) { if(_markerToSend==0) _markerToSend = _payloadSize; // TODO send back response } else //if (command == 1) { int difference = (int)_totalBytesSent -_payloadSize+CA_MESSAGE_HEADER_SIZE; // overrun check if(difference<0) difference += INT_MAX; _remoteBufferFreeSpace = _remoteTransportReceiveBufferSize +_remoteTransportSocketReceiveBufferSize -difference; // TODO if this is calculated wrong, this can be critical !!! } // no payload //stage = ReceiveStage.PROCESS_HEADER; continue; } else { errlogSevPrintf( errlogMajor, "Unknown packet type %d, received from client %s, disconnecting...", type, inetAddressToString(_socketAddress).c_str()); close(true); return; } } if(_stage==PROCESS_PAYLOAD) { // read header int8 version = (int8)(_magicAndVersion&0xFF); // last segment bit set (means in-between segment or last segment) bool notFirstSegment = (_packetType&0x20)!=0; _storedPayloadSize = _payloadSize; // if segmented, exit reading code if(nestedCall&¬FirstSegment) return; // NOTE: nested data (w/ payload) messages between segmented messages are not supported _storedPosition = _socketBuffer->getPosition(); _storedLimit = _socketBuffer->getLimit(); _socketBuffer->setLimit(min(_storedPosition +_storedPayloadSize, _storedLimit)); try { // handle response _responseHandler->handleResponse(_socketAddress, this, version, _command, _payloadSize, _socketBuffer); } catch(...) { //noop } /* * Java finally start */ _socketBuffer->setLimit(_storedLimit); int newPosition = _storedPosition+_storedPayloadSize; if(newPosition>_storedLimit) { newPosition -= _storedLimit; _socketBuffer->setPosition(_storedLimit); processReadCached(true, PROCESS_PAYLOAD, newPosition, false); newPosition += _startPosition; } _socketBuffer->setPosition(newPosition); // TODO discard all possible segments?!!! /* * Java finally end */ _stage = PROCESS_HEADER; continue; } } } catch(...) { // close connection close(true); if(nestedCall) throw; } } bool BlockingTCPTransport::flush() { // TODO implement! return true; } } }