All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ascert.open.term.core.RWTelnet Maven / Gradle / Ivy

Go to download

An open source emulator supporting 3270 and potentially later 5250 terminal types.

The newest version!
/*
 * Copyright (c) 2016, 2017 Ascert, LLC.
 * www.ascert.com
 *
 * Based on original code from FreeHost3270, copyright for derivations from original works remain:
 *  Copyright (C) 1998, 2001  Art Gillespie
 *  Copyright (2) 2005 the http://FreeHost3270.Sourceforge.net
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.ascert.open.term.core;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import java.util.logging.Logger;
import java.util.logging.Level;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
 * TODO: - look into TN3270E protocol, SSCP LU device name, options etc https://tools.ietf.org/html/rfc1647
 *
 *
 * @since 0.1
 */
public class RWTelnet implements Runnable
{

    private static final Logger log = Logger.getLogger(RWTelnet.class.getName());

    /*          TELNET protocol constants       */
 /*          found on page 14 of RFC 845     */
    public static final short SE = 240;             //End of subnegotiation parameters
    public static final short NOP = 241;            //No Operation
    public static final short DATA_MARK = 242;            //data mark Operation
    public static final short BREAK = 243;            //break Operation
    public static final short SB = 250;             //Begin subnegotiation
    public static final short WILL = 251;           //Will perform an indicated option
    public static final short WONT = 252;           //Won't perform an indicated option
    public static final short DO = 253;             //Please perform an indicated option
    public static final short DONT = 254;           //Please don't perform an indicated option
    public static final short IAC = 255;            //The following bytes are a telnet command
    public static final short EOR = 239;            //End of record

    /*             TELNET OPTIONS                */
    public static final short OPT_BINARY = 0;           //Use 8-bit data path
    public static final short OPT_ECHO = 1;             //Echo
    public static final short OPT_SUPPRESS_GA = 3;
    public static final short TIMING_MARK = 6;
    public static final short OPT_TERMINAL_TYPE = 24;   // 
    public static final short OPT_EOR = 25;         //End of Record
    public static final short OPT_WINDOW = 31;          //Window size
    public static final short OPT_LINE_MODE = 34;

    public static final short OPTION_IS = 0;        //option is
    public static final short OPTION_SEND = 1;      //send option

    /*             TELNET STATES (internal)      */
    public static final short TN_DEFAULT = 0;       //default incoming data
    public static final short TN_IAC = 1;           //next is command
    public static final short TN_CMD = 2;           //incoming command
    public static final short TN_SUB_CMD = 3;       //incoming sub-command

    /*  CUSTOM CODES                                                                        */
    /**
     * Custom byte for flagging the next bytes as broadcast message.
     */
    public static final short BROADCAST = 245;

    public static byte[] shortArrayToByte(short[] buff, int offset, int len)
    {
        return shortArrayToByte(buff, offset, len, null);
    }

    public static byte[] shortArrayToByte(short[] buff, int offset, int len, byte[] dest)
    {
        if (dest == null)
        {
            dest = new byte[len];
        }

        for (int i = 0; i < len; i++)
        {
            // Cast our results from the byte array to unsigned shorts so none are negative
            dest[i] = (byte) buff[i + offset];
        }

        return dest;
    }

    public static short[] byteArrayToShort(byte[] buff)
    {
        return byteArrayToShort(buff, buff.length);
    }

    public static short[] byteArrayToShort(byte[] buff, int len)
    {
        short[] sBuf = new short[len];

        for (int i = 0; i < len; i++)
        {
            // Cast our results from the byte array to unsigned shorts so none are negative
            sBuf[i] = (short) Byte.toUnsignedInt(buff[i]);
        }

        return sBuf;
    }

    private InputStream is;
    private OutputStream os;
    protected TnStreamParser tnParser;
    private Socket tnSocket;
    private SSLSocket tnSocketSSL;
    private volatile Thread sessionThread;
    protected short[] bufferTerm; //put the 3270 bytes in here
    protected int bufferTermLen;
    private boolean[] doHistory;
    private short[] inBuf; //put raw data from the inputstream here
    private byte[] key;
    private short[] subOptionBuffer;
    private boolean[] willHistory;
    private boolean encryption;
    private int connectionTimeout;
    private int inBufLen;
    private int keyCounter;
    private int subOptionBufferLen;
    private int tnState;
    private short tnCommand;

