├── releases ├── MANIFEST.MF ├── iCometClient4j.jar ├── iCometClient4j-2.0.0.jar └── iCometClient4j-2.0.1.jar ├── lib └── gson.jar ├── UPDATE.md ├── src └── com │ └── kyleduo │ └── icomet │ ├── Channel.java │ ├── ICometConf.java │ ├── ChannelAllocator.java │ ├── DefaultChannelAllocator.java │ ├── ICometCallback.java │ ├── IConnCallback.java │ ├── message │ ├── MessageInputStream.java │ └── Message.java │ ├── demo │ └── Demo.java │ └── ICometClient.java └── README.md /releases/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: lib/gson.jar -------------------------------------------------------------------------------- /lib/gson.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleduo/iCometClient4j/HEAD/lib/gson.jar -------------------------------------------------------------------------------- /releases/iCometClient4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleduo/iCometClient4j/HEAD/releases/iCometClient4j.jar -------------------------------------------------------------------------------- /releases/iCometClient4j-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleduo/iCometClient4j/HEAD/releases/iCometClient4j-2.0.0.jar -------------------------------------------------------------------------------- /releases/iCometClient4j-2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleduo/iCometClient4j/HEAD/releases/iCometClient4j-2.0.1.jar -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | #version 2.0.2 2 | * Make Content Object to implment Serializable interface, for more convient transfer by Intent in Android. 3 | 4 | ##version 2.0.0 5 | >Update for iComet latest version. 6 | * Change structure of Message class. -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/Channel.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | /** 4 | * 5 | * used for authority of your business 6 | * 7 | * @author zhangduo 8 | * 9 | */ 10 | public class Channel { 11 | public String cname; 12 | public String token; 13 | public int seq; 14 | } 15 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/ICometConf.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | 4 | public class ICometConf { 5 | public String host; 6 | public String port; 7 | public String url; 8 | 9 | public ChannelAllocator channelAllocator; 10 | public ICometCallback iCometCallback; 11 | public IConnCallback iConnCallback; 12 | } 13 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/ChannelAllocator.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | /** 4 | * 5 | * you should implement this interface to connect to your own server for the channel, token, sequence 6 | * 7 | * @author kyleduo 8 | * 9 | */ 10 | public interface ChannelAllocator { 11 | /** 12 | * you should never return null for this method 13 | * 14 | * @return Channel channel 15 | */ 16 | public Channel allocate(); 17 | } 18 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/DefaultChannelAllocator.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | /** 4 | * a default channel allocator, it is suggested that you should use your own allocator class 5 | * 6 | * @author kyleduo 7 | * 8 | */ 9 | public class DefaultChannelAllocator implements ChannelAllocator { 10 | 11 | @Override 12 | public Channel allocate() { 13 | Channel channel = new Channel(); 14 | channel.cname = ""; 15 | channel.token = ""; 16 | channel.seq = 1; 17 | return channel; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/ICometCallback.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | import com.kyleduo.icomet.message.Message; 4 | 5 | public interface ICometCallback { 6 | 7 | // a message with data arrived 8 | public void onDataMsgArrived(Message.Content content); 9 | 10 | // a message arrived, maybe not with data 11 | public void onMsgArrived(Message msg); 12 | 13 | // a error message arrived 14 | public void onErrorMsgArrived(Message msg); 15 | 16 | // message format error, can not parse json 17 | public void onMsgFormatError(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/IConnCallback.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | public interface IConnCallback { 4 | 5 | // connect to iComet server unsuccessfully 6 | public void onFail(String msg); 7 | 8 | // connect to iComet server successfully 9 | public void onSuccess(); 10 | 11 | // connection cut by server or there's an error 12 | public void onDisconnect(); 13 | 14 | // connection stopped by user (as well as the client) 15 | public void onStop(); 16 | 17 | /** 18 | * called when the client need to reconnect to the server 19 | * @param times show how many times this reconnection is 20 | * @return boolean return true if you want to intercept the reconnection; false for that you want the client to try to reconnect to the iComet server 21 | */ 22 | public boolean onReconnect(int times); 23 | 24 | // reconnection success 25 | public void onReconnectSuccess(int times); 26 | } 27 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/message/MessageInputStream.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet.message; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | 7 | import com.google.gson.Gson; 8 | 9 | public class MessageInputStream { 10 | private InputStream input; 11 | private BufferedReader reader; 12 | 13 | public MessageInputStream(InputStream input) { 14 | this.input = input; 15 | reader = new BufferedReader(new InputStreamReader(this.input)); 16 | } 17 | 18 | private String read() throws Exception { 19 | return reader.readLine(); 20 | } 21 | 22 | /** 23 | * get a message from the input stream, possibly a stream from sub connection 24 | * 25 | * @return a general message, caller should judge the type of this message and do the following work 26 | * @throws Exception 27 | */ 28 | public Message readMessage() throws Exception { 29 | Message msg = null; 30 | 31 | Gson gson = new Gson(); 32 | String jsonData = this.read(); 33 | if (jsonData != null) { 34 | msg = gson.fromJson(jsonData, Message.class); 35 | } 36 | return msg; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/message/Message.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet.message; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Message { 6 | public static class Type { 7 | public static String TYPE_DATA = "data"; 8 | public static String TYPE_NOOP = "noop"; 9 | // too many channels/subscribers 10 | public static String TYPE_429 = "429"; 11 | // error token 12 | public static String TYPE_401 = "401"; 13 | } 14 | 15 | public String type; 16 | public String cname; 17 | public String seq; 18 | public String content; 19 | 20 | public static class Content implements Serializable { 21 | 22 | private static final long serialVersionUID = 4340957908804000989L; 23 | 24 | public String time; 25 | public String from; 26 | public String text; 27 | public String id; 28 | 29 | @Override 30 | public String toString() { 31 | return "Content [time=" + time + ", from=" + from + ", text=" + text + ", id=" + id + "]"; 32 | } 33 | 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Message [type=" + type + ", cname=" + cname + ", seq=" + seq + ", content=" + content + "]"; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/demo/Demo.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet.demo; 2 | 3 | import com.kyleduo.icomet.Channel; 4 | import com.kyleduo.icomet.ChannelAllocator; 5 | import com.kyleduo.icomet.ICometCallback; 6 | import com.kyleduo.icomet.ICometClient; 7 | import com.kyleduo.icomet.ICometConf; 8 | import com.kyleduo.icomet.IConnCallback; 9 | import com.kyleduo.icomet.message.Message; 10 | import com.kyleduo.icomet.message.Message.Content; 11 | 12 | /** 13 | * Demo 14 | * 15 | * @author keyleduo 16 | */ 17 | public class Demo { 18 | 19 | private static ICometClient mClient; 20 | 21 | public static void main(String args[]) { 22 | mClient = ICometClient.getInstance(); 23 | ICometConf conf = new ICometConf(); 24 | conf.host = "127.0.0.1"; 25 | conf.port = "8100"; 26 | conf.url = "stream"; 27 | conf.iConnCallback = new MyConnCallback(); 28 | conf.iCometCallback = new MyCometCallback(); 29 | conf.channelAllocator = new NoneAuthChannelAllocator(); 30 | mClient.prepare(conf); 31 | mClient.connect(); 32 | } 33 | 34 | public static class MyConnCallback implements IConnCallback { 35 | 36 | @Override 37 | public void onFail(String msg) { 38 | System.out.println("connection fail"); 39 | System.err.println(msg); 40 | } 41 | 42 | @Override 43 | public void onSuccess() { 44 | System.out.println("connection ok"); 45 | mClient.comet(); 46 | } 47 | 48 | @Override 49 | public void onDisconnect() { 50 | System.err.println("connection has been cut off"); 51 | } 52 | 53 | @Override 54 | public void onStop() { 55 | System.out.println("client has been stopped"); 56 | } 57 | 58 | @Override 59 | public boolean onReconnect(int times) { 60 | System.err.println("This is the " + times + "st times."); 61 | if (times >= 3) { 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | @Override 68 | public void onReconnectSuccess(int times) { 69 | System.out.println("onReconnectSuccess at " + times + "st time"); 70 | mClient.comet(); 71 | } 72 | 73 | } 74 | 75 | public static class MyCometCallback implements ICometCallback { 76 | 77 | @Override 78 | public void onDataMsgArrived(Content content) { 79 | System.out.println("data msg arrived: " + content); 80 | } 81 | 82 | @Override 83 | public void onMsgArrived(Message msg) { 84 | System.out.println("msg arrived: " + msg); 85 | } 86 | 87 | @Override 88 | public void onErrorMsgArrived(Message msg) { 89 | System.err.println("error message arrived with type: " + msg.type); 90 | } 91 | 92 | @Override 93 | public void onMsgFormatError() { 94 | System.err.println("message format error"); 95 | } 96 | 97 | } 98 | 99 | public static class NoneAuthChannelAllocator implements ChannelAllocator { 100 | 101 | @Override 102 | public Channel allocate() { 103 | Channel channel = new Channel(); 104 | channel.cname = "kyle"; 105 | channel.token = "token"; 106 | channel.seq = 0; 107 | return channel; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iCometClient4j 2 | === 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-iCometClient4j-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1120) 4 | 5 | Client of iComet server for Java/Android. iComet server: https://github.com/ideawu/icomet 6 | 7 | This project is based the iComet service which can be found in https://github.com/ideawu/icomet created by ideawu(www.ideawu.com). 8 | 9 | Simply, this project is an implements of client for iComet service. You can use it in your java project as well as Android project. 10 | 11 | If you want to know what the effect of this client is look at, there's an demo in Java prepared for running in none auth mode. Obviously, you need an iComet server. Mine is run on my mac under default configuration. 12 | 13 | ### Demo 14 | 15 | There are two demos in this project for Java android Android. 16 | 17 | The one for Android is based on the one for Java by using the release jar file. You can find it in https://github.com/ideawu/icomet-demos , where you can also find a updated website demo. 18 | 19 | AndroidDemo can be your reference in your own project, but it is suggested that you should implement the logic yourself to match your own business. If there are bugs, please mail me. 20 | 21 | ### Usage 22 | 23 | Here's some decription of how to use this client. 24 | 25 | Using getInstance() method to get an instance of ICometClient. 26 | 27 | Before you connet to the iComet server, you should make the client prepared with the ICometConf object by calling prepare(ICometConf) method. The ICometConf object should be completely initialed with those params which you can find under. 28 | 29 | mClient = ICometClient.getInstance(); 30 | 31 | ICometConf conf = new ICometConf(); 32 | conf.host = "127.0.0.1"; 33 | conf.port = "8100"; 34 | conf.url = "stream"; 35 | conf.iConnCallback = new MyConnCallback(); 36 | conf.iCometCallback = new MyCometCallback(); 37 | conf.channelAllocator = new NoneAuthChannelAllocator(); 38 | 39 | mClient.prepare(conf); 40 | 41 | As you can see, there are one ChannelAllocator and two Callbacks for your business. 42 | 43 | The ChannelAllocator is used to connect to your own server and request a channel for connetiong to iComet server and a token for authorization if you need it. Also, seq is configurated in this allocator, such that you can decide whether to fetch some old message of that channel. There is only one method in this interface for now: 44 | 45 | allocate(); 46 | 47 | As their names say, the two Callbacks is used for connection and fetching process separately. It is important that you should always implement these two interface and put them into ICometConf object. Following by a outline of these two Callbacks: 48 | 49 | IConnCallback { 50 | 51 | public void onFail(String msg); 52 | 53 | public void onSuccess(); 54 | 55 | public void onDisconnect(); 56 | 57 | public void onStop(); 58 | 59 | public boolean onReconnect(int times); 60 | 61 | public void onReconnectSuccess(int times); 62 | } 63 | 64 | ICometCallback { 65 | 66 | public void onDataMsgArrived(Message.Content content); 67 | 68 | public void onMsgArrived(Message msg); 69 | 70 | public void onErrorMsgArrived(Message msg); 71 | 72 | public void onMsgFormatError(); 73 | } 74 | 75 | When the client is ready, just a invoke of connect() method will connect it to iComet server. If you use this client in an Android project, you should invoke this method in an children thread, otherwise you would get a NetworkOnMainThreadException. 76 | 77 | Then the IConnCallback will be invoked, if there's nothing wrong, you can use comet() method to listen the connection and you will receive the messages from iCometServer which you can deal with them by ICometCallback. 78 | 79 | There's something else to know about the ICometCallback, the onMsgArrived() method will be invoked when any message arrive while onDataMsgArrived() and onErrorMsgArrived() will be invoked on for arriving of specific message related to their name. 80 | 81 | Much more information will be in wiki and docs pages soon. 82 | 83 | Have fun with it! 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/com/kyleduo/icomet/ICometClient.java: -------------------------------------------------------------------------------- 1 | package com.kyleduo.icomet; 2 | 3 | import java.net.HttpURLConnection; 4 | import java.net.URL; 5 | import java.util.Timer; 6 | import java.util.TimerTask; 7 | 8 | import com.google.gson.Gson; 9 | import com.kyleduo.icomet.message.Message; 10 | import com.kyleduo.icomet.message.MessageInputStream; 11 | 12 | public class ICometClient { 13 | 14 | public static class State { 15 | // status for a client just created 16 | public static final int STATE_NEW = 0; 17 | // status for a prepared client 18 | public static final int STATE_READY = 1; 19 | // status for a client which has connected to iComet server 20 | public static final int STATE_CONNCTED = 2; 21 | // status for a client working with sending or receiving message 22 | public static final int STATE_COMET = 3; 23 | // comet was stopped manually 24 | public static final int STATE_STOP = 4; 25 | // stop the comet client 26 | public static final int STATE_STOP_PENDING = 5; 27 | // disconnect from iComet server, usually by error 28 | public static final int STATE_DISCONNECT = 6; 29 | } 30 | 31 | // delay for reconnection, if times of reconnection is larger than 3, treat as 3 32 | // you can deal with reconnection with onReconnect() method. too much times of reconnection is not recommended 33 | private static final int[] DELAY = { 30, 120, 600 }; 34 | 35 | // host for your iComet server 36 | // private String host; 37 | // // port of the server for iComet 38 | // private String port; 39 | // the URL for connection to iComet server 40 | private String finalUrl; 41 | // record the times of reconnection 42 | private int mReconnTimes = 0; 43 | // channel object got from your business server 44 | private Channel mChannel; 45 | 46 | private static ICometClient mClient = new ICometClient(); 47 | private ICometCallback mICometCallback; 48 | private IConnCallback mIConnCallback; 49 | 50 | private HttpURLConnection mConn; 51 | private MessageInputStream mInput; 52 | 53 | private ICometConf mConf; 54 | 55 | // current status 56 | private int mStatus = State.STATE_NEW; 57 | 58 | private ICometClient() { 59 | 60 | } 61 | 62 | /** 63 | * get the single Instance of ICometClient 64 | * 65 | * @return 66 | */ 67 | public static ICometClient getInstance() { 68 | if (mClient == null) { 69 | mClient = new ICometClient(); 70 | } 71 | return mClient; 72 | } 73 | 74 | /** 75 | * prepare for connection 76 | * 77 | * @param conf 78 | */ 79 | public void prepare(ICometConf conf) { 80 | if (conf.channelAllocator == null) { 81 | conf.channelAllocator = new DefaultChannelAllocator(); 82 | } 83 | mConf = conf; 84 | if (mReconnTimes == 0) { 85 | this.mChannel = conf.channelAllocator.allocate(); 86 | } 87 | this.finalUrl = buildURL(conf.url); 88 | this.mICometCallback = conf.iCometCallback; 89 | this.mIConnCallback = conf.iConnCallback; 90 | this.mStatus = State.STATE_READY; 91 | } 92 | 93 | /** 94 | * connect to iComet server please call this method in a child thread 95 | */ 96 | public void connect() { 97 | if (this.mStatus != State.STATE_READY) { 98 | return; 99 | } 100 | try { 101 | mConn = (HttpURLConnection) new URL(this.finalUrl).openConnection(); 102 | mConn.setRequestMethod("GET"); 103 | mConn.setConnectTimeout(3 * 60 * 1000); 104 | mConn.setDoInput(true); 105 | mConn.connect(); 106 | mInput = new MessageInputStream(mConn.getInputStream()); 107 | 108 | } catch (Exception e) { 109 | if (mConn != null) { 110 | mConn.disconnect(); 111 | } 112 | if (mIConnCallback != null) { 113 | mIConnCallback.onFail(e.getMessage()); 114 | } 115 | reconnect(); 116 | return; 117 | } 118 | 119 | this.mStatus = State.STATE_CONNCTED; 120 | 121 | if (mIConnCallback != null) { 122 | if (mReconnTimes == 0) { 123 | mIConnCallback.onSuccess(); 124 | } else { 125 | mIConnCallback.onReconnectSuccess(mReconnTimes); 126 | mReconnTimes = 0; 127 | } 128 | } 129 | 130 | } 131 | 132 | /** 133 | * start a new thread to deal with the data transfer 134 | */ 135 | public void comet() { 136 | if (this.mStatus != State.STATE_CONNCTED) { 137 | return; 138 | } 139 | this.mStatus = State.STATE_COMET; 140 | new SubThread().start(); 141 | 142 | } 143 | 144 | /** 145 | * close the connection to iComet server 146 | */ 147 | public void stopComet() { 148 | mStatus = State.STATE_STOP_PENDING; 149 | } 150 | 151 | /** 152 | * stop connecting to iComet server 153 | */ 154 | public void stopConnect() { 155 | if (mConn != null) { 156 | mConn.disconnect(); 157 | mConn = null; 158 | } 159 | } 160 | 161 | /** 162 | * get current status of this client 163 | * 164 | * @return status 165 | * @see Status 166 | */ 167 | public int currStatus() { 168 | return mStatus; 169 | } 170 | 171 | /** 172 | * used to reconnect to the server when the connection lose or an error occur 173 | */ 174 | private void reconnect() { 175 | if (mIConnCallback == null) { 176 | return; 177 | } 178 | 179 | Timer timer = new Timer(); 180 | timer.schedule(new TimerTask() { 181 | 182 | @Override 183 | public void run() { 184 | mReconnTimes++; 185 | if (!mIConnCallback.onReconnect(mReconnTimes)) { 186 | if (mStatus != State.STATE_READY) { 187 | prepare(mConf); 188 | } 189 | connect(); 190 | } 191 | } 192 | }, DELAY[mReconnTimes > 2 ? 2 : mReconnTimes] * 1000); 193 | 194 | } 195 | 196 | /** 197 | * build the URL by method and args 198 | * 199 | * @param method 200 | * pub sub sign frame 201 | * @param args 202 | * argument 203 | * @return URL 204 | */ 205 | private String buildURL(String url) { 206 | StringBuilder sb = new StringBuilder(); 207 | if (!this.mConf.host.startsWith("http://")) { 208 | sb.append("http://"); 209 | } 210 | sb.append(this.mConf.host); 211 | if (!isEmpty(this.mConf.port)) { 212 | sb.append(":").append(this.mConf.port); 213 | } 214 | if (!isEmpty(url)) { 215 | sb.append("/").append(url); 216 | } 217 | if (mChannel == null) { 218 | return sb.toString(); 219 | } 220 | 221 | sb.append("?"); 222 | sb.append("cname=").append(mChannel.cname); 223 | sb.append("&").append("seq=").append(mChannel.seq); 224 | sb.append("&").append("token=").append(mChannel.token); 225 | return sb.toString(); 226 | } 227 | 228 | /** 229 | * thread for retrieving data from iComet server 230 | * 231 | * @author keyleduo 232 | */ 233 | private class SubThread extends Thread { 234 | 235 | private Gson gson = new Gson(); 236 | 237 | @Override 238 | public void run() { 239 | super.run(); 240 | 241 | if (mICometCallback == null) { 242 | throw new IllegalArgumentException("There always should be an ICometCallback to deal with the coming data"); 243 | } 244 | 245 | try { 246 | while (mStatus == ICometClient.State.STATE_COMET) { 247 | // block here 248 | Message msg = mInput.readMessage(); 249 | 250 | if (msg != null) { 251 | 252 | mICometCallback.onMsgArrived(msg); 253 | 254 | if (msg.type.equals(Message.Type.TYPE_DATA) && msg.content != null && msg.content.length() > 0) { 255 | // here comes a data message 256 | Message.Content content = gson.fromJson(msg.content, Message.Content.class); 257 | mChannel.seq++; 258 | mICometCallback.onDataMsgArrived(content); 259 | 260 | } else if (msg.type.equals(Message.Type.TYPE_NOOP)) { 261 | 262 | } else { 263 | mICometCallback.onErrorMsgArrived(msg); 264 | } 265 | 266 | } else { 267 | // TODO error data 268 | 269 | } 270 | } 271 | } catch (Exception e) { 272 | e.printStackTrace(); 273 | mIConnCallback.onDisconnect(); 274 | mStatus = ICometClient.State.STATE_DISCONNECT; 275 | reconnect(); 276 | return; 277 | } 278 | 279 | mStatus = ICometClient.State.STATE_STOP; 280 | if (mIConnCallback != null) { 281 | mIConnCallback.onStop(); 282 | } 283 | } 284 | } 285 | 286 | /** 287 | * judge if the source is empty 288 | * 289 | * @param source 290 | * @return 291 | */ 292 | public boolean isEmpty(String source) { 293 | if (source == null || source.length() < 1) { 294 | return true; 295 | } 296 | return false; 297 | } 298 | 299 | } 300 | --------------------------------------------------------------------------------