精华内容
下载资源
问答
  • WebSocket区分不同客户端两种方法(HttpSession和@PathParam)
  • WebSocket安卓客户端实现详解(二)--客户端发送请求

    万次阅读 多人点赞 2017-08-06 11:02:08
    WebSocket安卓客户端实现详解(二)–客户端发送请求本篇接着上一篇讲解WebSocket客户端发送请求和服务端主动通知消息,还没看过第一篇的小伙伴,这里附上第一篇链接WebSocket安卓客户端实现详解(一)–连接建立与重连....

    本篇接着上一篇讲解WebSocket客户端发送请求和服务端主动通知消息,还没看过第一篇的小伙伴,这里附上第一篇链接WebSocket安卓客户端实现详解(一)–连接建立与重连.

    本篇依旧干货十足内容很多所以我们还是先热身下

    客户端发送请求

    为了方便我们协议使用json格式,当然你也可以替换成ProtoBuffer.

    这里先展示下发送请求的流程图,大体流程在第一篇已经讲过了这里不再赘述,直接搂细节.

    既然是请求,那么得有一个请求协议.这里我们不直接展示协议,而是通过http请求,引导到websocket请求协议.

    一般http请求我们会需要如下几个参数

    1. url

    2. 请求参数

    3. 回调

    而websocket是一个长连接,我们所有的请求都是通过这个连接发送的,也就是说我们没法像http那样通过一个url来区分不同请求,那么我们请求协议中就必须要有一个类似url的东西去区分请求,这里我用action字段来表示url,为了方便协议的扩展,我们在加一个参数req_event,由action和req_event共同组成url,下面是一个简单的例子.

    {
        "action": " login",
        "req_event": 0
    }
    

    由于请求是异步的所以当我们请求的时候需要把回调加入集合保存起来,等到服务端响应的时候我们需要从集合中找到对应回调调用对应的方法,那么为了能够找到对应回调我们需要给每个请求增加一个唯一标识,这里我用的seq_id字段表示,当请求的时候我们把客户端自己生成的seq_id传给服务端,然后当服务端响应的时候在回传给我们,这样我们就能通过seq_id作为key找到对应的回调,那么现在协议将变成下面这样.

    {
        "action": " login",
        "req_event": 0,
        "seq_id":0
    }
    

    在加上每个请求都有的请求参数,这里我用req字段表示,最终协议如下.

    {
        "action": " login",
        "req_event": 0,
        "seq_id":0,
        "req":{
          "aaa":"aaa",
          "bbb":0
        }
    }
    

    协议有了,那我们谈谈代码的实现,对于请求方法我们对外暴露如下参数.

    1. Action

    这里我把action、req_event、响应实体统一用一个枚举类Action来存储,调用者只需根据不同请求传入对应Action即可.

    1. Req

    请求参数

    1. Callback

    回调

    1. timeout(可选参数,默认给的10秒)

    请求超时时间.

    show code

    Action一个枚举类,把action、req_event、响应实体统一封装在一起,action和req_event用来组装url,响应实体在反序列化的时候会用到.调用者只需根据不同请求传入对应Action即可.

    public enum Action {
        LOGIN("login", 1, null);
    
        private String action;
        private int reqEvent;
        private Class respClazz;
    
    
        Action(String action, int reqEvent, Class respClazz) {
            this.action = action;
            this.reqEvent = reqEvent;
            this.respClazz = respClazz;
        }
    
    
        public String getAction() {
            return action;
        }
    
    
        public int getReqEvent() {
            return reqEvent;
        }
    
    
        public Class getRespClazz() {
            return respClazz;
        }
    
    }
    

    ui层回调

    public interface ICallback<T> {
    
        void onSuccess(T t);
    
        void onFail(String msg);
    
    }
    

    协议对应的请求实体类Request,这里还额外添加了一个没有参加序列化的参数reqCount来表示该请求的请求次数,在后面请求失败情况下对该请求的处理会用上.

    public class Request<T> {
        @SerializedName("action")
        private String action;
    
        @SerializedName("req_event")
        private int reqEvent;
    
        @SerializedName("seq_id")
        private long seqId;
    
        @SerializedName("req")
        private T req;
    
        private transient int reqCount;
    
        public Request(String action, int reqEvent, long seqId, T req, int reqCount) {
            this.action = action;
            this.reqEvent = reqEvent;
            this.seqId = seqId;
            this.req = req;
            this.reqCount = reqCount;
        }
    
        ....
        //这里还有各个参数对应get、set方法,为节省篇幅省略了
    
        public static class Builder<T> {
            private String action;
            private int reqEvent;
            private long seqId;
            private T req;
            private int reqCount;
    
            public Builder action(String action) {
                this.action = action;
                return this;
            }
    
            public Builder reqEvent(int reqEvent) {
                this.reqEvent = reqEvent;
                return this;
            }
    
            public Builder seqId(long seqId) {
                this.seqId = seqId;
                return this;
            }
    
            public Builder req(T req) {
                this.req = req;
                return this;
            }
    
            public Builder reqCount(int reqCount) {
                this.reqCount = reqCount;
                return this;
            }
    
            public Request build() {
                return new Request<T>(action, reqEvent, seqId, req, reqCount);
            }
    
        }
    }
    

    WsManager中发送请求代码

    public class WsManager{
    
      ....跟之前相同代码省略.....
    
      private static final int REQUEST_TIMEOUT = 10000;//请求超时时间
      private AtomicLong seqId = new AtomicLong(SystemClock.uptimeMillis());//每个请求的唯一标识
    
      public void sendReq(Action action, Object req, ICallback callback) {
          sendReq(action, req, callback, REQUEST_TIMEOUT);
      }
    
    
      public void sendReq(Action action, Object req, ICallback callback, long timeout) {
          sendReq(action, req, callback, timeout, 1);
      }
    
    
      /**
       * @param action Action
       * @param req 请求参数
       * @param callback 回调
       * @param timeout 超时时间
       * @param reqCount 请求次数
       */
      @SuppressWarnings("unchecked")
      private <T> void sendReq(Action action, T req, ICallback callback, long timeout, int reqCount) {
          if (!isNetConnect()) {
              callback.onFail("网络不可用");
              return;
          }
    
          Request request = new Request.Builder<T>()
              .action(action.getAction())
              .reqEvent(action.getReqEvent())
              .seqId(seqId.getAndIncrement())
              .reqCount(reqCount)
              .req(req)
              .build();
    
          Logger.t(TAG).d("send text : %s", new Gson().toJson(request));
          ws.sendText(new Gson().toJson(request));
      }
    
      ....跟之前相同代码省略.....
    
    }
    

    sendReq(Action action, T req, ICallback callback, long timeout, int reqCount)中有reqCount参数是因为在后面请求失败情况下会根据该请求进行请求的次数执行不同的策略.

    超时和回调的处理

    发送请求ok了接下来需要处理回调的问题了.虽然在方法sendReq(Action action, T req, ICallback callback, long timeout, int reqCount)中我们已经传入了ui层的回调ICallback,但这里还需要在封装一层回调处理一些通用逻辑,然后再调用ICallback对应方法.

    需要处理的通用逻辑有如下三个.

    1. 请求成功

    将服务端返回数据从子线程传到主线程,然后调用ui层回调.

    1. 请求失败

    将失败信息从子线程传到主线程,然后调用ui层回调.

    1. 请求超时

    该请求的reqCount <= 3再次通过websocket发送请求,reqCount > 3通过http通道发送请求,根据结果直接调用对应回调.

    中间层回调定义如下

    public interface IWsCallback<T> {
        void onSuccess(T t);
        void onError(String msg, Request request, Action action);
        void onTimeout(Request request, Action action);
    }
    

    onSuccess与普通的成功回调一样,onError和onTimeout回调中有Request与Action是为了方便后续再次请求操作.

    接下来showcode.

      ....跟之前相同代码省略.....
      private final int SUCCESS_HANDLE = 0x01;
      private final int ERROR_HANDLE = 0x02;
    
      private Handler mHandler = new Handler(Looper.getMainLooper()) {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case SUCCESS_HANDLE:
                      CallbackDataWrapper successWrapper = (CallbackDataWrapper) msg.obj;
                      successWrapper.getCallback().onSuccess(successWrapper.getData());
                      break;
                  case ERROR_HANDLE:
                      CallbackDataWrapper errorWrapper = (CallbackDataWrapper) msg.obj;
                      errorWrapper.getCallback().onFail((String) errorWrapper.getData());
                      break;
              }
          }
      };
    
      private <T> void sendReq(Action action, T req, final ICallback callback, final long timeout, int reqCount) {
          if (!isNetConnect()) {
              callback.onFail("网络不可用");
              return;
          }
    
          Request request = new Request.Builder<T>()
              .action(action.getAction())
              .reqEvent(action.getReqEvent())
              .seqId(seqId.getAndIncrement())
              .reqCount(reqCount)
              .req(req)
              .build();
    
          IWsCallback temp = new IWsCallback() {
    
              @Override
              public void onSuccess(Object o) {
                  mHandler.obtainMessage(SUCCESS_HANDLE, new CallbackDataWrapper(callback, o))
                      .sendToTarget();
              }
    
    
              @Override
              public void onError(String msg, Request request, Action action) {
                  mHandler.obtainMessage(ERROR_HANDLE, new CallbackDataWrapper(callback, msg))
                      .sendToTarget();
              }
    
    
              @Override
              public void onTimeout(Request request, Action action) {
                  timeoutHandle(request, action, callback, timeout);
              }
          };
    
          Logger.t(TAG).d("send text : %s", new Gson().toJson(request));
          ws.sendText(new Gson().toJson(request));
      }
    
      /**
       * 超时处理
       */
      private void timeoutHandle(Request request, Action action, ICallback callback, long timeout) {
          if (request.getReqCount() > 3) {
              Logger.t(TAG).d("(action:%s)连续3次请求超时 执行http请求", action.getAction());
              //走http请求
          } else {
              sendReq(action, request.getReq(), callback, timeout, request.getReqCount() + 1);
              Logger.t(TAG).d("(action:%s)发起第%d次请求", action.getAction(), request.getReqCount());
          }
      }
    
      ....跟之前相同代码省略.....
    

    Handler中获取的Callback与Data包装类如下

    public class CallbackDataWrapper<T> {
    
        private ICallback<T> callback;
        private Object data;
    
        public CallbackDataWrapper(ICallback<T> callback, Object data) {
            this.callback = callback;
            this.data = data;
        }
    
        public ICallback<T> getCallback() {
            return callback;
        }
    
    
        public void setCallback(ICallback<T> callback) {
            this.callback = callback;
        }
    
    
        public Object getData() {
            return data;
        }
    
    
        public void setData(Object data) {
            this.data = data;
        }
    }
    
    

    从上面代码中可以看出对于需要处理的逻辑1和2在回调中通过CallbackDataWrapper包装callback和data发送到主线程handler在调用对应的回调处理.

    需要处理的逻辑3则在timeoutHandle()方法中实现,具体http请求这里我没实现,大家只需要调用自己项目封装的http请求api即可,当然为了方便这里建议还是以我们前面定义的websocket协议的形式执行http请求,这样我们就不需要重新组装请求参数了.

    超时后的处理有了,接下来我们实现添加超时任务代码.

    ....跟之前相同代码省略.....
    
    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    private Map<Long, CallbackWrapper> callbacks = new HashMap<>();
    
    @SuppressWarnings("unchecked")
    private <T> void sendReq(Action action, T req, final ICallback callback, final long timeout, int reqCount) {
        if (!isNetConnect()) {
            callback.onFail("网络不可用");
            return;
        }
    
        Request request = new Request.Builder<T>()
            .action(action.getAction())
            .reqEvent(action.getReqEvent())
            .seqId(seqId.getAndIncrement())
            .reqCount(reqCount)
            .req(req)
            .build();
    
        ScheduledFuture timeoutTask = enqueueTimeout(request.getSeqId(), timeout);//添加超时任务
    
        IWsCallback tempCallback = new IWsCallback() {
    
            @Override
            public void onSuccess(Object o) {
                mHandler.obtainMessage(SUCCESS_HANDLE, new CallbackDataWrapper(callback, o))
                    .sendToTarget();
            }
    
    
            @Override
            public void onError(String msg, Request request, Action action) {
                mHandler.obtainMessage(ERROR_HANDLE, new CallbackDataWrapper(callback, msg))
                    .sendToTarget();
            }
    
    
            @Override
            public void onTimeout(Request request, Action action) {
                timeoutHandle(request, action, callback, timeout);
            }
        };
    
        callbacks.put(request.getSeqId(),
            new CallbackWrapper(tempCallback, timeoutTask, action, request));
    
        Logger.t(TAG).d("send text : %s", new Gson().toJson(request));
        ws.sendText(new Gson().toJson(request));
    }
    
    /**
     * 添加超时任务
     */
    private ScheduledFuture enqueueTimeout(final long seqId, long timeout) {
        return executor.schedule(new Runnable() {
            @Override
            public void run() {
                CallbackWrapper wrapper = callbacks.remove(seqId);
                if (wrapper != null) {
                    Logger.t(TAG).d("(action:%s)第%d次请求超时", wrapper.getAction().getAction(), wrapper.getRequest().getReqCount());
                    wrapper.getTempCallback().onTimeout(wrapper.getRequest(), wrapper.getAction());
                }
            }
        }, timeout, TimeUnit.MILLISECONDS);
    }
    
    ....跟之前相同代码省略.....
    

    这里我们用的ScheduledThreadPoolExecutor来处理超时任务,通过ScheduledThreadPoolExecutor.schedule()方法间隔一定时长执行超时的处理.当然为了能进行超时的处理我们需要将callback保存起来,又由于超时的时候我们需要进行再次请求所以不仅需要callback还需要request与action,至于执行超时任务返回的ScheduledFuture这个我们会在服务端响应的时候取消超时任务用上这边我提前添加了.最终通过CallbackWrapper将他们包装了起来.

    callback包装类

    
    public class CallbackWrapper {
    
        private final IWsCallback tempCallback;
        private final ScheduledFuture timeoutTask;
        private final Action action;
        private final Request request;
    
    
        public CallbackWrapper(IWsCallback tempCallback, ScheduledFuture timeoutTask, Action action, Request request) {
            this.tempCallback = tempCallback;
            this.timeoutTask = timeoutTask;
            this.action = action;
            this.request = request;
        }
    
    
        public IWsCallback getTempCallback() {
            return tempCallback;
        }
    
    
        public ScheduledFuture getTimeoutTask() {
            return timeoutTask;
        }
    
    
        public Action getAction() {
            return action;
        }
    
    
        public Request getRequest() {
            return request;
        }
    }
    
    

    接下来是请求的最后一步了服务端响应的处理.这里我们还是先从协议入手.

    首先说明下对于长连接无论是我们请求的响应还是服务端的主动通知都是通过同一个回调方法回调给客户端我们没法区分,所以服务端给我们数据中需要带一个标志来区分普通请求的响应与服务端主动通知.这里我们用resp_event来表示,当resp_event为10的时候是请求的响应,为20的时候是服务端的主动通知.

    对于请求的响应我们需要通过seq_id找到对应的回调,对于主动通知我们需要添加一个action来区分这个通知执行什么操作,在加上响应数据体resp协议就定制完成了.当然seq_id应该在客户端请求的响应的时候才有,action应该在服务端主动通知的时候才有.这里为了解析的方便最外层通用协议如下.

    {
        "resp_event": 10,
        "action": "",
        "seq_id": 11111111,
        "resp": {}
    }
    

    对于我们客户端主动请求的响应数据可以按照http请求的格式来定义code,msg,data那么最终数据结构如下

    {
        "resp_event": 10,
        "action": "",
        "seq_id": 11111111,
        "resp": {
          "code":0,
          "msg":"ok",
          "data":{
    
          }
        }
    }
    

    对于服务端主动的推送不需要code和msg那么数据结构改为如下

    {
        "resp_event": 20,
        "action": "",
        "seq_id": 11111111,
        "resp": {
    
          }
        }
    }
    

    可以看出最外层json数据结构一样,从resp开始不同.这里对于客户端主动请求的响应的情况我已经封装好了Codec工具类去解析,服务端主动推送的话后面再说。

    最外层bean如下

    public class Response {
    
        @SerializedName("resp_event")
        private int respEvent;
    
        @SerializedName("seq_id")
        private String seqId;
    
        private String action;
        private String resp;
    
        //省略get set方法
    }
    
    

    第二层bean如下

    public class ChildResponse {
    
        private int code;
        private String msg;
        private String data;
    
        public boolean isOK(){
            return code == 0;
        }
    
        //省略get set方法
    }
    
    

    对应的解析工具类

    public class Codec {
    
        public static Response decoder(String text) {
            Response response = new Response();
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(text);
            if (element.isJsonObject()) {
                JsonObject obj = (JsonObject) element;
                response.setRespEvent(decoderInt(obj, "resp_event"));
                response.setAction(decoderStr(obj, "action"));
                response.setSeqId(decoderStr(obj, "seq_id"));
                response.setResp(decoderStr(obj, "resp"));
                return response;
            }
            return response;
        }
    
        private static int decoderInt(JsonObject obj, String name) {
            int result = -1;
            JsonElement element = obj.get(name);
            if (null != element) {
                try {
                    result = element.getAsInt();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    
        private static String decoderStr(JsonObject obj, String name) {
            String result = "";
            try {
                JsonElement element = obj.get(name);
                if (null != element && element.isJsonPrimitive()) {
                    result = element.getAsString();
                } else if (null != element && element.isJsonObject()) {
                    result = element.getAsJsonObject().toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
        public static ChildResponse decoderChildResp(String jsonStr) {
            ChildResponse childResponse = new ChildResponse();
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(jsonStr);
            if (element.isJsonObject()) {
                JsonObject jsonObject = (JsonObject) element;
                childResponse.setCode(decoderInt(jsonObject, "code"));
                childResponse.setMsg(decoderStr(jsonObject, "msg"));
                childResponse.setData(decoderStr(jsonObject, "data"));
            }
            return childResponse;
        }
    
    }
    
    

    接下来就是响应的处理了

    ....跟之前相同代码省略.....
    
    class WsListener extends WebSocketAdapter {
        @Override
        public void onTextMessage(WebSocket websocket, String text) throws Exception {
            super.onTextMessage(websocket, text);
            Logger.t(TAG).d("receiverMsg:%s", text);
    
            Response response = Codec.decoder(text);//解析出第一层bean
            if (response.getRespEvent() == 10) {//响应
                CallbackWrapper wrapper = callbacks.remove(Long.parseLong(response.getSeqId()));//找到对应callback
                if (wrapper == null) {
                    Logger.t(TAG).d("(action:%s) not found callback", response.getAction());
                    return;
                }
    
                try {
                    wrapper.getTimeoutTask().cancel(true);//取消超时任务
                    ChildResponse childResponse = Codec.decoderChildResp(response.getResp());//解析第二层bean
                    if (childResponse.isOK()) {
    
                        Object o = new Gson().fromJson(childResponse.getData(),
                            wrapper.getAction().getRespClazz());
    
                        wrapper.getTempCallback().onSuccess(o);
                    } else {
                        wrapper.getTempCallback()
                            .onError(ErrorCode.BUSINESS_EXCEPTION.getMsg(), wrapper.getRequest(),
                                wrapper.getAction());
                    }
                } catch (JsonSyntaxException e) {
                    e.printStackTrace();
                    wrapper.getTempCallback()
                        .onError(ErrorCode.PARSE_EXCEPTION.getMsg(), wrapper.getRequest(),
                            wrapper.getAction());
                }
    
            } else if (response.getRespEvent() == 20) {//通知
    
            }
        }
    }
    
    ....跟之前相同代码省略.....
    

    先解析出第一层bean然后根据respEvent判断响应类型,当respEvent为10的时候通过seqId找对应的callback如果找到了则取消超时任务,解析第二层bean判断code是否为成功,失败调用失败回调,成功用gson解析对应的bean然后调用成功回调.

    错误信息的封装ErrorCode如下

    public enum ErrorCode {
        BUSINESS_EXCEPTION("业务异常"),
        PARSE_EXCEPTION("数据格式异常"),
        DISCONNECT_EXCEPTION("连接断开");
    
        private String msg;
    
        ErrorCode(String msg) {
            this.msg = msg;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    

    授权和心跳

    发送请求封装好了接下来我们可以开始授权和心跳了,这里我们先回顾下用户登录流程.

    可以看到我们连接成功后进行授权,授权就是发送一个携带用户信息的请求然后服务端通过这个请求验证用户信息,验证成功后服务端就知道了当前长连接属于哪个用户,由于我们是先通过http请求进行的登录然后在通过websocket进行的授权,既然http登录能成功所以正常情况下通过websocket进行授权不会有问题,所以这里错误回调没有进行处理.

    这里我们模拟这个过程,首先是定义请求的action,在action中添加请求对应的action,reqEvent,respClazz这个比较简单我就省略了,然后在连接成功回调进行授权请求doAuth()

    public class WsManager {
    
        ....跟之前相同代码省略.....
    
        private void doAuth() {
            sendReq(Action.LOGIN, null, new ICallback() {
                @Override
                public void onSuccess(Object o) {
                    Logger.t(TAG).d("授权成功");
                    setStatus(WsStatus.AUTH_SUCCESS);
                }
    
    
                @Override
                public void onFail(String msg) {
    
                }
            });
        }
    
        /**
         * 继承默认的监听空实现WebSocketAdapter,重写我们需要的方法
         * onTextMessage 收到文字信息
         * onConnected 连接成功
         * onConnectError 连接失败
         * onDisconnected 连接关闭
         */
        class WsListener extends WebSocketAdapter {
    
            @Override
            public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
                throws Exception {
                super.onConnected(websocket, headers);
                Logger.t(TAG).d("连接成功");
                setStatus(WsStatus.CONNECT_SUCCESS);
                cancelReconnect();//连接成功的时候取消重连,初始化连接次数
                doAuth();
            }
    
        }
    
    ....跟之前相同代码省略.....
    }
    
    

    授权成功后接下来是心跳和同步数据,本质跟授权一样也是发送请求.

    这里单独说下心跳,心跳其实就是每隔一段时间我们请求服务端然后服务端有响应我们就认为这条连接是稳定的,对于websocket其实已经定义了心跳的格式ping和pong,为了方便这里我就直接使用我们自己定义的协议发送的心跳请求,本质上跟ping和pong是一样的只是协议不同罢了.对于心跳重连策略我这里做的比较简单按一个固定时间执行心跳请求,当心跳连续失败3次就进行重连.

    首先还是定义action.这里只是模拟所以respClazz都是null

    public enum Action {
        LOGIN("login", 1, null),
        HEARTBEAT("heartbeat", 1, null),
        SYNC("sync", 1, null);
    
        private String action;
        private int reqEvent;
        private Class respClazz;
    
    
        Action(String action, int reqEvent, Class respClazz) {
            this.action = action;
            this.reqEvent = reqEvent;
            this.respClazz = respClazz;
        }
    
    
        public String getAction() {
            return action;
        }
    
    
        public int getReqEvent() {
            return reqEvent;
        }
    
    
        public Class getRespClazz() {
            return respClazz;
        }
    
    }
    

    心跳和同步

    public class WsManager {
      ....跟之前相同代码省略.....
    
        private static final long HEARTBEAT_INTERVAL = 30000;//心跳间隔
        private Handler mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case SUCCESS_HANDLE:
                        CallbackDataWrapper successWrapper = (CallbackDataWrapper) msg.obj;
                        successWrapper.getCallback().onSuccess(successWrapper.getData());
                        break;
                    case ERROR_HANDLE:
                        CallbackDataWrapper errorWrapper = (CallbackDataWrapper) msg.obj;
                        errorWrapper.getCallback().onFail((String) errorWrapper.getData());
                        break;
                }
            }
        };
    
        private void doAuth() {
            sendReq(Action.LOGIN, null, new ICallback() {
                @Override
                public void onSuccess(Object o) {
                    Logger.t(TAG).d("授权成功");
                    setStatus(WsStatus.AUTH_SUCCESS);
                    startHeartbeat();
                    delaySyncData();
                }
    
    
                @Override
                public void onFail(String msg) {
    
                }
            });
        }
    
        //同步数据
        private void delaySyncData() {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    sendReq(Action.SYNC, null, new ICallback() {
                        @Override
                        public void onSuccess(Object o) {
    
                        }
    
    
                        @Override
                        public void onFail(String msg) {
    
                        }
                    });
                }
            }, 300);
        }
    
    
        private void startHeartbeat() {
            mHandler.postDelayed(heartbeatTask, HEARTBEAT_INTERVAL);
        }
    
    
        private void cancelHeartbeat() {
            heartbeatFailCount = 0;
            mHandler.removeCallbacks(heartbeatTask);
        }
    
    
        private int heartbeatFailCount = 0;
        private Runnable heartbeatTask = new Runnable() {
            @Override
            public void run() {
                sendReq(Action.HEARTBEAT, null, new ICallback() {
                    @Override
                    public void onSuccess(Object o) {
                        heartbeatFailCount = 0;
                    }
    
    
                    @Override
                    public void onFail(String msg) {
                        heartbeatFailCount++;
                        if (heartbeatFailCount >= 3) {
                            reconnect();
                        }
                    }
                });
    
                mHandler.postDelayed(this, HEARTBEAT_INTERVAL);
            }
        };
    
        private int reconnectCount = 0;//重连次数
        private long minInterval = 3000;//重连最小时间间隔
        private long maxInterval = 60000;//重连最大时间间隔
    
    
        public void reconnect() {
            if (!isNetConnect()) {
                reconnectCount = 0;
                Logger.t(TAG).d("重连失败网络不可用");
                return;
            }
    
            //这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
            //由于我们这里是个demo所以省略了
            if (ws != null &&
                !ws.isOpen() &&//当前连接断开了
                getStatus() != WsStatus.CONNECTING) {//不是正在重连状态
    
                reconnectCount++;
                setStatus(WsStatus.CONNECTING);
                cancelHeartbeat();//取消心跳
    
                long reconnectTime = minInterval;
                if (reconnectCount > 3) {
                    url = DEF_URL;
                    long temp = minInterval * (reconnectCount - 2);
                    reconnectTime = temp > maxInterval ? maxInterval : temp;
                }
    
                Logger.t(TAG).d("准备开始第%d次重连,重连间隔%d -- url:%s", reconnectCount, reconnectTime, url);
                mHandler.postDelayed(mReconnectTask, reconnectTime);
            }
        }
    
        ....跟之前相同代码省略.....
    }
    
    

    授权成功后开始心跳和同步数据,心跳连续失败三次开始重连,在重连时候会取消心跳.当重连成功的时候在连接成功回调会再次进行授权然后授权成功后会再次开启心跳就形成了一个循环.

    感觉篇幅又有些长了…为了避免大伙疲劳将服务端主动通知放第三篇WebSocket安卓客户端实现详解(三)–服务端主动通知好了,并且WebSocket整个模块我觉得我写的最好的就是服务端主动通知这部分还请各位看官一定要捧场啊.

    源码下载WebSocket安卓客户端实现详解(二)–客户端发送请求

    展开全文
  • PHP利用websocket实现客户端请求ws协议功能

    万次阅读 热门讨论 2018-12-07 16:15:05
    最近项目需要,利用websocket实现PHP对ws协议的请求,这里PHP是作为一个客户端通过ws协议请求服务,而不是作为服务端,这个首先要区分下。 ws协议是websocket的东西,现在也有很多东西可以实现,如workerman、swoole...

    最近项目需要,利用websocket实现PHP对ws协议的请求,这里PHP是作为一个客户端通过ws协议请求服务,而不是作为服务端,这个首先要区分下。

    ws协议是websocket的东西,现在也有很多东西可以实现,如workerman、swoole,但是说实话,看到workerman的文档,有介绍怎么实现这个功能,但是个人是感觉真的很头疼,一切都是基于cli,基本都是用到命令行去执行,一个类似curl请求获取结果的功能还要去捣鼓这些东西,真是呵呵哒。swoole就不说了,首先Windows都不支持(当然,现在基本都是用Linux,但是个人感觉不是很通用)。

    网上找了很多资料,说实话百度的东西都是扯淡的,越看越让你心烦,最后翻墙去Google终于找到方法了,实现的办法也很简单,几句代码就解决了,当然websocket类库还是要的(这个可能个人能力有限,百度没找到)。

    先说明,这里是基于thinkPHP5来实现的,不过基本流程都是差不多的

    把下载下来的websocket类库放到vendor,然后引用Client.php文件

    vendor("websocket.lib.Client");
    
    $data='{"id":"1".....}';请求数据
    
    $url="ws://xxx:9009"; //服务地址
    
    $client=new \WebSocket\Client($url); //实例化
    
    $client->send($data); //发送数据
    
    $result=$client->receive(); //接收数据
    
    $client->close();//关闭连接
    

    基本就是这几行代码,当然,当时用的时候,websocket引用会有一些小问题,具体的看问题解决即可

    这里提供下websocket类库的下载,说实话,这个还是用Google找到的(可能个人的搜索不到位)

    链接地址:https://github.com/Textalk/websocket-php

    展开全文
  • php 异步websocket客户端实现

    千次阅读 2017-06-15 21:35:29
    php 异步websocket客户端

    目前swoole websocket实现了server,并给出了同步client的实现示例,花了一个晚上实现了异步的websocket实现,源码仓库为:

    https://github.com/xhjcehust/AsyncWebSocketClient.git

    展开全文
  • springboot框架下整合websocket客户端和服务端
    • websocket简介

      WebSocket协议本质上是一个基于TCP的协议,它由通信协议和编程API组成,WebSocket能够在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器实时通信能力。既然是双向通信,就意味着服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应。
              在WebSocket出现之前,很多网站为了实现实时推送技术,通常采用的方案是轮询(Polling)和Comet技术,Comet又可细分为两种实现方式,一种是长轮询机制,一种称为流技术,这两种方式实际上是对轮询技术的改进,这些方案带来很明显的缺点,需要由浏览器对服务器发出HTTP request,大量消耗服务器带宽和资源。面对这种状况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并实现真正意义上的实时推送。

    • 应用场景

    Web领域的实时推送技术,也被称作Realtime技术。这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新。它有着广泛的应用场景,比如在线聊天室、在线客服系统、评论系统、WebIM等。


    • springboot框架下websocket客户端和服务端实现

    1. 依赖导入
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>
    2. WebSocketConfig
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.socket.server.standard.ServerEndpointExporter;
      
      @Configuration
      public class WebSocketConfig {
      
          /**
           * ServerEndpointExporter 作用
           * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
           *
           * @return
           */
          @Bean
          public ServerEndpointExporter serverEndpointExporter() {
              return new ServerEndpointExporter();
          }
      }
      说明:该配置类很简单,通过这个配置springboot才回去扫描websocket的注解。
    3. websocket服务端
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      
      import javax.websocket.OnClose;
      import javax.websocket.OnMessage;
      import javax.websocket.OnOpen;
      import javax.websocket.Session;
      import javax.websocket.server.PathParam;
      import javax.websocket.server.ServerEndpoint;
      import java.util.concurrent.ConcurrentHashMap;
      
      
      /**
       * @ServerEndpoint 这个注解有什么作用?
       * <p>
       * 这个注解用于标识作用在类上,它的主要功能是把当前类标识成一个WebSocket的服务端
       * 注解的值用户客户端连接访问的URL地址
       */
      
      @Slf4j
      @Component
      @ServerEndpoint("/websocket/{name}")
      public class SocketServer {
      
          /**
           * 用于存所有的连接服务的客户端,这个对象存储是安全的
           */
          private static ConcurrentHashMap<String, SocketServer> webSocketSet = new ConcurrentHashMap<>();
          /**
           * 与某个客户端的连接对话,需要通过它来给客户端发送消息
           */
          private Session session;
          /**
           * 标识当前连接客户端的用户名
           */
          private String name;
      
          @OnOpen
          public void OnOpen(Session session, @PathParam(value = "name") String name) {
              this.session = session;
              this.name = name;
              // name是用来表示唯一客户端,如果需要指定发送,需要指定发送通过name来区分
              webSocketSet.put(name, this);
              log.info("[WebSocket] 连接成功,当前连接人数为:={}", webSocketSet.size());
          }
      
      
          @OnClose
          public void OnClose() {
              webSocketSet.remove(this.name);
              log.info("[WebSocket] 退出成功,当前连接人数为:={}", webSocketSet.size());
          }
      
          @OnMessage
          public void OnMessage(String message) {
              log.info("[WebSocket] 收到消息:{}", message);
              //判断是否需要指定发送,具体规则自定义
              if (message.indexOf("TOUSER") == 0) {
                  String name = message.substring(message.indexOf("TOUSER") + 6, message.indexOf(";"));
                  AppointSending(name, message.substring(message.indexOf(";") + 1, message.length()));
              } else {
                  GroupSending(message);
              }
      
          }
      
          /**
           * 群发
           *
           * @param message
           */
          public void GroupSending(String message) {
              for (String name : webSocketSet.keySet()) {
                  try {
                      webSocketSet.get(name).session.getBasicRemote().sendText(message);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
          /**
           * 指定发送
           *
           * @param name
           * @param message
           */
          public void AppointSending(String name, String message) {
              try {
                  webSocketSet.get(name).session.getBasicRemote().sendText(message);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      说明:@ServerEndpoint("/websocket/{name}")注明该类是websocket的服务端,参数表明访问地址。
    4. websocket客户端
      import lombok.extern.slf4j.Slf4j;
      
      import javax.websocket.*;
      
      @Slf4j
      @ClientEndpoint
      public class SocketClient {
      
          @OnOpen
          public void onOpen() {
              log.info("webSocket on onOpen------");
          }
      
          @OnMessage
          public void onMessage(String message) {
      
              log.info("message:{}",message);
          }
      
          @OnClose
          public void onClose(Session session, CloseReason reason) {
              log.info("断线重连:{}",reason.toString());
      
          }
      
          @OnError
          public void onError(Throwable ex) {
              log.error("ex:{}", ex);
          }
      
      }
      说明:@ClientEndpoint标识是websocket客户端,实现@OnOpen,@OnMessage,@OnClose,@OnError四个方法。
      @OnOpen 当 websocket 建立连接成功后,会触发这个注解修饰的方法
      @OnMessage 当websocket接收到消息,会触发这个注解修改的方法
      @OnClose 当 websocket 建立的连接断开后,会触发这个注解修饰的方法。CloseReason对象可以获取到断开连接的原因和错误码
      @OnError 当 websocket 建立连接时出现异常会触发这个注解修饰的方法
    5. 客户端发送消息
          public static void main(String[] args) {
              URI path = URI.create("ws://localhost:8080/websocket/");
              WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
      
              Session session = null;
              SocketClient socketClient = new SocketClient();
              try {
                  session = webSocketContainer.connectToServer(socketClient, path);
              }catch (Exception e) {
                  log.error("sendMsg error:{}", e);
              }
              session.getAsyncRemote().sendText("msg");
          }

      参考以上代码通过 session.getAsyncRemote().sendText("msg");的方式发送消息。path参数填入自己websocket服务端的路径。

    参考:
    http://www.bubuko.com/infodetail-3254804.html

     

    展开全文
  • Websocket 客户端 +服务端 (用户——TCP连接) 服务端 pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</...
  • Node.js Websocket 区分不同的用户

    千次阅读 2017-10-26 11:55:16
    Websocket 区分不同的用户 实现ws://serverIP:port/:param1/:param2 。通过param1,param2来管理不同的ws回话,以便实现群发和指定用户的消息推送   1 2 npm install ws ...
  • 使用 WebSocket 客户端连接 MQTT 服务器

    千次阅读 2018-12-11 10:49:47
    近年来随着 Web 前端的快速发展,浏览器新特性层出不穷,越来越多的应用可以在浏览器端或通过浏览器渲染引擎实现,Web 应用的即时通信方式 WebSocket 得到了广泛的应用。 WebSocket 是一种在单个 TCP 连接上进行全...
  • Websocket 客户端 +服务端 (服务——TCP连接) 服务端 pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</...
  • 我们知道websocket协议与http协议有些不同,websocket有自带的Session,但是跟http的Session是不一样的,而我们做有些web项目的时候,又需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权...
  •  WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。  以前网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每隔1秒),由浏览器对...
  • 一个tornado websocket 客户端例子

    千次阅读 2016-04-01 14:19:31
    An example of tornado websocket client to show: 1, how to use tornado client 2, auto reconnect when lost the connection github 地址 https://github.com/gjwang/towsclient# -*- coding: utf-8 -*-''' ...
  • springboot+websocket实现服务端、客户端

    万次阅读 多人点赞 2019-01-12 12:54:43
    一、引言 小编最近一直在使用springboot框架...websocket主要功能就是实现网络通讯,比如说最经典的客服聊天窗口、您有新的消息通知,或者是项目与项目之间的通讯,都可以采用websocket来实现。 二、websocket介...
  • connectede和disconnected:表示两个状态相应的值 handshake - host - WebSocket服务器的...id:当多个客户端连接服务器时,用于区分每一个客户端 要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,330
精华内容 2,932
关键字:

websocket区分客户端