    // Optional Tn commands - respond positively to a DO or WILL 
    private Set optTnCmds = new HashSet<>();
    // Required Tn commands - an error if we get a DONT or WONT
    private Set reqdTnCmds = new HashSet<>();
    // Currently negotiated commands - used to prevent loops
    private Set negTnCmds = new HashSet<>();
    // Determines whether optional and required commands are sent initially
    private final boolean sendInitialCmds;
    // Line mode options - if not null, will be used at startup
    protected byte[] lineModeOpts = null;

    protected TraceHandler traceHandler = null;
    // For keep Alive handling
    private long lastSendTS;
    private int keepAliveMillis;

    /**
     * DOCUMENT ME!
     *
     * @param rw          the Parser for the incoming data stream.
     * @param tn3270Model the tn3270 model number corresponding to this session.
     */
    public RWTelnet(TnStreamParser tnParser)
    {
        this(tnParser, new Short[]
         {
             OPT_TERMINAL_TYPE
        }, new Short[]
         {
             OPT_BINARY, OPT_EOR
        }, false);
    }

    public RWTelnet(TnStreamParser tnParser, Short[] reqdCmds, Short[] optCmds, boolean sendInitialCmds)
    {
        this.tnParser = tnParser;
        this.sendInitialCmds = sendInitialCmds;

        bufferTerm = new short[50000];
        bufferTermLen = 0;
        subOptionBuffer = new short[50];
        subOptionBufferLen = 0;
        tnState = TN_DEFAULT;
        keyCounter = 0;
        connectionTimeout = 60;
        doHistory = new boolean[3];
        willHistory = new boolean[3];
        // Temp for now - allow caller to supply
        reqdTnCmds.addAll(Arrays.asList(reqdCmds));
        optTnCmds.addAll(Arrays.asList(optCmds));
        // A Telnet client should never negotiate or refuse echo - so we add it all here in 
        // case, and make sure not set as required
        optTnCmds.add(OPT_ECHO);
        reqdTnCmds.remove(OPT_ECHO);
    }

