From 43d5f5a48c5b3a94b59a1ff1b91dcfa6e0a1b47c Mon Sep 17 00:00:00 2001 From: Redkale <22250530@qq.com> Date: Mon, 29 May 2017 17:21:34 +0800 Subject: [PATCH] --- net.html | 305 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 157 insertions(+), 148 deletions(-) diff --git a/net.html b/net.html index d7cc2a25c..450d9505d 100644 --- a/net.html +++ b/net.html @@ -105,7 +105,7 @@

        继承HttpServlet的子类可以使用其自带的鉴权、请求分支、缓存等功能, 一个典型的操作用户HttpServlet:

-
@WebServlet(value = {"/user/*"}, comment = "用户模块服务")  //拦截所有 /user/ 开头的请求
+            
@WebServlet(value = {"/user/*"}, comment = "用户模块服务")  //拦截所有 /user/ 开头的请求
 public class UserServlet extends BaseSerlvet {
 
     @Resource
@@ -114,50 +114,51 @@
     //登录操作 
     //因为HttpMapping的判断规则用的是String.startsWith,所以HttpMapping.url不能用正则表达式,只能是getRequestURI的前缀
     //且同一个HttpServlet类内的所有HttpMapping不能存在包含关系, 如 /user/myinfo 和 /user/myinforecord 不能存在同一HttpServlet中。
-    @HttpMapping(url = "/user/login", comment = "用户登录")
-    @HttpParam(name = "bean", type = LoginBean.class, comment = "登录参数对象")
+    @HttpMapping(url = "/user/login", auth = false, comment = "用户登录")
+    @HttpParam(name = "bean", type = LoginBean.class, comment = "登录参数对象")
     public void login(HttpRequest req, HttpResponse resp) throws IOException {
-        LoginBean bean = req.getJsonParameter(LoginBean.class, "bean"); //获取参数
+        LoginBean bean = req.getJsonParameter(LoginBean.class, "bean"); //获取参数
         RetResult<UserInfo> result = service.login(bean); //登录操作, service内部判断bean的合法性
         resp.finishJson(result); //输出结果
     }
 
     //获取当前用户信息
     //未登录的请求会被BaseSerlvet.authenticate方法拦截,因此能进入该方法说明用户态存在
-    @HttpMapping(url = "/user/myinfo", comment = "获取当前用户信息")
+    @HttpMapping(url = "/user/myinfo", comment = "获取当前用户信息")
     public void myinfo(HttpRequest req, HttpResponse resp) throws IOException {
         UserInfo user = service.current(req.getSessionid(false));
-        //或者使用 user = req.getAttribute("_current_userinfo"); 因为BaseSerlvet.authenticate方法已经将UserInfo注入到_current_userinfo属性中
+        //或者使用 user = req.getAttribute("_current_userinfo"); 因为BaseSerlvet.authenticate方法已经将UserInfo注入到_current_userinfo属性中
         resp.finishJson(user);  //输出用户信息
     }
 
     //获取指定用户ID的用户信息, 请求如: /user/username/43565443
     // 翻页查询想缓存就需要将翻页信息带进url: /user/query/page:2/size:50 。
-    @HttpMapping(url = "/user/userinfo/", comment = "获取指定用户ID的用户信息")
-    @HttpParam(name = "#", type = int.class, comment = "用户ID")
+    @HttpMapping(url = "/user/userinfo/", comment = "获取指定用户ID的用户信息")
+    @HttpParam(name = "#", type = int.class, comment = "用户ID")
     public void userinfo(HttpRequest req, HttpResponse resp) throws IOException {
         UserInfo user = service.findUserInfo(Integer.parseInt(req.getRequstURILastPath()));
         resp.finishJson(user);  //输出用户信息
     }
 
     //更新个人头像
-    @HttpMapping(url = "/user/updateface", comment = "更新用户头像")
+    @HttpMapping(url = "/user/updateface", comment = "更新用户头像")
     public void updateface(HttpRequest req, HttpResponse resp) throws IOException {
         UserInfo user = service.current(req.getSessionid(false));
         for (MultiPart part : req.multiParts()) { //遍历上传文件列表 
             byte[] byts = part.getContentBytes(5 * 1024 * 1024L);  //图片大小不能超过5M,超过5M返回null
             if (byts == null) {
-                resp.finishJson(new RetResult(102, "上传的文件过大"));
+                resp.finishJson(new RetResult(102, "上传的文件过大"));
             } else {
-                BufferedImage img = ImageIO.read(new ByteArrayInputStream(byts));
+                BufferedImage img = ImageIO.read(new ByteArrayInputStream(byts));
                 //... 此处处理并存储头像图片
                 resp.finishJson(RetResult.SUCCESS);  //输出成功信息
             }
             return; //头像图片仅会传一个, 只需要读取一个即可返回
         }
-        resp.finishJson(new RetResult(103, "没有上传图片"));
+        resp.finishJson(new RetResult(103, "没有上传图片"));
     }
-}
+} +

        如上,所有/user/前缀的请求都会进入UserServlet,若没匹配的则返回505错误,为了方便以后编写前方静动分离服务器转发规则,比较好的习惯是将项目中所有动态Servlet加一个固定前缀,在 application.xml 里设置path即可。

