/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.jwebsocket.tcp.connectors; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.InetAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Arrays; import org.apache.log4j.Logger; import org.jwebsocket.api.EngineConfiguration; import org.jwebsocket.api.WebSocketPacket; import org.jwebsocket.api.WebSocketConnector; import org.jwebsocket.api.WebSocketEngine; import org.jwebsocket.config.JWebSocketCommonConstants; import org.jwebsocket.connectors.BaseConnector; import org.jwebsocket.kit.CloseReason; import org.jwebsocket.kit.RawPacket; import org.jwebsocket.logging.Logging; /** * Implementation of the jWebSocket TCP socket connector. * @author aschulze */ public class TCPConnector extends BaseConnector { private static Logger mLog = Logging.getLogger(TCPConnector.class); private InputStream mIn = null; private OutputStream mOut = null; private Socket mClientSocket = null; private boolean mIsRunning = false; private CloseReason mCloseReason = CloseReason.TIMEOUT; /** * creates a new TCP connector for the passed engine using the passed * client socket. Usually connectors are instantiated by their engine * only, not by the application. * @param aEngine * @param aClientSocket */ public TCPConnector(WebSocketEngine aEngine, Socket aClientSocket) { super(aEngine); mClientSocket = aClientSocket; try { mIn = mClientSocket.getInputStream(); mOut = new PrintStream(mClientSocket.getOutputStream(), true, "UTF-8"); } catch (Exception ex) { mLog.error(ex.getClass().getSimpleName() + " instantiating " + getClass().getSimpleName() + ": " + ex.getMessage()); } } @Override public void startConnector() { int lPort = -1; int lTimeout = -1; try { lPort = mClientSocket.getPort(); lTimeout = mClientSocket.getSoTimeout(); } catch (Exception ex) { } if (mLog.isDebugEnabled()) { mLog.debug("Starting TCP connector on port " + lPort + " with timeout " + (lTimeout > 0 ? lTimeout + "ms" : "infinite") + ""); } ClientProcessor clientProc = new ClientProcessor(this); Thread clientThread = new Thread(clientProc); clientThread.start(); if (mLog.isInfoEnabled()) { mLog.info("Started TCP connector on port " + lPort + " with timeout " + (lTimeout > 0 ? lTimeout + "ms" : "infinite") + ""); } } @Override public void stopConnector(CloseReason aCloseReason) { if (mLog.isDebugEnabled()) { mLog.debug("Stopping TCP connector (" + aCloseReason.name() + ")..."); } int lPort = mClientSocket.getPort(); mCloseReason = aCloseReason; mIsRunning = false; try { mIn.close(); if (mLog.isInfoEnabled()) { mLog.info("Stopped TCP connector (" + aCloseReason.name() + ") on port " + lPort + "."); } } catch (IOException ex) { if (mLog.isDebugEnabled()) { mLog.info(ex.getClass().getSimpleName() + " while stopping TCP connector (" + aCloseReason.name() + ") on port " + lPort + ": " + ex.getMessage()); } } } @Override public void processPacket(WebSocketPacket aDataPacket) { // forward the data packet to the engine getEngine().processPacket(this, aDataPacket); } @Override public synchronized void sendPacket(WebSocketPacket aDataPacket) { try { if (aDataPacket.getFrameType() == RawPacket.FRAMETYPE_BINARY) { // each packet is enclosed in 0xFF<length><data> // TODO: for future use! Not yet finally spec'd in IETF drafts! mOut.write(0xFF); byte[] lBA = aDataPacket.getByteArray(); // TODO: implement multi byte length! mOut.write(lBA.length); mOut.write(lBA); } else { // each packet is enclosed in 0x00<data>0xFF mOut.write(0x00); mOut.write(aDataPacket.getByteArray()); mOut.write(0xFF); } mOut.flush(); } catch (IOException ex) { mLog.error(ex.getClass().getSimpleName() + " sending data packet: " + ex.getMessage()); } } private class ClientProcessor implements Runnable { private WebSocketConnector connector = null; /** * Creates the new socket listener thread for this connector. * @param aConnector */ public ClientProcessor(WebSocketConnector aConnector) { connector = aConnector; } @Override public void run() { WebSocketEngine engine = getEngine(); int lMaxFrameSize = JWebSocketCommonConstants.DEFAULT_MAX_FRAME_SIZE; EngineConfiguration config = engine.getConfiguration(); if (config != null && config.getMaxFramesize() > 0) { lMaxFrameSize = config.getMaxFramesize(); } byte[] lBuff = new byte[lMaxFrameSize]; int pos = -1; int lStart = -1; try { // start client listener loop mIsRunning = true; // call connectorStarted method of engine engine.connectorStarted(connector); while (mIsRunning) { try { int b = mIn.read(); // start of frame if (b == 0x00) { pos = 0; lStart = 0; // end of frame } else if (b == 0xff) { if (lStart >= 0) { if (pos <= lMaxFrameSize) { RawPacket lPacket = new RawPacket(Arrays.copyOf(lBuff, pos)); try { engine.processPacket(connector, lPacket); } catch (Exception ex) { mLog.error(ex.getClass().getSimpleName() + " in processPacket of connector " + connector.getClass().getSimpleName() + ": " + ex.getMessage()); } } else { mLog.error("Datapacket exceeded maximum size of " + lMaxFrameSize + " bytes and will not be processed!"); } } lStart = -1; // end of stream } else if (b < 0) { mCloseReason = CloseReason.CLIENT; mIsRunning = false; // any other byte within or outside a frame } else { if (lStart >= 0 && pos < lMaxFrameSize) { lBuff[pos] = (byte) b; } pos++; } } catch (SocketTimeoutException ex) { mLog.error("(timeout) " + ex.getClass().getSimpleName() + ": " + ex.getMessage()); mCloseReason = CloseReason.TIMEOUT; mIsRunning = false; } catch (Exception ex) { mLog.error("(other) " + ex.getClass().getSimpleName() + ": " + ex.getMessage()); mCloseReason = CloseReason.SERVER; mIsRunning = false; } } // call client stopped method of engine // (e.g. to release client from streams) engine.connectorStopped(connector, mCloseReason); // br.close(); mIn.close(); mOut.close(); mClientSocket.close(); } catch (Exception ex) { // ignore this exception for now mLog.error("(close) " + ex.getClass().getSimpleName() + ": " + ex.getMessage()); } } } @Override public String generateUID() { String lUID = mClientSocket.getInetAddress().getHostAddress() + "@" + mClientSocket.getPort(); return lUID; } @Override public int getRemotePort() { return mClientSocket.getPort(); } @Override public InetAddress getRemoteHost() { return mClientSocket.getInetAddress(); } @Override public String toString() { // TODO: weird results like... '0:0:0:0:0:0:0:1:61130'... on JDK 1.6u19 Windows 7 64bit String lRes = getRemoteHost().getHostAddress() + ":" + getRemotePort(); // TODO: don't hard code. At least use JWebSocketConstants field here. String lUsername = getString("org.jWebSocket.plugins.system.username"); if (lUsername != null) { lRes += " (" + lUsername + ")"; } return lRes; } }