    /**
     * The 'thread code' for this class. Consumers need to invoke this method to begin the communications process. It will run indefinitely
     * until the socket read returns -1 (Host disconnected) or disconnect is called. Usage  Thread t = new Thread(RWTelnet
     * instance);
t.run;
Any problems encountered (IOException, Host Disconnect) will be transmitted back to the consumer via * the TnAction interface. */ public void run() { int n = 0; log.finer("started the telnet thread"); try { sendInitialCommands(); startKeepAlive(); while (true) { if ((inBufLen = readSocket()) == -1) { log.finer("telnet socket is empty, disconnecting"); tnParser.status(TnAction.DISCONNECTED_BY_REMOTE_HOST); break; } synchronized (this) { parseData(); } } //rw.connectionStatus(TnAction.DISCONNECTED_BY_REMOTE_HOST); } catch (IOException e) { if (sessionThread == null) { log.log(Level.FINER, "Telnet thread loop terminated", e); } else { log.log(Level.SEVERE, "Failure in telnet thread loop", e); } tnParser.status(TnAction.DISCONNECTED_BY_REMOTE_HOST); } finally { disconnect(); } } /** * Performs a direct connection to a host terminal server. * * @param host destination server host name. * @param port destination terminal server port number. * * @throws UnknownHostException DOCUMENT ME! * @throws IOException DOCUMENT ME! */ protected void connect(String host, int port) throws UnknownHostException, IOException { log.fine("connecting to " + host + ":" + port); if (encryption) { log.fine("encrypted connection"); SSLSocketFactory sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault(); tnSocketSSL = (SSLSocket) sslFact.createSocket(); tnSocketSSL.connect(new InetSocketAddress(host, port), connectionTimeout * 1000); connect(tnSocketSSL.getInputStream(), tnSocketSSL.getOutputStream()); } else { tnSocket = new Socket(); tnSocket.connect(new InetSocketAddress(host, port), connectionTimeout * 1000); connect(tnSocket.getInputStream(), tnSocket.getOutputStream()); } } protected void connect(InputStream is, OutputStream os) { this.is = is; this.os = os; negTnCmds.clear(); tnState = TN_DEFAULT; sessionThread = new Thread(this); sessionThread.start(); } /** * Disconnects the current session. */ protected void disconnect() { if (sessionThread == null || tnSocket == null && tnSocketSSL == null) { log.fine("socket is null, not connected"); return; } try { Thread sessionNotify = sessionThread; sessionThread = null; sessionNotify.interrupt(); silentClose(is); silentClose(os); silentClose(encryption ? tnSocketSSL : tnSocket); willHistory = new boolean[3]; doHistory = new boolean[3]; log.fine("disconnected"); } catch (Exception e) { log.log(Level.SEVERE, "Error in disconnecting", e); } finally { is = null; os = null; tnSocketSSL = null; tnSocket = null; } } public boolean isConnected() { if (tnSocket != null) { return tnSocket.isConnected(); } else if (tnSocketSSL != null) { return tnSocketSSL.isConnected(); } else if (is != null && os != null) { // we have streams, so must be a direct connection return true; } return false; } protected void silentClose(Closeable cls) { if (cls != null) { try { cls.close(); } catch (Exception e) { log.finest("Closeable exception: " + e); } } } /** * Processes broadcast message. Is called when a broadcast message is received. * * @param netBuf DOCUMENT ME! */ protected void receiveMessage(short[] netBuf) { log.fine("received broadcast message"); char[] msg = new char[netBuf.length]; for (int i = 2; i < netBuf.length; i++) { msg[i - 2] = (char) netBuf[i]; } tnParser.broadcastMessage(new String(msg).trim()); } public boolean using(short opt) { return negTnCmds.contains(opt); } /** * This method provides outbound communication to the Telnet host. * * @param out an array of shorts, representing the data to be sent to the host. * @param outLen the number of valid bytes in the out array * * @throws IOException DOCUMENT ME! */ public void sendData(short[] out, int outLen) throws IOException { sendData(shortArrayToByte(out, 0, outLen, new byte[outLen + 2]), outLen); } public void sendData(byte[] out) throws IOException { sendData(out, out.length); } public void sendData(byte[] out, int outLen) throws IOException { if (os == null) { log.warning("attempt to send when telnet not connected, discarding"); return; } if (using(OPT_EOR)) { //add the is a command telnet command out[outLen++] = (byte) IAC; //add the end of record telnet command out[outLen++] = (byte) EOR; } send(out, 0, outLen); //System.out.println("Sent " + tmpByteBuf.length + " bytes"); //for(int i = 0; i < tmpByteBuf.length; i++) //System.out.print(Integer.toHexString(tmpByteBuf[i]) + " "); } // Precautionary sync so we can't get overlapping calls to put data into the // output buffer. public synchronized void send(byte[] out, int off, int outLen) throws IOException { lastSendTS = System.currentTimeMillis(); //write the data out to the EncryptedOutputStream os.write(out, off, outLen); os.flush(); if (traceHandler != null) { // possible we'd want this waited/queued to some async handler thread traceHandler.outgoingData(out, off, outLen); } } public void sendBreak() throws IOException { sendData(new byte[] { (byte) IAC, (byte) BREAK }); } public void sendNOP() throws IOException { sendData(new byte[] { (byte) IAC, (byte) NOP }); } /** * Turns the encryption on and off. * * @param encryption True = on False = off */ protected void setEncryption(boolean encryption) { this.encryption = encryption; } /** * Sets the connection timeout for the connect(String,int) method. * * @param timeout integer value in seconds */ protected void setConnectionTimeout(int timeout) { this.connectionTimeout = timeout; } public synchronized void setSessionData(String key, String value) { log.fine("SessionData Key: " + key + " Value: " + value); byte[] keyByte = charToByte(key.toCharArray()); byte[] valueByte = charToByte(value.toCharArray()); byte[] outData = new byte[keyByte.length + valueByte.length + 4]; outData[0] = (byte) 0xCC; outData[1] = (byte) 0xCC; System.arraycopy(keyByte, 0, outData, 2, keyByte.length); outData[keyByte.length + 2] = (byte) 0xCC; System.arraycopy(valueByte, 0, outData, keyByte.length + 3, valueByte.length); outData[keyByte.length + valueByte.length + 3] = (byte) 0xCC; try { os.write(outData, 0, outData.length); log.fine("SessionData sent to server"); } catch (IOException e) { // Seems somewhat bogus to swallow this? log.log(Level.SEVERE, "Error setting session data "+key+" to "+value, e); } } private byte[] charToByte(char[] c) { byte[] ret = new byte[c.length]; for (int i = 0; i < c.length; i++) { ret[i] = (byte) c[i]; } return ret; } /** * This method sends TELNET specific commands to the telnet host. Primarily this would be used for protocol feature negotiation. * * @param tnCmd DOCUMENT ME! * @param tnOption DOCUMENT ME! */ private void sendCommand(short tnCmd, short tnOption) throws IOException { log.finer(String.format("sending command: %s - %s", decodeCmd(tnCmd), decodeOpt(tnOption))); byte[] tmpBuffer = new byte[3]; tmpBuffer[0] = (byte) IAC; tmpBuffer[1] = (byte) tnCmd; tmpBuffer[2] = (byte) tnOption; send(tmpBuffer, 0, 3); } /** * This is a special instance of sendCommand where the client specifies that it is a 3270 client. * * @throws IOException DOCUMENT ME! */ public void sendTerminalType() throws IOException { String type = tnParser.getTermType(); log.fine("sending terminal type: " + type); byte[] bTyp = (type != null) ? type.getBytes() : "NO-TYPE".getBytes(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(new byte[] { (byte) IAC, (byte) SB }); baos.write(new byte[] { (byte) OPT_TERMINAL_TYPE, (byte) OPTION_IS }); baos.write(bTyp); baos.write(new byte[] { (byte) IAC, (byte) SE }); if (getLineModeOpts() != null) { log.finer("sending TN SUB OPTS : " + decodeSubOpts(byteArrayToShort(getLineModeOpts()), getLineModeOpts().length)); // strictly, line mode opts should be sent afte a DO LINE_MODE // for current usaes, sending after we've sent terminal type is workable baos.write(new byte[] { (byte) IAC, (byte) SB }); baos.write(this.getLineModeOpts()); baos.write(new byte[] { (byte) IAC, (byte) SE }); } send(baos.toByteArray(), 0, baos.size()); } // Override if needed protected void doIsTerminalType(String termType) { } //Probably should use lookup maps or Enums - quick hack for now or better tracing private String decodeOpt(short opt) { switch (opt) { case OPT_BINARY: return "BINARY"; case OPT_EOR: return "OPT_EOR"; case OPT_TERMINAL_TYPE: return "TERMINAL_TYPE"; case OPT_ECHO: return "ECHO"; case OPT_WINDOW: return "WINDOW"; case OPT_LINE_MODE: return "LINE_MODE"; case OPT_SUPPRESS_GA: return "SUPPRESS_GA"; default: return String.format("0x%x", opt); } } private String decodeCmd(short tnCmd) { switch (tnCmd) { case WILL: return "WILL"; case DO: return "DO"; case WONT: return "WONT"; case DONT: return "DONT"; default: return String.format("0x%x", tnCmd); } } public String decodeSubOpts(short[] subOptBuf, int len) { StringBuffer dBuf = new StringBuffer(); dBuf.append(decodeOpt(subOptBuf[0])); dBuf.append(" - "); for (int ix = 1; ix < len; ix++) { dBuf.append(String.format("0x%x ", subOptBuf[ix])); } return dBuf.toString(); } /** * This method handles incoming TELNET-specific commands. specifically, WILL, WONT, DO, DONT. * * @param tnCmd the incoming telnet command * @param tnOption the option for which the command is being sent * * @throws IOException DOCUMENT ME! */ private void handleTnCommand(short tnCmd, short tnOption) throws IOException { log.finer(String.format("received telnet command: %s - %s", decodeCmd(tnCmd), decodeOpt(tnOption))); short cmd; switch (tnCmd) { case WILL: case DO: if (optTnCmds.contains(tnOption) || reqdTnCmds.contains(tnOption)) { cmd = (tnCmd == WILL) ? DO : WILL; if (tnCmd == DO && !negTnCmds.add(tnOption)) { // Prevent endless loop negotiation as per RFC854 log.finer("tn option already present: " + tnOption); } else { log.finer("tn option not already present: " + tnOption); sendCommand(cmd, tnOption); } } else { cmd = (tnCmd == WILL) ? DONT : WONT; negTnCmds.remove(tnOption); sendCommand(cmd, tnOption); } break; case WONT: case DONT: if (reqdTnCmds.contains(tnOption)) { // Nasty, but what else can we do?? throw new IOException("Telnet WONT/DONT received for required option: " + decodeOpt(tnOption)); } else { cmd = (tnCmd == WONT) ? DONT : WONT; // Record option, and also prevent endless loop negotiation as per RFC854 if (tnCmd == DONT && !negTnCmds.remove(tnOption)) { log.finer("tn option was not present: " + tnOption); } else { log.finer("tn option was present: " + tnOption); sendCommand(cmd, tnOption); } sendCommand(cmd, tnOption); } } } protected void processTermBuffer() throws IOException { tnParser.parse(bufferTerm, bufferTermLen); bufferTermLen = 0; } private void handleUnframedData() throws IOException { if (!using(OPT_EOR) && bufferTermLen > 0) { processTermBuffer(); } } /** * Checks the input stream for commands and routes the stream appropriately. Standard data is stored in the bufferTerm * array and passed to the RWTelnetAction interface's refresh(buf, int) method when an EOR (End-of-record) * byte is detected. Other telnet commands (WILL WONT DO DONT IAC) are handled in accordance to RFC 845 * * @throws IOException DOCUMENT ME! */ private void parseData() throws IOException { short curr_byte; log.finer("parsing data"); //this if clause traps the inputStream if it is a broadcast message if ((inBuf[0] == IAC) && (inBuf[1] == BROADCAST)) { receiveMessage(inBuf); inBufLen = 0; return; } for (int i = 0; i < inBufLen; i++) { curr_byte = inBuf[i]; switch (tnState) { case TN_DEFAULT: if (curr_byte == IAC) { tnState = TN_IAC; } else { try { bufferTerm[bufferTermLen++] = curr_byte; } catch (ArrayIndexOutOfBoundsException ee) { log.fine("telnet buffer size: " + bufferTerm.length + " len: " + bufferTermLen); return; } } break; case TN_IAC: switch (curr_byte) { case IAC: //Two IACs in a row means this is really a single occurrence //of byte 255 (0xFF). (255 is its own escape character) bufferTerm[bufferTermLen++] = curr_byte; //Since it wasn't really an IAC, reset the tnState to default tnState = TN_DEFAULT; break; case EOR: //Done with this data record, send to Implementation via TnAction interface log.finer("TN EOR data len: " + bufferTermLen); processTermBuffer(); tnState = TN_DEFAULT; break; case WILL: case WONT: case DO: case DONT: // Check for and handle any unframed data that has been buffered as it // might be important to do this before processing any command handleUnframedData(); tnCommand = curr_byte; tnState = TN_CMD; break; case SB: // Check for and handle any unframed data that has been buffered as it // might be important to do this before processing any command handleUnframedData(); //System.err.println("Sub-option: " + subOptionBufferLen); subOptionBufferLen = 0; tnState = TN_SUB_CMD; break; } break; case TN_CMD: //System.out.println("CMD..."); handleTnCommand(tnCommand, curr_byte); //System.out.println("did command..."); tnState = TN_DEFAULT; break; case TN_SUB_CMD: if (curr_byte != SE) { // buffer until we reach end of negotiation subOptionBuffer[subOptionBufferLen++] = curr_byte; break; } handleSubOptions(); tnState = TN_DEFAULT; break; } } // Handle any leftover unframed data handleUnframedData(); } public void handleSubOptions() throws IOException { log.finer("> received TN SUB OPTS : " + decodeSubOpts(subOptionBuffer, subOptionBufferLen - 1)); switch (subOptionBuffer[0]) { case OPT_TERMINAL_TYPE: switch (subOptionBuffer[1]) { case OPTION_SEND: sendTerminalType(); break; case OPTION_IS: byte[] bType = RWTelnet.shortArrayToByte(subOptionBuffer, 2, subOptionBufferLen - 3); doIsTerminalType(new String(bType)); break; default: log.severe("Invalid Terminal Type command sub-option:: " + subOptionBuffer[1]); } break; default: this.tnParser.telnetSubOpts(this.subOptionBuffer, this.subOptionBufferLen - 1); } } private int readSocket() throws IOException { inBufLen = 2048; // Since inputstreams require a byte array as a parameter (and // not a short[]), we have to use this temporary buffer to // store the results. byte[] tmpByteBuf = new byte[inBufLen]; int bytes_read = is.read(tmpByteBuf, 0, inBufLen); if (bytes_read > -1) { // System.out.println("Done... "); if (traceHandler != null) { // possible we'd want this waited/queued to some async handler thread traceHandler.incomingData(tmpByteBuf, 0, bytes_read); } inBuf = byteArrayToShort(tmpByteBuf, bytes_read); } //System.out.println("Bytes read: " + bytes_read); return bytes_read; } public void processDataIn(byte[] buff) throws IOException { processDataIn(buff, buff.length); } public void processDataIn(byte[] buff, int len) throws IOException { inBuf = byteArrayToShort(buff, len); inBufLen = len; parseData(); } public void setOutputStream(OutputStream os) { this.os = os; } public void setTraceHandler(TraceHandler traceHandler) { this.traceHandler = traceHandler; } public void sendTnCommand(short tnCmd, short tnOption) throws IOException { switch (tnCmd) { case WILL: case DO: reqdTnCmds.add(tnOption); //negTnCmds.add(tnOption); break; case WONT: case DONT: reqdTnCmds.remove(tnOption); //negTnCmds.remove(tnOption); break; } sendCommand(tnCmd, tnOption); } public void sendTnSubOpts(short[] optVals) throws IOException { log.finer("sending TN SUB OPTS : " + decodeSubOpts(optVals, optVals.length)); byte[] tmpBuffer = new byte[4 + optVals.length]; tmpBuffer[0] = (byte) IAC; tmpBuffer[1] = (byte) SB; for (int ix = 0; ix < optVals.length; ix++) { tmpBuffer[2 + ix] = (byte) optVals[ix]; } tmpBuffer[tmpBuffer.length - 2] = (byte) IAC; tmpBuffer[tmpBuffer.length - 1] = (byte) SE; send(tmpBuffer, 0, tmpBuffer.length); } private void sendInitialCommands() throws IOException { if (!this.sendInitialCmds) { return; } Set cmds = new HashSet<>(); cmds.addAll(this.reqdTnCmds); cmds.addAll(this.optTnCmds); ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (short cmd : cmds) { // All except ECHO are fair game for client to say they will support. if (cmd != OPT_ECHO) { log.finer(String.format("sending initial command: %s - %s", decodeCmd(WILL), decodeOpt(cmd))); baos.write((byte) IAC); baos.write((byte) WILL); baos.write((byte) cmd); //negTnCmds.add(cmd); } } send(baos.toByteArray(), 0, baos.size()); } /** * @return the lineModeOpts */ public byte[] getLineModeOpts() { return lineModeOpts; } /** * @param lineModeOpts the lineModeOpts to set */ public void setLineModeOpts(byte[] lineModeOpts) { this.lineModeOpts = lineModeOpts; } void setKeepAliveTimeout(int keepAliveTimeout) { this.keepAliveMillis = keepAliveTimeout * 1000; log.fine("Keep alive timeout: " + this.keepAliveMillis); } void startKeepAlive() { if (keepAliveMillis > 0) { log.fine("Starting keep alive thread"); Thread kaThread = new Thread(new KeepAliveRunner()); kaThread.start(); } } private class KeepAliveRunner implements Runnable { @Override public void run() { while (true) { try { Thread.sleep(keepAliveMillis); if (lastSendTS < (System.currentTimeMillis() - keepAliveMillis)) { log.fine("Sending keep alive ..."); sendNOP(); } } catch (IOException ex) { // Will probably get caught and handled elsewhere anyway - warn for now, disconnect on multiple maybe log.warning("IO Exception on KeepAlive: " + ex); } catch (InterruptedException ex) { // Nothing we need to do here really } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy