Java tutorial
/* * Juick * Copyright (C) 2008-2012, Ugnich Anton * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.juick.android; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.ArrayList; import android.content.Context; import android.util.Log; import com.juickadvanced.data.MessageID; import com.juickadvanced.data.juick.JuickMessage; import com.juick.android.juick.JuickCompatibleURLMessagesSource; import com.juickadvanced.data.juick.JuickMessageID; import com.juickadvanced.data.point.PointMessage; import org.apache.http.util.ByteArrayBuffer; import org.json.JSONException; import org.json.JSONObject; /** * * @author Ugnich Anton */ public class WsClient implements ThreadFragment.ThreadExternalUpdater { public static int instanceCount; { instanceCount++; } static final byte keepAlive[] = { (byte) 0x81, (byte) 0x01, (byte) 0x20 }; static final byte closeConnection[] = { (byte) 0x88, (byte) 0x00 }; Socket sock; InputStream is; OutputStream os; Listener listener; Context ctx; JuickMessageID mid; boolean terminated; private final Thread wsthr; private int beforePausedCounter; @Override public void terminate() { terminated = true; setPaused(false); // these are various means to terminate socket disconnect(); listener = null; // these are measures to unreference gui if sockets stuck anyway ctx = null; Log.w("UgnichWS", "inst=" + toString() + ": terminated=" + terminated); } @Override public void setListener(Listener listener) { this.listener = listener; } @Override public void setPaused(boolean paused) { beforePausedCounter = paused ? 3 : 0; } public WsClient(Context context, final MessageID mid) { this.ctx = context; wsthr = new Thread(new Runnable() { public void run() { while (!terminated) { if (performConnect(mid)) { readLoop(); } } } }, "Websocket thread: mid=" + mid); wsthr.start(); } protected boolean performConnect(MessageID mid) { return connect("ws.juick.com", 80, "/" + ((JuickMessageID) mid).getMid(), null); } public String getOrigin() { return "http://juick.com/"; } public boolean connect(String host, int port, String location, String headers) { try { sock = new Socket(host, port); sock.setSoTimeout(15000); is = sock.getInputStream(); os = sock.getOutputStream(); String handshake = "GET " + location + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Origin: " + getOrigin() + "\r\n" + "User-Agent: JuickAdvanced\r\n" + "Sec-WebSocket-Key: SomeKey\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n"; if (headers != null) { handshake += headers; } handshake += "\r\n"; os.write(handshake.getBytes()); return true; } catch (Exception e) { System.err.println(e); //e.printStackTrace(); return false; } } public boolean isConnected() { return sock.isConnected(); } public void readLoop() { try { int b; int byteCnt = 0; boolean bigPacket = false; int PacketLength = 0; ByteArrayBuffer buf = new ByteArrayBuffer(16); boolean flagInside = false; while (!terminated) { try { b = is.read(); if (b == -1) break; if (terminated) break; } catch (SocketTimeoutException e) { if (beforePausedCounter-- < 0) { sock.setSoTimeout(3 * 60 * 1000); } Log.w("UgnichWS", "inst=" + toString() + ": read sotimeout, terminated=" + terminated); continue; } if (flagInside) { byteCnt++; if (byteCnt == 1) { if (b < 126) { PacketLength = b + 1; bigPacket = false; } else { bigPacket = true; } } else { if (byteCnt == 2 && bigPacket) { PacketLength = b << 8; } if (byteCnt == 3 && bigPacket) { PacketLength |= b; PacketLength += 3; } if (byteCnt > 3 || !bigPacket) { buf.append((char) b); } } if (byteCnt == PacketLength && listener != null) { if (PacketLength > 2) { if (listener != null) { String incomingData = new String(buf.toByteArray(), "utf-8"); final ArrayList<JuickMessage> messages = convertMessages(incomingData); if (messages.size() > 0) { listener.onNewMessages(messages); } } } else { os.write(keepAlive); os.flush(); } flagInside = false; } } else if (b == 0x81) { buf.clear(); flagInside = true; byteCnt = 0; } } } catch (Exception e) { Log.e("UgnichWS", "inst=" + toString(), e); } finally { Log.w("UgnichWS", "inst=" + toString() + " DISCONNECTED readLoop"); } } protected ArrayList<JuickMessage> convertMessages(String incomingData) throws JSONException { JuickCompatibleURLMessagesSource jcus = new JuickCompatibleURLMessagesSource(ctx, "dummy"); return jcus.parseJSONpure("[" + incomingData + "]"); } public void disconnect() { try { os.write(closeConnection); os.flush(); } catch (Exception e) { } try { is.close(); os.close(); sock.close(); } catch (Exception e) { System.err.println(e); } } @Override protected void finalize() throws Throwable { super.finalize(); instanceCount--; } }