/** Drops this socket; may trigger a reconnect. */ public void drop() { synchronized (_lock) { if (_shutdown) return; Stream.safeClose(_socket); } }
/** Creates a new socket, cleaning up if anything goes wrong in the process */ private MulticastSocket createMulticastSocket(String label, InetAddress intf, int port) throws Exception { MulticastSocket socket = null; try { _logger.info( "Preparing {} socket. interface:{}, port:{}, group:{}", label, (intf == null ? "default" : intf), (port == 0 ? "any" : port), _group); // in previous versions the interface was selected using constructor instead of // 'socket.setInterface(intf)' // but that uncovered side-effect in OSX which caused 'cannot assign address' Java bug socket = new MulticastSocket(port); // (port '0' means any port) if (intf != null) socket.setInterface(intf); // join the multicast group socket.joinGroup(_group); _logger.info("{} ready. localAddr:{}", label, socket.getLocalSocketAddress()); return socket; } catch (Exception exc) { Stream.safeClose(socket); throw exc; } }
/** Permanently shuts down all related resources. */ @Override public void close() throws IOException { // clear flag _enabled = false; // release timers for (TimerTask timer : _timers) timer.cancel(); Stream.safeClose(_sendSocket, _receiveSocket, _hardLinksSocket); }
/** * Check if the main receiver has been silent for some time so recycle the socket for best * resilience. */ private void recycleReceiverIfNecessary(long currentTime) { long timeDiff = (currentTime - _lastExternalMulticastPacket) / 1000000; if (timeDiff > SILENCE_TOLERANCE) { _logger.info( "There appears to be external silence on the multicast receiver (this may or may not be expected); the socket will be recycled to ensure resilience."); synchronized (_lock) { // recycle receiver which will in turn recycle sender _recycleReceiver = true; // "signal" the other thread Stream.safeClose(_receiveSocket); } } }
/** Used to monitor address changes on the the interface. */ private void handleInterfaceCheck() { try { int port = super.getAdvertisementPort(); if (port < 0) { // can't compose a nodel address yet _logger.info("(nodel server port still not available; will wait.)"); return; } InetAddress localIPv4Address = getLocalIPv4Address(); String nodelAddress = "tcp://" + localIPv4Address.getHostAddress() + ":" + port; if (nodelAddress.equals(_nodelAddress)) // nothing to do return; // the address has changed so should update advertisements _logger.info( "An address change has been detected. previous={}, current={}", _nodelAddress, nodelAddress); _nodelAddress = "tcp://" + localIPv4Address.getHostAddress() + ":" + port; _httpAddress = "http://" + localIPv4Address.getHostAddress() + ":" + Nodel.getHTTPPort() + Nodel.getHTTPSuffix(); Nodel.updateHTTPAddress(_httpAddress); synchronized (_lock) { // recycle receiver which will in turn recycle sender _recycleReceiver = true; // "signal" thread Stream.safeClose(_receiveSocket); } } catch (Exception exc) { _logger.warn("'handleInterfaceCheck' did not complete cleanly; ignoring for now.", exc); } } // (method)
/** Permanently shuts down this managed TCP connection. */ @Override public void close() { synchronized (_lock) { if (_shutdown) return; _shutdown = true; _outputStream = null; if (_startTimer != null) _startTimer.cancel(); Stream.safeClose(_socket); _socket = null; // notify the connection and receive thread if it happens to be waiting _lock.notify(); } }
/** (thread entry-point) */ private void hardLinksReceiverThreadMain() { _logger.info("Instructed to use hardlinks. address:{}", _hardLinksAddresses); DatagramSocket socket = null; try { // initialise a UDP socket on an arbitrary port _hardLinksSocket = new DatagramSocket(); socket = _hardLinksSocket; while (_enabled) { DatagramPacket dp = UDPPacketRecycleQueue.instance().getReadyToUsePacket(); // ('returnPacket' will be called in 'catch' or later after use in thread-pool) try { socket.receive(dp); s_unicastInData.addAndGet(dp.getLength()); s_unicastInOps.incrementAndGet(); enqueueForProcessing(dp, s_hardLinksSocketlabel); } catch (Exception exc) { UDPPacketRecycleQueue.instance().returnPacket(dp); // ignore } } // (while) } catch (Exception exc) { _logger.warn("Failed to initialise [" + s_hardLinksSocketlabel + "] socket.", exc); } finally { _logger.info("[" + s_hardLinksSocketlabel + "] thread run to completion."); // close for good measure Stream.safeClose(socket); } }
/** * The reading loop will continually read until an error occurs or the stream is gracefully ended * by the peer. * * <p>(Suppress 'resource' ignored because 'in' stream is closed in calling function.) */ @SuppressWarnings("resource") private void readLengthDelimitedLoop(Socket socket, char startFlag, Character optStopFlag) throws Exception { InputStream in = socket.getInputStream(); BufferedInputStream bis = new BufferedInputStream( new CountableInputStream(in, _counterRecvOps, _counterRecvRate), 1024); // create a buffer that'll be reused // start off small, will grow as needed BufferBuilder bb = new BufferBuilder(256); // create an additional temporary buffer which can be used with 'length' delimited parsing byte[] buffer = new byte[1024]; // got 'start flag'? boolean synced = false; // how many bytes to read int bytesToRead = 0; while (!_shutdown) { int c = bis.read(); if (c < 0) break; // discard data until we sync up with the 'start' flag if (!synced) { if (c == startFlag) { synced = true; // (put back) bb.append(c); // read 'length' (value excludes 'start', 'stop' flags) int len1 = Stream.readUnsignedByte(bis); int len2 = Stream.readUnsignedByte(bis); int length = Stream.readUnsignedShort(len1, len2); if (length >= MAX_SEGMENT_ALLOWED) // drop the connection throw new IOException( "Packet length indicates it is too large to accept (" + Formatting.formatByteLength(length) + ")."); // (put back) bb.append(len1); bb.append(len2); // already read 2 ('length') length bytesToRead = length - 2; } else { // not synced, so keep discarding continue; } } while (!_shutdown) { // we're synced, so can read as much as we need to if (bytesToRead > 0) { int bytesRead = bis.read(buffer, 0, Math.min(bytesToRead, buffer.length)); if (bytesRead < 0) throw new EOFException(); bytesToRead -= bytesRead; // 'copy' the buffer bb.append(buffer, 0, bytesRead); } else { // we've read enough break; } } if (optStopFlag != null) { c = Stream.readUnsignedByte(bis); if (c != optStopFlag.charValue()) throw new IOException("Stop flag was not matched after reading the packet bytes."); // (put back) bb.append(c); // fire the handled event String str = bb.getRawString(); handleReceivedData(str); bb.reset(); synced = false; } } // (while) // the peer has gracefully closed down the connection or we're shutting down if (!_shutdown) { // drop left over data and fire the disconnected callback Handler.tryHandle(_disconnectedCallback, _callbackErrorHandler); } }
/** Establishes a socket and continually reads. */ private void connectAndRead() throws Exception { Socket socket = null; OutputStream os = null; try { socket = new Socket(); InetSocketAddress socketAddress = parseAndResolveDestination(_dest); socket.connect(socketAddress, CONNECT_TIMEOUT); _counterConnections.incr(); socket.setSoTimeout(_timeout); // 'inject' countable stream os = new CountableOutputStream(socket.getOutputStream(), _counterSendOps, _counterSendRate); // update flag _lastSuccessfulConnection = System.nanoTime(); synchronized (_lock) { if (_shutdown) return; _socket = socket; _outputStream = os; // connection has been successful so reset variables // related to exponential back-off _recentlyConnected = true; _backoffTime = MIN_CONNETION_GAP; } // fire the connected event _callbackHandler.handle(_connectedCallback, _callbackErrorHandler); // start reading if (_mode == Modes.LengthDelimitedRaw) readLengthDelimitedLoop(socket, _binaryStartFlag, _binaryStopFlag); else if (_mode == Modes.CharacterDelimitedText) readTextLoop(socket); else { // mode is 'UnboundedRaw' readUnboundedRawLoop(socket); } // (any non-timeout exceptions will be propagated to caller...) } catch (Exception exc) { // fire the disconnected handler if was previously connected if (os != null) Handler.tryHandle(_disconnectedCallback, _callbackErrorHandler); throw exc; } finally { // always gracefully close the socket and invalidate the socket fields synchronized (_lock) { _socket = null; _outputStream = null; } Stream.safeClose(socket); } }
/** (thread entry-point) */ private void unicastReceiverThreadMain() { while (_enabled) { MulticastSocket socket = null; try { synchronized (_lock) { // clear flag regardless _recycleSender = false; } socket = createMulticastSocket(s_sendSocketLabel, s_interface, 0); // make sure a recycle request hasn't since occurred synchronized (_lock) { if (_recycleSender) { Stream.safeClose(socket); continue; } _sendSocket = socket; } while (_enabled) { DatagramPacket dp = UDPPacketRecycleQueue.instance().getReadyToUsePacket(); try { socket.receive(dp); } catch (Exception exc) { UDPPacketRecycleQueue.instance().returnPacket(dp); throw exc; } if (dp.getAddress().isMulticastAddress()) { s_multicastInData.addAndGet(dp.getLength()); s_multicastInOps.incrementAndGet(); } else { s_unicastInData.addAndGet(dp.getLength()); s_unicastInOps.incrementAndGet(); } enqueueForProcessing(dp, s_sendSocketLabel); } // (inner while) } catch (Exception exc) { boolean wasClosed = (socket != null && socket.isClosed()); // clean up regardless Stream.safeClose(socket); synchronized (_lock) { if (!_enabled) break; if (wasClosed) _logger.info( s_sendSocketLabel + " was signalled to gracefully close. Will reinitialise..."); else _logger.warn(s_sendSocketLabel + " receive failed; will reinitialise...", exc); // stagger retry Threads.waitOnSync(_lock, 333); } } } // (outer while) _logger.info("This thread has run to completion."); }
/** (thread entry-point) */ private void multicastReceiverThreadMain() { while (_enabled) { MulticastSocket socket = null; try { synchronized (_lock) { // clear flag regardless _recycleReceiver = false; } socket = createMulticastSocket(s_receiveSocketlabel, s_interface, MDNS_PORT); synchronized (_lock) { // make sure not flagged since reset if (_recycleReceiver) { Stream.safeClose(socket); continue; } _receiveSocket = socket; } while (_enabled) { DatagramPacket dp = UDPPacketRecycleQueue.instance().getReadyToUsePacket(); // ('returnPacket' will be called in 'catch' or later after use in thread-pool) try { socket.receive(dp); } catch (Exception exc) { UDPPacketRecycleQueue.instance().returnPacket(dp); throw exc; } InetAddress recvAddr = dp.getAddress(); if (recvAddr.isMulticastAddress()) { s_multicastInData.addAndGet(dp.getLength()); s_multicastInOps.incrementAndGet(); } else { s_unicastInData.addAndGet(dp.getLength()); s_unicastInOps.incrementAndGet(); } // check whether it's external i.e. completely different IP address // (local multicasting would almost always be reliable) MulticastSocket otherLocal = _sendSocket; boolean isLocal = (otherLocal != null && recvAddr.equals(otherLocal.getLocalAddress())); // update counter which is used to detect silence if (!isLocal) _lastExternalMulticastPacket = System.nanoTime(); enqueueForProcessing(dp, s_receiveSocketlabel); } // (inner while) } catch (Exception exc) { // (timeouts and general IO problems) // clean up regardless Stream.safeClose(socket); synchronized (_lock) { if (!_enabled) break; if (_recycleReceiver) _logger.info(s_receiveSocketlabel + " was gracefully closed. Will reinitialise..."); else _logger.warn( s_receiveSocketlabel + " receive failed; this may be a transitional condition. Will reinitialise... message was '" + exc.toString() + "'"); // set flag _recycleSender = true; // "signal" other thread Stream.safeClose(_sendSocket); // stagger retry Threads.waitOnSync(_lock, 333); } } } // (outer while) _logger.info("This thread has run to completion."); } // (method)