<server protocol="HTTP" port="6060" root="root"> 
     <services autoload="true" />  
@@ -176,7 +177,7 @@
                         一个WebSocket连接对应一个WebSocket实体,即一个WebSocket会绑定一个TCP连接。且有两种模式:
         1) 普通模式: 协议上符合HTML5规范, 其流程顺序如下:
                 1.1 onOpen               若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
-                  1.2 createGroupid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
+                  1.2 createUserid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
                 1.3 onConnected      WebSocket成功连接后在准备接收数据前回调此方法。
                 1.4 onMessage/onFragment+    WebSocket接收到消息后回调此消息类方法。
                 1.5 onClose              WebSocket被关闭后回调此方法。
@@ -184,56 +185,51 @@
         2) 原始二进制模式: 此模式有别于HTML5规范,可以视为原始的TCP连接。通常用于音频视频通讯场景。其流程顺序如下:
                 2.1 onOpen               如果方法返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
-                  2.2 createGroupid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
+                  2.2 createUserid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
                 2.3 onRead               WebSocket成功连接后回调此方法, 由此方法处理原始的TCP连接, 同时业务代码去控制WebSocket的关闭。
        此模式下 以上方法都应该被重载。

        实现一个WebSocket服务需要继承 org.redkale.net.http.WebSocketServlet,以下是一个简单的聊天范例:

-
@WebServlet("/ws/chat")
+            
@WebServlet("/ws/chat")
 public class ChatWebSocketServlet extends WebSocketServlet {
 
-    @Resource //[Redkale内置资源]
-    protected JsonConvert jsonConvert;
-
     @Resource
     private UserService userService;
 
     @Override
     public void init(HttpContext context, AnyValue conf) {
-        System.out.println("本实例的WebSocketEngine: " + super.engine);
-        System.out.println("本实例的WebSocketNode: " + super.node);
+        System.out.println("本实例的WebSocketNode: " + super.node);
     }
 
     @Override
     public void destroy(HttpContext context, AnyValue conf) {
-        System.out.println("关闭了ChatWebSocketServlet");
+        System.out.println("关闭了ChatWebSocketServlet");
     }
 
     @Override
-    protected WebSocket createWebSocket() {
+    protected WebSocket<Integer, ChatMessage> createWebSocket() {
 
-        return new WebSocket() {
+        return new WebSocket<Integer, ChatMessage>() {
 
             private UserInfo user;
 
             @Override
-            public void onMessage(String text) { // text 接收的格式:  {"receiveid":200000001, "content":"Hi Redkale!"}
-                final ChatMessage message = jsonConvert.convertFrom(ChatMessage.class, text); //获取给对方的消息体信息
+            public void onMessage(ChatMessage message, boolean last) { // text 接收的格式:  {"receiveid":200000001, "content":"Hi Redkale!"}
                 message.sendid = user.getUserid(); //将当前用户设为消息的发送方
                 message.sendtime = System.currentTimeMillis(); //设置消息发送时间
                 //给接收方发送消息, 即使接收方在其他WebSocket进程节点上有链接,Redkale也会自动发送到其他链接进程节点上。
-                super.sendEachMessage(message.receiveid, jsonConvert.convertTo(message));
+                super.sendMessage(message, message.receiveid);
             }
 
             @Override
-            protected Serializable createGroupid() { //以用户ID作为WebSocketGroup的groupid
+            protected CompletableFuture<Integer> createUserid() { //创建用户ID
                 this.user = userService.current(String.valueOf(super.getSessionid()));
-                return this.user == null ? null : this.user.getUserid();
+                return CompletableFuture.completedFuture(this.user == null ? null : this.user.getUserid());
             }
 
             @Override
-            public Serializable onOpen(HttpRequest request) {
-                return request.getSessionid(false); //以request中的sessionid字符串作为WebSocket的sessionid
+            public CompletableFuture<String> onOpen(HttpRequest request) {
+                return CompletableFuture.completedFuture(request.getSessionid(false)); //以request中的sessionid字符串作为WebSocket的sessionid
             }
 
         };
@@ -249,7 +245,8 @@
 
         public long sendtime;  //消息发送时间
     }
-}
+} +

  . HttpRequest 对象

public class HttpRequest {
@@ -268,14 +265,29 @@
     public String getBodyUTF8();
 
     //获取请求内容的byte[]
-    public byte[] getBody();
+    public byte[] getBody();
+    
+    //获取请求内容的JavaBean对象
+    public <T> T getBodyJson(java.lang.reflect.Type type);
 
+    //获取请求内容的JavaBean对象
+    public <T> T getBodyJson(JsonConvert convert, java.lang.reflect.Type type);
+    
     //获取文件上传对象
     public MultiContext getMultiContext();
 
     //获取文件上传信息列表 等价于 getMultiContext().parts();
     public Iterable<MultiPart> multiParts() throws IOException;
 
+    //获取当前用户信息 数据类型由@HttpUserType指定
+    public <T> T currentUser();
+    
+    //获取模块ID,来自@HttpServlet.moduleid()
+    public int getModuleid();
+    
+    //获取操作ID,来自@HttpMapping.actionid()
+    public int getActionid();
+    
     //获取sessionid
     public String getSessionid(boolean autoCreate);
 
@@ -361,54 +373,54 @@
     // 获取type参数: double type = request.getRequstURILastPath(0.0); //type = 2.0
     public double getRequstURILastPath(double defvalue);
 
-    //从prefix之后截取getRequestURI再对"/"进行分隔
+    //从prefix之后截取getRequestURI再对"/"进行分隔
     public String[] getRequstURIPaths(String prefix);
 
     // 获取请求URL分段中含prefix段的值
     // 例如请求URL /pipes/record/query/name:hello
-    // 获取name参数: String name = request.getRequstURIPath("name:", "none");
+    // 获取name参数: String name = request.getRequstURIPath("name:", "none");
     public String getRequstURIPath(String prefix, String defaultValue);
 
     // 获取请求URL分段中含prefix段的short值
     // 例如请求URL /pipes/record/query/type:10
-    // 获取type参数: short type = request.getRequstURIPath("type:", (short)0);
+    // 获取type参数: short type = request.getRequstURIPath("type:", (short)0);
     public short getRequstURIPath(String prefix, short defaultValue);
 
     // 获取请求URL分段中含prefix段的short值
     // 例如请求URL /pipes/record/query/type:a
-    // 获取type参数: short type = request.getRequstURIPath(16, "type:", (short)0); type = 10
+    // 获取type参数: short type = request.getRequstURIPath(16, "type:", (short)0); type = 10
     public short getRequstURIPath(int radix, String prefix, short defvalue);
 
     // 获取请求URL分段中含prefix段的int值
     // 例如请求URL /pipes/record/query/offset:2/limit:50
-    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
-    // 获取limit参数: int limit = request.getRequstURIPath("limit:", 20);
+    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
+    // 获取limit参数: int limit = request.getRequstURIPath("limit:", 20);
     public int getRequstURIPath(String prefix, int defaultValue);
 
     // 获取请求URL分段中含prefix段的int值
     // 例如请求URL /pipes/record/query/offset:2/limit:10
-    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
-    // 获取limit参数: int limit = request.getRequstURIPath(16, "limit:", 20); // limit = 16
+    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
+    // 获取limit参数: int limit = request.getRequstURIPath(16, "limit:", 20); // limit = 16
     public int getRequstURIPath(int radix, String prefix, int defaultValue);
 
     // 获取请求URL分段中含prefix段的float值   
     // 例如请求URL /pipes/record/query/point:40.0  
-    // 获取time参数: float point = request.getRequstURIPath("point:", 0.0f);
+    // 获取time参数: float point = request.getRequstURIPath("point:", 0.0f);
     public float getRequstURIPath(String prefix, float defvalue);
 
     // 获取请求URL分段中含prefix段的long值
     // 例如请求URL /pipes/record/query/time:1453104341363/id:40
-    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
+    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
     public long getRequstURIPath(String prefix, long defaultValue);
 
     // 获取请求URL分段中含prefix段的long值
     // 例如请求URL /pipes/record/query/time:1453104341363/id:40
-    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
+    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
     public long getRequstURIPath(int radix, String prefix, long defvalue);
 
     // 获取请求URL分段中含prefix段的double值   <br>
     // 例如请求URL /pipes/record/query/point:40.0   <br>
-    // 获取time参数: double point = request.getRequstURIPath("point:", 0.0);
+    // 获取time参数: double point = request.getRequstURIPath("point:", 0.0);
     public double getRequstURIPath(String prefix, double defvalue);
 
     //获取所有的header名
@@ -504,20 +516,20 @@
     //获取指定的参数double值, 没有返回默认double值
     public double getDoubleParameter(String name, double defaultValue);
 
-    //获取翻页对象 同 getFlipper("flipper", false, 0);
+    //获取翻页对象 同 getFlipper("flipper", false, 0);
     public org.redkale.source.Flipper getFlipper();
 
-    //获取翻页对象 同 getFlipper("flipper", needcreate, 0);
+    //获取翻页对象 同 getFlipper("flipper", needcreate, 0);
     public org.redkale.source.Flipper getFlipper(boolean needcreate);
 
-    //获取翻页对象 同 getFlipper("flipper", false, maxLimit);
+    //获取翻页对象 同 getFlipper("flipper", false, maxLimit);
     public org.redkale.source.Flipper getFlipper(int maxLimit);
 
-    //获取翻页对象 同 getFlipper("flipper", needcreate, maxLimit)
+    //获取翻页对象 同 getFlipper("flipper", needcreate, maxLimit)
     public org.redkale.source.Flipper getFlipper(boolean needcreate, int maxLimit);
 
     //获取翻页对象 http://redkale.org/pipes/records/list/offset:0/limit:20/sort:createtime%20ASC
-    //http://redkale.org/pipes/records/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
+    //http://redkale.org/pipes/records/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
     //以上两种接口都可以获取到翻页对象
     public org.redkale.source.Flipper getFlipper(String name, boolean needcreate, int maxLimit);
 
@@ -527,13 +539,13 @@
     //获取所有属性值, servlet执行完后会被清空
     public Map<String, Object> getAttributes();
 
-    //获取指定属性值
+    //获取指定属性值, servlet执行完后会被清空
     public <T> T getAttribute(String name);
 
     //删除指定属性
     public void removeAttribute(String name);
 
-    //设置属性值
+    //设置属性值, servlet执行完后会被清空
     public void setAttribute(String name, Object value);
 
     //获取request创建时间
@@ -551,6 +563,12 @@
     //增加Cookie值
     public HttpResponse addCookie(Collection<HttpCookie> cookies);
 
+    //创建AsyncHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
+    public AsyncHandler createAsyncHandler();
+
+    //传入的AsyncHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数
+    public <H extends AsyncHandler> H createAsyncHandler(Class<H> handlerClass);
+    
     //设置状态码
     public void setStatus(int status);
 
@@ -585,9 +603,9 @@
     //异步输出指定内容
     public <A> void sendBody(ByteBuffer buffer, A attachment, AsyncHandler<Integer, A> handler);
 
-    //创建AsyncHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
-    public AsyncHandler createAsyncHandler();
-
+    //异步输出指定内容
+    public <A> void sendBody(ByteBuffer[] buffers, A attachment, AsyncHandler<Integer, A> handler);
+    
     //关闭HTTP连接,如果是keep-alive则不强制关闭
     public void finish();
 
@@ -616,14 +634,20 @@
     public void finishJson(final JsonConvert convert, final org.redkale.service.RetResult ret);
 
     //将CompletableFuture的结果对象以JSON格式输出
-    public void finishJson(CompletableFuture future);
+    public void finishJson(final CompletableFuture future);
 
     //将CompletableFuture的结果对象以JSON格式输出
-    public void finishJson(JsonConvert convert, CompletableFuture future);
+    public void finishJson(final JsonConvert convert, final CompletableFuture future);
 
     //将CompletableFuture的结果对象以JSON格式输出
     public void finishJson(final JsonConvert convert, final Type type, final CompletableFuture future);
 
+    //将HttpResult的结果对象以JSON格式输出
+    public void finishJson(final HttpResult result);
+    
+    //将HttpResult的结果对象以JSON格式输出
+    public void finishJson(final JsonConvert convert, final HttpResult result) ;
+    
     //将指定字符串以响应结果输出
     public void finish(String obj);
 
@@ -660,76 +684,106 @@
     public void finish(final String filename, File file) throws IOException;
 
     //HttpResponse回收时回调的监听方法
-    public void setRecycleListener(BiConsumer<HttpRequest, HttpResponse> recycleListener);
+    public void recycleListener(BiConsumer<HttpRequest, HttpResponse> recycleListener);
+
 }
                 

  . WebSocket 对象

-
public abstract class WebSocket {
+            
public abstract class WebSocket<G, T> {
 
-    //发送消息体, 包含二进制/文本  返回结果0表示成功,非0表示错误码
-    public int send(WebSocketPacket packet);
+    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> send(Object message);
 
-    //发送单一的文本消息  返回结果0表示成功,非0表示错误码
-    public int send(String text);
+    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> send(Object message, boolean last);
 
-    //发送文本消息  返回结果0表示成功,非0表示错误码
-    public int send(String text, boolean last);
+    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> send(JsonConvert convert, Object message);
 
-    //发送单一的二进制消息  返回结果0表示成功,非0表示错误码
-    public int send(byte[] data);
+    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> send(JsonConvert convert, Object message, boolean last);
 
-    //发送单一的二进制消息  返回结果0表示成功,非0表示错误码
-    public int send(byte[] data, boolean last);
+    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> sendMessage(Object message, G... userids);
 
-    //发送消息, 消息类型是String或byte[]  返回结果0表示成功,非0表示错误码
-    public int send(Serializable message, boolean last);
+    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids);
 
-    //给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息
-    public int sendEachMessage(Serializable groupid, String text);
+    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> broadcastMessage(final Object message);
 
-    //给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息
-    public int sendEachMessage(Serializable groupid, String text, boolean last);
+    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
+    public CompletableFuture<Integer> broadcastMessage(final Object message, boolean last);
 
-    //给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息
-    public int sendEachMessage(Serializable groupid, byte[] data);
+    //获取用户在线的SNCP节点地址列表,不是分布式则返回元素数量为1,且元素值为null的列表
+    public CompletableFuture<Collection<InetSocketAddress>> getRpcNodeAddresses(final Serializable userid);
 
-    //给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息
-    public int sendEachMessage(Serializable groupid, byte[] data, boolean last);
-
-    //给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送文本消息
-    public int sendRecentMessage(Serializable groupid, String text);
-
-    //给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送文本消息
-    public int sendRecentMessage(Serializable groupid, String text, boolean last);
-
-    //给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送二进制消息
-    public int sendRecentMessage(Serializable groupid, byte[] data);
-
-    //给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送二进制消息
-    public int sendRecentMessage(Serializable groupid, byte[] data, boolean last);
+    //获取在线用户的详细连接信息
+    public CompletableFuture<Map<InetSocketAddress, List<String>>> getRpcNodeWebSocketAddresses(final Serializable userid);
 
     //发送PING消息  返回结果0表示成功,非0表示错误码
-    public int sendPing();
+    public CompletableFuture<Integer> sendPing();
 
     //发送PING消息,附带其他信息  返回结果0表示成功,非0表示错误码
-    public int sendPing(byte[] data);
+    public CompletableFuture<Integer> sendPing(byte[] data);
 
     //发送PONG消息,附带其他信息  返回结果0表示成功,非0表示错误码
-    public int sendPong(byte[] data);
+    public CompletableFuture<Integer> sendPong(byte[] data);
+
+
+    //获取指定userid的WebSocket数组, 没有返回null  此方法用于单用户多连接模式
+    protected Stream<WebSocket> getLocalWebSockets(G userid);
+
+
+    //获取指定userid的WebSocket数组, 没有返回null 此方法用于单用户单连接模式
+    protected WebSocket findLocalWebSocket(G userid);
+
+
+    //获取当前进程节点所有在线的WebSocket
+    protected Collection<WebSocket> getLocalWebSockets();
+
+
+    //返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
+    protected CompletableFuture<String> onOpen(final HttpRequest request);
+
+
+    //创建userid, null表示异常, 必须实现该方法
+    protected abstract G createUserid();
+
+    //标记为@WebSocketBinary才需要重写此方法
+    public void onRead(AsyncConnection channel);
+
+    //WebSokcet连接成功后的回调方法
+    public void onConnected();
+
+    //ping后的回调方法
+    public void onPing(byte[] bytes);
+
+    //pong后的回调方法
+    public void onPong(byte[] bytes);
+
+    //接收到消息的回调方法
+    public void onMessage(T message, boolean last);
+
+    //接收到二进制消息的回调方法
+    public void onMessage(byte[] bytes, boolean last);
+
+    //关闭的回调方法,调用此方法时WebSocket已经被关闭
+    public void onClose(int code, String reason);
 
     //获取当前WebSocket下的属性
-    public <T> T getAttribute(String name);
+    public T getAttribute(String name);
 
     //移出当前WebSocket下的属性
-    public <T> T removeAttribute(String name);
+    public T removeAttribute(String name);
 
     //给当前WebSocket下的增加属性
     public void setAttribute(String name, Object value);
 
-    //获取当前WebSocket所属的groupid
-    public Serializable getGroupid();
+    //获取当前WebSocket所属的userid
+    public G userid();
 
     //获取当前WebSocket的会话ID, 不会为null
     public Serializable getSessionid();
@@ -743,58 +797,13 @@
     //获取WebSocket创建时间
     public long getCreatetime();
 
+    //获取最后一次发送消息的时间
+    public long getLastSendTime();
+
     //显式地关闭WebSocket
     public void close();
-
-    //获取当前WebSocket所属的WebSocketGroup, 不会为null
-    protected WebSocketGroup getWebSocketGroup();
-
-    //获取指定groupid的WebSocketGroup, 没有返回null
-    protected WebSocketGroup getWebSocketGroup(Serializable groupid);
-
-    //获取当前进程节点所有在线的WebSocketGroup
-    protected Collection<WebSocketGroup> getWebSocketGroups();
-
-    //获取在线用户的节点地址列表
-    protected Collection<InetSocketAddress> getOnlineNodes(Serializable groupid);
-
-    //获取在线用户的详细连接信息
-    protected Map<InetSocketAddress, List<String>> getOnlineRemoteAddress(Serializable groupid);
-
-    //返回sessionid, null表示连接不合法或异常,默认实现是request.getSessionid(false),通常需要重写该方法
-    public Serializable onOpen(final HttpRequest request);
-
-    //创建groupid, null表示异常, 必须实现该方法, 通常为用户ID为groupid
-    protected abstract Serializable createGroupid();
-
-    //标记为@WebSocketBinary才需要重写此方法
-    public void onRead(AsyncConnection channel) {
-    }
-
-    public void onConnected() {
-    }
-
-    public void onMessage(String text) {
-    }
-
-    public void onPing(byte[] bytes) {
-    }
-
-    public void onPong(byte[] bytes) {
-    }
-
-    public void onMessage(byte[] bytes) {
-    }
-
-    public void onFragment(String text, boolean last) {
-    }
-
-    public void onFragment(byte[] bytes, boolean last) {
-    }
-
-    public void onClose(int code, String reason) {
-    }
-}
+} +

SNCP 协议