精华内容
下载资源
问答
  • 循序渐进的介绍Android中MessageQueue的实现原理。并且介绍MessageQueue中所涉及到的重要知识点。
  • pushMessage推聊

    热门讨论 2013-11-08 16:41:02
    pushMessage推聊
  • 但最近在正式环境中,有一次与同事联调的过程中,Tomcat 服务器出现异常,百思不得其解,之前一直正常的 Tomcat 出现异常,服务器窗口不间断输出org.apache.coyote.ajp.AjpMessage.processHeader Invalid message ...

    工作中项目一直放在测试环境中测试,但最近在正式环境中,有一次与同事联调的过程中,Tomcat 服务器出现异常,百思不得其解,之前一直正常的 Tomcat 出现异常,服务器窗口不间断输出 org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635,但我并没有对服务器配置进行修改过,what?

    如下为 Tomcat 服务器的报错信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    23-Jul-2019 14:28:43.711 信息 [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
    23-Jul-2019 14:28:50.039 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory D:tomcatwebappsQYWeixinyh has finished in 9,907 ms
    23-Jul-2019 14:28:50.086 信息 [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-apr-17000"]
    23-Jul-2019 14:28:50.101 信息 [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-apr-8096"]
    23-Jul-2019 14:28:50.101 信息 [main] org.apache.catalina.startup.Catalina.start Server startup in 10852 ms
    23-Jul-2019 14:28:50.148 严重 [ajp-apr-8096-exec-7] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-6] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-5] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-3] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-4] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-7] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-2] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-10] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-1] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-9] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-8] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    23-Jul-2019 14:28:50.164 严重 [ajp-apr-8096-exec-2] org.apache.coyote.ajp.AjpMessage.processHeader Invalid message received with signature 5635
    

    自 Server startup in 10852 ms 启动之后,持续不间断的输出严重的报错信息,通过浏览器访问 Tomcat/conf/Server.xml 配置的端口也无法访问,通过报错信息追踪,问题产生于这一配置

    1
    
    <Connector port="8096" protocol="AJP/1.3" redirectPort="8443"/>
    

    而在 Stack Overflow 中,对于 AjpMessage.processHeader 问题的记录并不多, 对于配置的修改则为将 AJP/1.3 替换为 HTTP/1.1,但这个修改对 5635 报错并没有起作用,反而会引起其他的报错信息。

    对此,修改了如下配置便解决了一直输出端口严重错误的问题:

    1
    
    <Connector port="8096" protocol="AJP/1.3" address="127.0.0.1" redirectPort="8443"/>
    

    添加 address 属性,将地址指向本地服务器,关于 address 属性,官方文档 也有具体说明,在默认的情况下,此端口将用于与服务器关联的所有IP地址,指定本地则免去暴露的危险,这个情况早在 Tomcat7 时就有出现,而此处使用 Tomcat8 也在启动时进行检测,避免该情况发生。


    鉴于以上配置和今天遇到的一下log打印的错误,错误号各有不同

    运行tomcat最后在窗口打印出   Invalid message received with signature 18245这个错误,只是写了一个简单的demo,最后百度了一通,说是TomcatAjp端口8009,外网访问的原因,如果没有指定IP地址的话,默认是绑定任意地址,这样就导致外网也可以访问这个端口。因此出于安全考虑,我们需要增加这个address的设置,并且绑定到127.0.0.1,结果如下:<Connector port="8009" protocol="AJP/1.3" address="127.0.0.1" redirectPort="8443" />,修改的目录是tomcat下的conf/server.xml文件,里面找一下port=‘8009’的那一行,添加address="127.0.0.1"。

    八月 19, 2020 3:56:01 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 356
    八月 19, 2020 3:56:02 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 12420
    八月 19, 2020 3:56:03 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 12300
    八月 19, 2020 3:56:03 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 20304
    八月 19, 2020 3:56:05 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 21582
    八月 19, 2020 3:56:05 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 768
    八月 19, 2020 3:56:06 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 17517
    八月 19, 2020 3:56:07 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 14848
    八月 19, 2020 3:56:08 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 17481
    八月 19, 2020 3:56:08 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 19026
    八月 19, 2020 3:56:09 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 256
    八月 19, 2020 3:56:10 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 15392
    八月 19, 2020 3:56:11 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 256
    八月 19, 2020 3:56:11 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 90
    八月 19, 2020 3:56:12 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 21365
    八月 19, 2020 3:56:12 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 20853
    八月 19, 2020 3:56:13 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 1284
    八月 19, 2020 3:56:14 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 1025
    八月 19, 2020 3:56:15 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 4609
    八月 19, 2020 3:56:16 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 18501
    八月 19, 2020 3:56:16 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 29556
    八月 19, 2020 3:56:16 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 0
    八月 19, 2020 3:56:17 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 0
    八月 19, 2020 3:56:17 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 450
    八月 19, 2020 3:56:17 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 17260
    八月 19, 2020 3:56:18 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 15360
    八月 19, 2020 4:02:02 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 5635
    八月 19, 2020 5:53:00 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 5635
    八月 19, 2020 5:53:00 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 18245
    八月 19, 2020 5:53:01 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 5635
    八月 19, 2020 5:53:01 下午 org.apache.coyote.ajp.AjpMessage processHeader
    严重: Invalid message received with signature 18245

    展开全文
  • springboot中websocket配置见 ...携带的用户ID可以直接拿到给MessageMapping注解的函数注入,后端可以使用这个ID双向通信 需要定义一个实体实现Principal,实现getName()方法 @Getter @Setter publ...

    1、发送数据携带用户ID
    2、发送JSON数据体
    3、将参数携带到发送请求的URL路径中
    4、发送header
    5、发送Httpsession中的数据

    springboot中websocket配置见
    https://blog.csdn.net/u011943534/article/details/81007002

    1、发送数据携带用户ID
    携带的用户ID可以直接拿到给MessageMapping注解的函数注入,后端可以使用这个ID双向通信
    需要定义一个实体实现Principal,实现getName()方法

    @Getter
    @Setter
    public class User implements Principal {
    
        private String username;
    
        private String password;
    
        private String role;
    
        private List<Url> urls;
    
        @Override
        public String getName() {
            return username;
        }
    }
    

    定义用户拦截器做认证,并生成User,注入StompHeaderAccessor

    /**
     *用户拦截器
     **/
    public class UserInterceptor implements ChannelInterceptor {
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
    
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
                if (raw instanceof Map) {
                    //这里就是token
                    Object name = ((Map) raw).get(Constants.TOKEN_KEY);
                    if (name instanceof LinkedList) {
                        // 设置当前访问器的认证用户
                        String token = ((LinkedList) name).get(0).toString();
                        String username = null;
                        try {
                            Map<String, Claim> claimMap = JWTUtils.verifyToken(token);
                            username = claimMap.get("username").asString();
                            if(username == null){
                                throw new RuntimeException("websocket认证失败");
                            }
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                            throw new RuntimeException("websocket认证失败", e);
                        } catch (ValidTokenException e) {
                            e.printStackTrace();
                            throw new RuntimeException("websocket认证失败", e);
                        }
                        User user = new User();
                        user.setUsername(username);
                        accessor.setUser(user);
    
    //                    User user = new User();
    //                    user.setUsername("lalala");
    //                    accessor.setUser(user);
    
                    }
                }
            }
    
            return message;
        }
    
        @Override
        public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
    
        }
    
        @Override
        public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
    
        }
    
        @Override
        public boolean preReceive(MessageChannel channel) {
            return false;
        }
    
        @Override
        public Message<?> postReceive(Message<?> message, MessageChannel channel) {
            return null;
        }
    
        @Override
        public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
    
        }
    }
    
    /*将客户端渠道拦截器加入spring ioc容器*/
        @Bean
        public UserInterceptor createUserInterceptor() {
            return new UserInterceptor();
        }
    

    服务端

    /**
         * 接收用户信息
         * */
        @MessageMapping(value = "/principal")
        public void test(Principal principal) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    

    客户端:

     /**
             * 发送用户信息
             * */
            function send0() {
                stompClient.send("/app/principal", {},
                    {});
            }
    

    2、发送JSON数据体
    服务端可以直接在函数中注入JavaBean或者Map,List或者String接收

    服务端:

    /*点对点通信*/
        @MessageMapping(value = "/P2P")
        public void templateTest(Principal principal, Map<String,String> data) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    

    客户端:

            /**
             * 发送JSON数据体
             * */
            function send() {
                stompClient.send("/app/P2P", {},
                    JSON.stringify({ 'name': 'test' }));
            }
    

    3、将参数携带到发送请求的URL路径中
    使用@DestinationVariable注解,类似SpringMVC的@PathVirable

    服务端:

    /**
         * 接收路径参数
         * */
        @MessageMapping(value = "/path/{name}/{company}")
        public void pathTest(Principal principal, @DestinationVariable String name, @DestinationVariable String company) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    

    客户端:

            /**
             * 发送路径参数
             * */
            function send2() {
                stompClient.send("/app/path/zhangsan/XXX公司", {},
                    {});
            }
    

    4、发送header
    使用@Header注解

    服务端:

    /**
         * 接收header参数
         * */
        @MessageMapping(value = "/header")
        public void headerTest(Principal principal, @Header String one, @Header String two) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    

    客户端:

            /**
             * 发送header参数
             * */
            function send3() {
                stompClient.send("/app/header", {"one":"lalala", "two":"中国"},
                    {});
            }
    

    5、发送Httpsession中的数据
    这里有一点儿小问题,我理解的是只能发送握手连接时的HttpSession中的数据

    注册HttpSessionHandshakeIntercepror

        /**
         * 注册stomp的端点
         */
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            // 允许使用socketJs方式访问,访问点为webSocketServer,允许跨域
            // 在网页上我们就可以通过这个链接
            // http://localhost:8080/webSocketServer
            // 来和服务器的WebSocket连接
            registry.addEndpoint("/webSocketServer")
                    .addInterceptors(new HttpSessionHandshakeInterceptor())
                    .setAllowedOrigins("*")
                    .withSockJS();
    
        }
    

    服务端:

        /**
         * 接收HttpSession数据
         * */
        @MessageMapping(value = "/httpsession")
        public void httpsession( StompHeaderAccessor accessor) {
            String name = (String) accessor.getSessionAttributes().get("name");
            System.out.println(1111);
        }
    

    客户端:

            /**
             * 发送httpsession
             * */
            function send4() {
                stompClient.send("/app/httpsession", {},
                    {});
            }
    

    所有代码

    前端JS:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
        <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
        <script>
            var socket = new SockJS("http://192.168.100.88:7601/demo/webSocketServer");
            var stompClient = Stomp.over(socket);
            window.onload = function () {
                connect();
            }
            //订阅消息
            function subscribe() {
                stompClient.subscribe('/user/queue/message', function (response) {
                    console.log("/user/queue/message 你接收到的消息为:" + response);
                });
    
            }
    
            /**
             * 发送用户信息
             * */
            function send0() {
                stompClient.send("/app/principal", {},
                    {});
            }
    
            /**
             * 发送JSON数据体
             * */
            function send() {
                stompClient.send("/app/P2P", {},
                    JSON.stringify({ 'name': 'test' }));
            }
    
            /**
             * 发送路径参数
             * */
            function send2() {
                stompClient.send("/app/path/zhangsan/XXX公司", {},
                    {});
            }
    
            /**
             * 发送header参数
             * */
            function send3() {
                stompClient.send("/app/header", {"one":"lalala", "two":"中国"},
                    {});
            }
    
            /**
             * 发送httpsession
             * */
            function send4() {
                stompClient.send("/app/httpsession", {},
                    {});
            }
    
            // /**
            //  * 发送URL中?&参数
            //  * */
            // function send5() {
            //     stompClient.send("/app/param?name=张三", {},
            //         {});
            // }
    
            function connect() {
    
                stompClient.connect({
                    Authorization:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIxOTg1NjQxNjAsImlhdCI6MTUzMTg5NzUwMCwidXNlcm5hbWUiOiJ6cXcxMSJ9.VFR2EKUx5BTYLDkDogiLA9LfNVoPjOzQ3rTWoEy7He4"
                        //这里可以改成token
                        // name: 'admin' // 携带客户端信息
                    },
                    function connectCallback(frame) {
                        // 连接成功时(服务器响应 CONNECTED 帧)的回调方法
                        alert("success");
                        subscribe();
                    },
                    function errorCallBack(error) {
                        // 连接失败时(服务器响应 ERROR 帧)的回调方法
                        alert("error");
                    });
            }
            function disconnect() {
                if (stompClient != null) {
                    stompClient.disconnect();
                }
    //            setConnected(false);
                console.log("Disconnected");
            }
        </script>
    </head>
    <body>
        <input type="text" id="info"/><button onclick="send5();">发送</button>
    </body>
    </html>
    

    后端MessaeMapping处:

    package com.iscas.biz.test.controller;
    
    import com.iscas.templet.common.ResponseEntity;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.handler.annotation.*;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
    import org.springframework.messaging.simp.user.SimpUser;
    import org.springframework.messaging.simp.user.SimpUserRegistry;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.security.Principal;
    import java.util.Map;
    
    /**
     * 如有要看例子,请打开注释
     *
     **/
    @RestController
    @Slf4j
    public class WebSoketDemoController {
    
        //spring提供的发送消息模板
        @Autowired
        private SimpMessagingTemplate messagingTemplate;
    
        @Autowired
        private SimpUserRegistry userRegistry;
    
    
        /**
         * 接收用户信息
         * */
        @MessageMapping(value = "/principal")
        public void test(Principal principal) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    
    
        /**
         * 接收数据体
        * */
        @MessageMapping(value = "/P2P")
        public void templateTest(Principal principal, Map<String,String> data) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    
    
        /**
         * 接收路径参数
         * */
        @MessageMapping(value = "/path/{name}/{company}")
        public void pathTest(Principal principal, @DestinationVariable String name, @DestinationVariable String company) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    
        /**
         * 接收header参数
         * */
        @MessageMapping(value = "/header")
        public void headerTest(Principal principal, @Header String one, @Header String two) {
            log.info("当前在线人数:" + userRegistry.getUserCount());
            int i = 1;
            for (SimpUser user : userRegistry.getUsers()) {
                log.info("用户" + i++ + "---" + user);
            }
            //发送消息给指定用户
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/message","服务器主动推的数据");
        }
    
        /**
         * 接收HttpSession数据
         * */
        @MessageMapping(value = "/httpsession")
        public void httpsession( StompHeaderAccessor accessor) {
            String name = (String) accessor.getSessionAttributes().get("name");
            System.out.println(1111);
        }
    
    //    /**
    //     * 接收param数据
    //     * */
    //    @MessageMapping(value = "/param")
    //    public void param(String name) {
    //        System.out.println(1111);
    //    }
    
        /*广播*/
        @MessageMapping("/broadcast")
        @SendTo("/topic/getResponse")
        public ResponseEntity topic() throws Exception {
            return new ResponseEntity(200,"success");
        }
    
    }
    
    

    Websocket配置类:

    package com.iscas.base.biz.config.stomp;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.messaging.simp.config.ChannelRegistration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.*;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    /**
     * webscoket配置
     *
    * @auth zhuquanwen
     *
     **/
    
    //@Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketStompConfig /*extends AbstractWebSocketMessageBrokerConfigurer*/ implements WebSocketMessageBrokerConfigurer {
    
        /**
         * 注册stomp的端点
         */
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            // 允许使用socketJs方式访问,访问点为webSocketServer,允许跨域
            // 在网页上我们就可以通过这个链接
            // http://localhost:8080/webSocketServer
            // 来和服务器的WebSocket连接
            registry.addEndpoint("/webSocketServer")
                    .addInterceptors(new HttpSessionHandshakeInterceptor())
                    .setAllowedOrigins("*")
                    .withSockJS();
    
        }
    
        /**
         * 配置信息代理
         */
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            // 订阅Broker名称
            registry.enableSimpleBroker("/queue", "/topic");
            // 全局使用的消息前缀(客户端订阅路径上会体现出来)
            registry.setApplicationDestinationPrefixes("/app");
            // 点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
            registry.setUserDestinationPrefix("/user/");
        }
    
        /**
         * 配置客户端入站通道拦截器
         */
        @Override
        public void configureClientInboundChannel(ChannelRegistration registration) {
            registration.interceptors(createUserInterceptor());
    
        }
    
    
    
         /*将客户端渠道拦截器加入spring ioc容器*/
        @Bean
        public UserInterceptor createUserInterceptor() {
            return new UserInterceptor();
        }
    
    
        @Override
        public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
            registration.setMessageSizeLimit(500 * 1024 * 1024);
            registration.setSendBufferSizeLimit(1024 * 1024 * 1024);
            registration.setSendTimeLimit(200000);
        }
    
    
    }
    
    

    用户拦截器:

    package com.iscas.base.biz.config.stomp;
    
    import com.auth0.jwt.interfaces.Claim;
    import com.iscas.base.biz.config.Constants;
    import com.iscas.base.biz.util.SpringUtils;
    import com.iscas.templet.exception.ValidTokenException;
    import com.iscas.base.biz.model.auth.User;
    import com.iscas.base.biz.util.JWTUtils;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.MessageChannel;
    import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
    import org.springframework.messaging.simp.stomp.StompCommand;
    import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
    import org.springframework.messaging.support.ChannelInterceptor;
    import org.springframework.messaging.support.MessageHeaderAccessor;
    
    import javax.servlet.http.HttpSession;
    import java.io.UnsupportedEncodingException;
    import java.util.LinkedList;
    import java.util.Map;
    
    /**
     *用户拦截器
     **/
    public class UserInterceptor implements ChannelInterceptor {
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
    
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
                if (raw instanceof Map) {
                    //这里就是token
                    Object name = ((Map) raw).get(Constants.TOKEN_KEY);
                    if (name instanceof LinkedList) {
                        // 设置当前访问器的认证用户
    //                    String token = ((LinkedList) name).get(0).toString();
    //                    String username = null;
    //                    try {
    //                        Map<String, Claim> claimMap = JWTUtils.verifyToken(token);
    //                        username = claimMap.get("username").asString();
    //                        if(username == null){
    //                            throw new RuntimeException("websocket认证失败");
    //                        }
    //                    } catch (UnsupportedEncodingException e) {
    //                        e.printStackTrace();
    //                        throw new RuntimeException("websocket认证失败", e);
    //                    } catch (ValidTokenException e) {
    //                        e.printStackTrace();
    //                        throw new RuntimeException("websocket认证失败", e);
    //                    }
    //                    User user = new User();
    //                    user.setUsername(username);
    //                    accessor.setUser(user);
    
                        User user = new User();
                        user.setUsername("lalala");
                        accessor.setUser(user);
    
                    }
                }
            } else if (StompCommand.SEND.equals(accessor.getCommand())) {
                //发送数据
    
            }
    
            return message;
        }
    
        @Override
        public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
    
        }
    
        @Override
        public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
    
        }
    
        @Override
        public boolean preReceive(MessageChannel channel) {
            return false;
        }
    
        @Override
        public Message<?> postReceive(Message<?> message, MessageChannel channel) {
            return null;
        }
    
        @Override
        public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
    
        }
    }
    
    
    展开全文
  • 这是Native Handler系列的第二...本篇详解讲述AMessage源码。 先来回顾一下第一部分的图: AMessage AOSP Version: Oreo 8.0.0_r4 AMessage可以算的上市整个消息系统中的核心接口了。自然,它的接口也比其它两个...

    该系列文章,会分为三个部分:

    AMessage

    AOSP Version: Oreo 8.0.0_r4

    AMessage可以算的上市整个消息系统中的核心接口了。自然,它的接口也比其它两个结构体复杂得多。

    namespace android {
    
    struct ABuffer;
    struct AHandler;
    struct AString;
    class Parcel;
    
    struct AMessage : public RefBase {
        AMessage();
        AMessage(uint32_t what, const sp<const AHandler> &handler);
    
        /**
         * 根据parcel构建一个AMessage
         *
         * @param parcel 数据包
         * @param maxNestingLevel 最大嵌套层级
         * @return AMessage是可以嵌套存在的,但如果嵌套层级大于maxNestingLevel,将产生异常,并返回NULL
         * 如果消息类型无法被函数识别也会产生异常,并返回NULL。支持的消息类型有:
         * int32_t Int32 int64_t Int64 size_t Size float Float double Double
         * AString String AMessage Message
         */
        static sp<AMessage> FromParcel(const Parcel &parcel,
                                       size_t maxNestingLevel = 255);
    
        /** 将当前AMessage写入数据包parcel。AMessage中所有的items类型必须能被识别(见FromParcel部 
         *  分),否则会产生异常。
         */
        void writeToParcel(Parcel *parcel) const;
        void clear();
    	/**
    	 * 设置Int32类型的item,类似的函数还有setInt64、setSize、setFloat、setDouble、setPointer、
    	 * setString、setRect、setBuffer、setObject等。并通过findXXX获取每个item的值。
    	 * 
    	 * @param name 每个item都会有一个独立的key
    	 * @param value 每个item的值
    	 */
        void setInt32(const char *name, int32_t value);
        bool contains(const char *name) const; // 判断是否包含名为name的item
        status_t post(int64_t delayUs = 0); // 发送消息
        // 将消息post到目标AHandler,并等待回复或者异常。
        status_t postAndAwaitResponse(sp<AMessage> *response);
    
        // If this returns true, the sender of this message is synchronously
        // awaiting a response and the reply token is consumed from the message
        // and stored into replyID. The reply token must be used to send the response
        // using "postReply" below.
        bool senderAwaitsResponse(sp<AReplyToken> *replyID);
    
        // 将message作为回复令牌发送,一个回复令牌只能使用一次,如何可以被发送,返回true,否则返回false
        status_t postReply(const sp<AReplyToken> &replyID);
    
        // 执行当前对象的深拷贝。警告:RefBase类型item值,不会被拷贝,只会让引用计数加+1。
        sp<AMessage> dup() const;
    
        /**
         * 对当前对象进行深/浅拷贝,并返回一个包含差异的AMessage。
         * 警告:RefBase类型item值,不会被拷贝,只会让引用计数加+1。
         *
         * @param other 用于和当前对象比较的AMessage对象。
         * @param deep 是否进行深比较。
         * @return AMessage 返回一个差异的AMessage对象。
         */
        sp<AMessage> changesFrom(const sp<const AMessage> &other, bool deep = false) const;
    
        AString debugString(int32_t indent = 0) const;// 这是干啥的?可能是用来打印消息本身的,有兴趣的小伙伴可以自己试一下
        enum Type { // 消息中,item的类型
            kTypeInt32,
            kTypeInt64,
            kTypeSize,
            kTypeFloat,
            kTypeDouble,
            kTypePointer,
            kTypeString,
            kTypeObject,
            kTypeMessage,
            kTypeRect,
            kTypeBuffer,
        };
    
        size_t countEntries() const;
        const char *getEntryNameAt(size_t index, Type *type) const;
    protected:
        virtual ~AMessage(); // 虚析构函数
    
    private:
        friend struct ALooper; // deliver()
        // 通过setWhat函数设置该值,通过what函数获取该值。该值用于区分不同的消息,可以认为是一种消息类型
        uint32_t mWhat; 
    
        // used only for debugging
        ALooper::handler_id mTarget;
        // 消息的处理器:指定该消息最终由那个AHandler处理。通过setTarget函数初始化。
        wp<AHandler> mHandler;
        wp<ALooper> mLooper;
        struct Rect { // 矩形结构体, 用于保存视频帧的显示尺寸的
            int32_t mLeft, mTop, mRight, mBottom;
        };
    
        struct Item { // item结构体
            union { // item 的值
                int32_t int32Value;
                int64_t int64Value;
                size_t sizeValue;
                float floatValue;
                double doubleValue;
                void *ptrValue;
                RefBase *refValue;
                AString *stringValue;
                Rect rectValue;
            } u;
            const char *mName; // item的key
            size_t      mNameLength; // item key的长度
            Type mType; // item的消息类型
            void setName(const char *name, size_t len);
        };
    
        enum {
            kMaxNumItems = 64 // 最大item数
        };
        Item mItems[kMaxNumItems]; // item 数组
        size_t mNumItems; // 实际item数
    
        Item *allocateItem(const char *name);
        void freeItemValue(Item *item);
        const Item *findItem(const char *name, Type type) const;
    
        void setObjectInternal(
                const char *name, const sp<RefBase> &obj, Type type);
    
        size_t findItemIndex(const char *name, size_t len) const;
    
        void deliver();
    
        DISALLOW_EVIL_CONSTRUCTORS(AMessage);
    };
    
    }  // namespace android
    

    AMessage的函数接口相对较多,这里就挑一个我认为重要的展开一下:

    构造函数

    AMessage::AMessage(void)
        : mWhat(0),
          mTarget(0),
          mNumItems(0) {
    }
    
    AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler)
        : mWhat(what),
          mNumItems(0) {
        setTarget(handler);
    }
    
    1. 无参构造没什么好说的,将mWhat、mTarget、mNumItems设置为零。
    2. 有参构造:将mNumItems设置为零,参数中的handler设置给mHandler。

    这里出现了setTarget函数,索性先看看这个函数呗。

    setTarget

    void AMessage::setTarget(const sp<const AHandler> &handler) {
        if (handler == NULL) { // 如果参数为NULL,AMessage对象回到初始化的状态
            mTarget = 0;
            mHandler.clear();
            mLooper.clear();
        } else { // 将handler中的相关应用赋值给AMessage对象
            mTarget = handler->id(); 
            mHandler = handler->getHandler();
            mLooper = handler->getLooper();
        }
    }
    

    为什么取名叫做setTarget呢? 因为AMessage对象,最终需要AHandler对象处理,这两个原本孤立的对象,通过各自内部对彼此的引用持有,达到这样的目的。所以,该函数可以理解为,为该条消息(AMessage)设定目标处理对象。

    setXXX/findXXX

    在看基本数据类型的setXXX/findXXX函数之前,先介绍一下几个比较重要的函数,allocateItem、findItem、findItemIndex。

    findItemIndex

    findItemIndex函数中,有大量的DUMP_STATS的宏定义,对本文来说没什么意义。去掉之后的代码就像这个样子:

    inline size_t AMessage::findItemIndex(const char *name, size_t len) const {
        size_t i = 0;
        for (; i < mNumItems; i++) {
            if (len != mItems[i].mNameLength) { // 如果入参len和itemName长度不一致,继续下一次判断
                continue;
            }
            if (!memcmp(mItems[i].mName, name, len)) { // 长度一致,且名字相同,找到了目标item,跳出循环
                break;
            }
        }
        return i;
    }
    

    在AMessage.h的代码分析部分,已经说明了:

    • mNumItems:是当前AMessage中的Item计数。
    • mItems:是当前AMessage的Item数组。

    这个函数的功能从名字上就很直观:findItemIndex可以直接看出是“找到指定item的数组index”的意思,这种自注释的命名,值得我们学习。

    另外,方法实现上也很简单:

    1. 编译mItems数组。
    2. 通过比较入参len的值,和每个item名称长度作对比,如果相等进入下一个判断逻辑,如果不相等进入下一步循环。
    3. 如果名称长度相等,且名称通过memcmp判断相同,则找到和入参name匹配的item,返回找到的item在数组中的index。如果没有找到,则返回数组mItems的长度。

    findItem

    const AMessage::Item *AMessage::findItem(
            const char *name, Type type) const {
        size_t i = findItemIndex(name, strlen(name));
        if (i < mNumItems) {
            const Item *item = &mItems[i];
            return item->mType == type ? item : NULL;
        }
        return NULL;
    }
    

    findItem 函数主要功能:

    1. 通过findItemIndex函数,找到对应入参name在mItems数组中的索引。
    2. 通过上一步的索引,从mItems数组中获取Item指针,并返回。
    3. 没找到,返回NULL。

    allocateItem

    我们都知道AMessage中有很多的Item,Amessage为创建这些Item,专门编写了一个函数:allocateItem

    AMessage::Item *AMessage::allocateItem(const char *name) {
        size_t len = strlen(name);
        size_t i = findItemIndex(name, len); // 查看需要新建的item是否已近存在
        Item *item;
        if (i < mNumItems) { // 如果需要新建的item已存在,将已存在的item清空
            item = &mItems[i];
            freeItemValue(item);
        } else { // 需要新建的item不存在
            CHECK(mNumItems < kMaxNumItems);
            i = mNumItems++; // item计数加1
            item = &mItems[i]; // 将mItems中第i个位置留给新的item
            item->setName(name, len); // 为item设置名称
        }
        return item;
    }
    

    allocateItem函数,并不会真的去alloc内存,因为mItems数组中已经将内存分配好了,只需要将对应数组中index的位置指针赋值给一个临时变量,然后通过临时指针变量来初始化数组中item的值就可以了。这也就是allocateItem函数的逻辑。

    setXXX/findXXX

    #define BASIC_TYPE(NAME,FIELDNAME,TYPENAME)                             
    void AMessage::set##NAME(const char *name, TYPENAME value) {            
        Item *item = allocateItem(name);                                    
                                                                            
        item->mType = kType##NAME;                                          
        item->u.FIELDNAME = value;                                          
    }                                                                       
                                                                            
    /* NOLINT added to avoid incorrect warning/fix from clang.tidy */       
    bool AMessage::find##NAME(const char *name, TYPENAME *value) const {  /* NOLINT */ 
        const Item *item = findItem(name, kType##NAME);                     
        if (item) {                                                         
            *value = item->u.FIELDNAME;                                     
            return true;                                                    
        }                                                                   
        return false;                                                       
    }
    
    BASIC_TYPE(Int32,int32Value,int32_t)
    BASIC_TYPE(Int64,int64Value,int64_t)
    BASIC_TYPE(Size,sizeValue,size_t)
    BASIC_TYPE(Float,floatValue,float)
    BASIC_TYPE(Double,doubleValue,double)
    BASIC_TYPE(Pointer,ptrValue,void *)
    
    #undef BASIC_TYPE
    

    AMessage中,基本数据类型的set/find方法定义就在上边了。通过宏定义,设计一个同样算法的函数,表达N个函数的做法,真的是绝了,如果java里也可以这么做的话,要少多少set/get函数,少多少重复代码啊。我代表自己,强烈建议java9引入这种机制。

    在这里,通过宏替换,总共可以展开12个不同函数。篇幅起见,就只展开Int32类型的分析分析。

    set##NAME中的“##”符号的作用是,将相邻的两个宏连接起来。于是就有了setInt32、setInt64等函数。

    setInt32

    void AMessage::setInt32(const char *name, int32_t value) {            
        Item *item = allocateItem(name); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看
        item->mType = kTypeInt32;                                          
        item->u.int32Value = value;                                          
    }                                                 
    

    set系列函数都比较简单:

    1. 通过allocateItem函数,获取mItems数组中,对应name的空item的指针。
    2. 通过上一步获取的指针,修改mItems数组对应item的mType值。
    3. 通过第一步获取的指针,修改mItems数组对应item的值(该值的内存使用联合体表示)。

    简单直白又透彻。完美的代码

    findInt32

    /* NOLINT added to avoid incorrect warning/fix from clang.tidy */       
    bool AMessage::findInt32(const char *name, int32_t *value) const {  /* NOLINT */ 
        const Item *item = findItem(name, kTypeInt32); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看                    
        if (item) {                                                         
            *value = item->u.int32Value;                                     
            return true;                                                    
        }                                                                   
        return false;                                                       
    }
    

    find系列函数,同样直白:

    1. 通过findItem找到name相同且type相同的item,返回它的指针。没找到的话,会返回一个NULL。
    2. 如果第一步找到了对应指针,则通过指针,将对应item的值赋值给*value,并返回true。表示找到了对应item,函数调用线程可以根据*value 指向的内存,获取对应item的值。
    3. 如果第一步没有找到,返回false。

    其它常见的set/find函数

    setMessage

    void AMessage::setMessage(const char *name, const sp<AMessage> &obj) {
        Item *item = allocateItem(name); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看
        item->mType = kTypeMessage;
    
        if (obj != NULL) { obj->incStrong(this); }
        item->u.refValue = obj.get();
    }
    

    如果你记性足够好的话,应该记得AMessage的源码介绍的时候,FromParcel函数的注释部分提示:AMessage是可以嵌套存在的,但如果嵌套层级大于maxNestingLevel,将产生异常。

    这个函数,就可以实现AMessage嵌套,只需将item类型指定为kTypeMessage即可,剩下的操作和其它set函数也没啥区别了。

    我发现,set/find系列方法,都不是很复杂:

    1. 通过allocateItem找到合适的item内存指针。
    2. 通过指针,设置对应item的mType类型,在这个函数中,类型自然是kTypeMessage
    3. 将被嵌套的AMessage对象引用计数加1,后,将AMessage对象的值放到当前item中去。

    findMessage

    bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const {
        const Item *item = findItem(name, kTypeMessage);
        if (item) {
            *obj = static_cast<AMessage *>(item->u.refValue);
            return true;
        }
        return false;
    }
    

    和基础类型的find函数类似:

    1. 通过findItem找到对应的item,如果没找到*item为NULL。
    2. 如果找到了对应item,通过*obj将找到的AMessage提供给调用线程使用,并返回true。
    3. 没找到,返回false。

    其它的setBuffer、setObject以及与之对应的find函数,都和set/findMessage相关函数类似,就不多说了。

    setRect

    不得不说一下Rect 相关的函数(Rect结构体,前面代码中已介绍)。Rect结构体是用来保存视频帧的显示尺寸的。所以,就Android多媒体框架来说,非常重要。

    void AMessage::setRect(
            const char *name,
            int32_t left, int32_t top, int32_t right, int32_t bottom) {
        Item *item = allocateItem(name); // 拿到一个空的item
        item->mType = kTypeRect; // 设置item类型
    	// 左上右下的边界值
        item->u.rectValue.mLeft = left;
        item->u.rectValue.mTop = top;
        item->u.rectValue.mRight = right;
        item->u.rectValue.mBottom = bottom;
    }
    

    findRect

    再看一下find函数:

    bool AMessage::findRect(
            const char *name,
            int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const {
        const Item *item = findItem(name, kTypeRect); // 找到item
        if (item == NULL) {
            return false;
        }
    	// 将上下左右值,通过指针,放到不同的内存地址去。
        *left = item->u.rectValue.mLeft; 
        *top = item->u.rectValue.mTop;
        *right = item->u.rectValue.mRight;
        *bottom = item->u.rectValue.mBottom;
        return true;
    }
    

    和其他find函数也没啥区别,一定要有的话,大概就是:findRect的参数比较多 .~. 。

    消息交互相关函数

    deliver

    void AMessage::deliver() {
        sp<AHandler> handler = mHandler.promote(); // 获取当前AMessage绑定的Handler对象
        if (handler == NULL) {
            ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
            return;
        }
    
        handler->deliverMessage(this); // 将自己作为参数,调用目标handler的deliverMessage
    }
    

    deliver 也不复杂,咦,为啥说也?

    哎,简单的说一下需要注意的地方:

    • 关于mHandler:mHandler对象是AMessage中的私有字段,该字段唯一初始化的地方在前面讲过的AMessage::setTarget函数中。虽然,到现在,还没有分析整个消息机制工作流程,但我们可以大胆的猜想一下:AMessage实际只是消息的载体,消息只有发送出去了,被处理了才有意义。但是在AMessage的deliver(发送)函数中,却用到了一个mHandler对象。那么,可以考虑,mHandler对象,在消息创建之初便已经被初始化。换句话说,setTarget函数,在AMessage创建之初就会调用。这点,我希望在下一篇文章得到验证。
    • 关于handler->deliverMessage:看到这里,该函数应该已经熟悉了,AHandler::deliverMessage函数什么都没做,直接将入参原封不动的通过onMessageReceived纯虚函数,交给子类处理了。子类怎么处理,关我屁事!!!! 好吧,不急,后面分析具体事例的时候肯定会稍微提一下。

    post

    status_t AMessage::post(int64_t delayUs) {
        sp<ALooper> looper = mLooper.promote(); // 获取当前AMessage绑定的mLooper对象
        if (looper == NULL) {
            ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
            return -ENOENT;
        }
    
        looper->post(this, delayUs); // 将自己和发送时间传递给looper处理。
        return OK;
    }
    

    哇哇哇~,这函数简直就是AMessage::deliver函数的翻版,已经在deliver函数中说过的就不再说了。说一下不同的地方:

    • looper: mLooper赋值的地方,也在AMessage::setTarget函数中,和mHandler赋值的时机是一样的,在AMessage创建时。
    • AMessage::post直接将自己和消息的发送时间,通过AMessage创建时赋值的ALooper::post函数,交给looper去处理,处理结果就是将AMessage对象自己,和发送时间(delayUs)包装一下,放到一个消息队列中区。不知道ALooper::post函数的帅哥(应该没有仙女会看这种文章吧!)可以去ALooper一小节回顾一下。

    postAndAwaitResponse

    status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
        sp<ALooper> looper = mLooper.promote(); // 获取当前AMessage绑定的mLooper对象
        if (looper == NULL) {
            ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
            return -ENOENT;
        }
    
        sp<AReplyToken> token = looper->createReplyToken();
        if (token == NULL) {
            ALOGE("failed to create reply token");
            return -ENOMEM;
        }
        setObject("replyID", token);
    
        looper->post(this, 0 /* delayUs */);
        return looper->awaitResponse(token, response);
    }
    

    从函数名可以看出,该函数除了和AMessage::post函数一样,会将消息放到ALooper中的消息队列中外,还会等待消息的返回。而在整个Native Handler 消息机制中,消息的返回都是通过回复令牌体现。

    这里重点看一下回复令牌和等待返回的代码。

    1、回复令牌的创建

    sp<AReplyToken> token = looper->createReplyToken();
        if (token == NULL) {
            ALOGE("failed to create reply token");
            return -ENOMEM;
    }
    setObject("replyID", token);
    

    这段代码,直接通过mLooper获取一个回复令牌AReplyToken。并将回复令牌的指针当作自己的一个item存起来,key是“replyID”,值是刚刚获取的回复令牌。

    来来来,我们这里刨一个坑先。再来联想一下,在一个需要等待回复的函数调用中,创建了一个回复令牌,并将该令牌作为消息的一部分(一个item)存储起来,但在不需要回复的另一个函数中,却没有这种动作。是不是可以大胆的判断:

    • 一个消息,如果有名为"replyID"的item,那么它就已经在准备返回了。
    • 如果没有“replyID”的item,那么它应该是处在创建或者刚加入到消息队列中,而没有被线程处理。

    大胆假设,我会在后面小心求证的。至于假设的意义? 哎~ 想太多了, 消磨廉价的光阴罢了。我的发际线~

    2、等待回复

    looper->post(this, 0 /* delayUs */);
    return looper->awaitResponse(token, response);
    

    post函数就不多说了。简单聊一下awaitResponse:

    两个参数:

    • token:回复令牌实际上是记录当前消息是否已经回复等消息的。因为前面记录到当前消息item中的是token的指针,所以,这里传参的意义是通过指针,间接修改当前消息的token。
    • response:外界传入的AMessage指针,用于回传消息处理的结果。

    另外:因为ALooper::awaitResponse函数中存在while循环和Condition锁,所以该函数是个会阻塞的函数。直到有了回复消息,才会解除锁定状态。

    postReply

    status_t AMessage::postReply(const sp<AReplyToken> &replyToken) {
        if (replyToken == NULL) {
            ALOGW("failed to post reply to a NULL token");
            return -ENOENT;
        }
        sp<ALooper> looper = replyToken->getLooper();
        if (looper == NULL) {
            ALOGW("failed to post reply as target looper is gone.");
            return -ENOENT;
        }
        return looper->postReply(replyToken, this);
    }
    

    该函数的调用时机,暂时还不知道,通过“postReply”函数名称可以猜测,该函数是在消息接收端处理好消息和对应的回复令牌后,调用该函数,执行消息回复的逻辑。这里又挖了个坑,看后面能不能圆回来。

    根据上面的假设:回复令牌已经处理好,准备回复消息,那么代码块前面的各种非空校验显然是能够通过的。

    最终调用了ALooper::postReply函数,这里有两个参数:

    • replyToken:即已经设置了回复的回复令牌本身
    • this:AMessage消息对象自身。

    好累啊,不过还是先来回顾一下,前面说过的ALooper::postReply函数吧:

    postReply的主要作用,就是将回复令牌和回复消息绑定,并唤醒awaitResponse函数,处理回复消息。

    小结一下:AMessage::postReply函数也只是将传入的回复令牌和自己,交给ALooper::postReply函数处理,处理的结果是:将令牌与自身绑定后,通过广播,将所有等待mRepliesCondition锁的线程唤醒,告诉它们消息处理完了,起来干活。

    senderAwaitsResponse

    bool AMessage::senderAwaitsResponse(sp<AReplyToken> *replyToken) {
        sp<RefBase> tmp;
        bool found = findObject("replyID", &tmp);
    
        if (!found) {
            return false;
        }
    
        *replyToken = static_cast<AReplyToken *>(tmp.get());
        tmp.clear();
        setObject("replyID", tmp);
        // TODO: delete Object instead of setting it to NULL
    
        return *replyToken != NULL;
    }
    

    根据内心的指引,让我们畅想一下… ,咳咳,不对。

    应该是根据函数名,让我们继续猜一猜函数功能。sender Awaits Response = 发件人等待回应 。 什么鬼,我姑且理解为,这个函数是在消息回复的时候调用的吧,实在看不出其它含义了。还是看代码靠谱点。

    • findObject: 找到当前AMessage对象的回复令牌,如果没有就返回false。
    • *replyToken = static_cast<AReplyToken *>(tmp.get());:如果有就将指针赋值给入参,供调用者差遣,最后将当前AMessage的回复令牌指针清掉,返回true。

    小结一下:根据代码的意思,很简单,就是将当前AMessage的回复令牌拿给别人用,自己不要了。clear之后,又是一具白花花的身体。

    源码相关路径

    Android底层代码,一般*.h文件和.cpp*文件都存放在不同路径下。

    头文件

    /frameworks/av/include/media/stagefright/foundation/

    AMessage.h

    AHandler.h

    ALooper.h

    .cpp文件

    /frameworks/av/media/libstagefright/foundation/

    AMessage.cpp

    AHandler.cpp

    ALooper.cpp

    展开全文
  • vue element-ui message或this.$message 主动关闭 ヤッハロー、Kaiqisanすうう、一つふつうの学生プログラマである, 在我实际的项目过程中,赶巧不巧碰到服务器特别迟缓的时候,有时候在向后端请求信息的时候,会...

    vue element-ui message或this.$message 主动关闭

    ヤッハロー、Kaiqisanすうう、一つふつうの学生プログラマである, 在我实际的项目过程中,赶巧不巧碰到服务器特别迟缓的时候,有时候在向后端请求信息的时候,会卡顿特别久才能弹出“提交成功”,在提交成功弹出之前,页面就一直僵持在那里,这对用户非常不友好,于是,借此机会,我打算增加一个加载中动画效果。于是,我打算使用element-ui 的 message 功能模块来做一个“加载中”效果,提示用户的信息正在提交中。

    简答

    this.$message.close() // 不行
    
    let msg = this.$message({
    	....
    })
    msg.close() // 行
    

    详解

    this.$message({
    	duration: 0,   // 设置为0就可以使永久停留
    	type: 'warning',
    	message: '等待中....'
    })
    

    现在,信息加载已经完成了,我现在要把这个弹出框关掉

    this.$message.close() // 官网指定方法
    

    然而这样并不行,因为您还给这个弹出框指定一个参数来接收,所以它是匿名的,上面的方法并不能找到需要关闭的弹出框。就像setTimeout方法一样,如果没有一个接收的参数,它就永远无法被销毁。

    let timer = setTimeout(() => {
        console.log('ok')
        clearTimeout(timer) // 找到计时器传入的参数就可以删除,否则就无法删除。
    }, 1000)
    

    所以,下面这种方法才是OK的。

    // 点击按钮后触发
    openMsg() {
        this.msg = this.$message({ // 需要一个参数接收这个$message(msg来自data)
            duration: 0,
            type: 'warning',
            message: '等待中....'
        })
        // 使用延时器来模拟请求的异步方法
        setTimeout(() => {
            this.msg.close()  // 这样才能正确关闭
            this.msg = this.$message({
                duration: 1000,
                type: 'success',
                message: '提交成功'
            })
        }, 2000)
    }
    

    显示效果
    msg-gif

    下面为更加完美的方法,更加适用于项目,程序也更加健壮----页面只能容许一个弹出框的存在。克服了 message 中 close() 只能关闭一个弹框的缺点。

    openMsg() {
        if (this.status) { // 为事件加锁,点击之后,在动作没有完成之前不允许再次触发事件
            this.status = false
            if (this.msg) {	// 防止第一次点击报错
    		    this.msg.close()
    		}
            this.msg = this.$message({
                duration: 0,
                type: 'warning',
                message: '等待中....'
            })
            setTimeout(() => {
                this.msg.close()
                this.msg = this.$message({
                    duration: 1000,
                    type: 'success',
                    message: '提交成功'
                })
                this.status = true
            }, 2000)
        }
    }
    

    展示效果
    msg-2

    总结

    给大伙一个建议,如果觉得官方文档的方法失效的话,请第一时间去百度,验证自己的方法是否有误(大半的错误是自己引起的),如果百度有解决方法的话就直接拿过来,吃个教训;如果没有的话就去专业社区寻求求助,或者去专门讨论这个专项内容的群里面寻找解决方案。

    展开全文
  • ElementUI 消息提示组件Message

    千次阅读 2019-11-21 14:07:32
    目录官方地址应用场景基本用法通知类型偏移量使用HTML代码片段开启关闭按钮文字内容居中自定义图标iconClassonCloseclose实例方法全局引入单独引用Options方法 ...$message()接受一个对象, messa...
  • 本文是从源码的角度对andorid异步消息处理机制的梳理,那么在文章开始阶段,先简单介绍,异步消息处理机制中各部件的作用以及处理处理机制的概述1、Handler、MessageMessageQueue、Looper功能简述Handler负责消息...
  • 之前我们的消息都是直接通过使用String,然后通过netty的默认编解码器StringDecoder、StringEncoder来进行...所以就想到用一个message进行封装,同时自定义自己的编解码器。 备注:这篇内容修改的内容比较多,或许有...
  • 消息队列(Message Queue)简介及其使用 利用 MSMQ(Microsoft Message Queue),应用程序开发
  • Django3.0中settings.py的消息配置。 消息 配置模板参数说明 模板参数说明 MESSAGE_LEVEL 设置消息框架将记录的...MESSAGE_LEVEL = message_constants.DEBUG MESSAGE_STORAGE 默认: ‘django.contrib.messages.sto
  • Message 如何获取一个消息 Message.obtain() 消息的回收利用 MessageQueue MessageQueue 的属性 何时初始化 消息入队的过程 消息出队的过程 Looper 线程相关 ThreadLocal 无限循环调度 如何停止 Handler ...
  • 文章目录MessageMessage 的基本概念Messagebox Message Message 的基本概念 Messagebox
  • 先分析完MessageMessageQueue源码 /** * 获取Message的最好办法是调用Message.obtain()或Handler.obtainMessage()方法 * 这将会从message pool中获取,避免硬创建导致过大的开销。 */ public final class ...
  • 解决 iframe.postMessage()多次触发请求问题 方法一,将addEventListener改成onmessage window.addEventListener(‘message’, (e) => {})多次调用会多次生成不同的匿名函数e,应指向同一个命名函数 window....
  • 进行消息类型转换,将org.springframework.amqp.core.Message类型通过messagingMessageConverter解析成org.springframework.messaging.Message类型,这一点与消息发送时的RabbitTemplate#convertAndSend中的 ...
  • RabbitMQ系列-MessageListener

    千次阅读 2018-10-11 17:41:11
    Spring AMQP 源码分析 04 - MessageListener ## 测试代码 gordon.study.rabbitmq.springamqp.AsyncConsumer.java   ### 分析 ## MessageListener org.springframework.amqp.core.MessageListener 是 ...
  • 深入源码解析Android中的Handler,Message,MessageQueue,Looper

    万次阅读 多人点赞 2015-08-01 02:20:53
    本文主要是对Handler和消息循环的实现原理进行源码分析,通过分析源码的方式去探索Thread、MessageQueue、Looper、Message以及Handler的实现原理,并最终通过一张图的形式将它们之间的关系展示出来。
  • 看到有信息java.sql.SQLException: Ioexception: Message file 'oracle.net.mesg.Message' is missing. 先去查看数据库是否正常:通过plsql连接oracle数据库,显示数据库正常:   SQL> create table zz_bak...
  • MessageID

    千次阅读 2019-03-22 10:37:10
    MessageID是在发送时才确定好的,所以在message发送前,使用提供的setMessageId设置新的Id也没有作用 另外,topic下同一条消息发送至不同的消费者,但是所有消息ID是一致的。因为广播模式只是按照订阅者数量,将消息...
  • vue+elementUI $message

    万次阅读 2018-12-21 16:13:52
    element-ui,$message显示倒计时信息 element-ui,$message显示倒计时信息 element-ui 提供的message组件,文字是写死的,没有提供动态变化的方法。 但是作为一个vue组件,他的message属性是双向绑定的。 下面是...
  • 深入理解MessageQueue

    万次阅读 2017-05-05 09:32:55
    这里有必要提一下MessageQueue的数据结构,是一个单向链表,Message对象有个next字段保存列表中的下一个,MessageQueue中的mMessages保存链表的第一个元素。 循环体内首先调用 nativePollOnce(ptr, ...
  • 本文更新地址:https://haoqchen.site/2018/05/07/understanding-of-message_filters/ 左侧专栏还在更新其他ROS实用技巧哦,关注一波? 0. 写在最前面  因为日常看代码经常能看到tf相关的一些函数,转来转去,绕...
  • 1. 前言创建Message对象的时候,有三种方式,分别为: 1.Message msg = new Message(); 2.Message msg2 = Message.obtain(); 3.Message msg1 = handler1.obtainMessage(); 这三种方式有什么区别呢?2.使用方式...
  • 痛点:你有没有因为手快就commit了,但是忘记修改了message?你是不是有时候觉得自己push过的记录,message写的不明确?这个时候,怎么办?2.修改已经commit但没有push的记录message2.1我现在有3次commit等待push,...
  • 问题 Element UI的Message消息提示是点击一次触发一次的,如果一直点,然后就会出现...import ElementUI, { Message } from 'element-ui' // 为了实现Class的私有属性 const showMessage = Symbol('showMessage') /...
  • 很多小伙伴可能会在实际的...官方文档的描述是这样 : " 调用 Message 或 this.$message 会返回当前 Message 的实例。如果需要手动关闭实例,可以调用它的 close 方法。" 所以贴上亲测有效的代码 OnOfflineStatus
  • 简单介绍sip协议message方法

    千次阅读 2019-03-12 17:38:25
    简单介绍sip协议message方法实验环境报文交互过程 实验环境 通过实验抓取message报文进行分析。 sip server采用的是brekeke(可以官网免费下载,获取60天使用)。具体安装过程见博客:...
  • "328","action":"ADD","type":"COMMON_EQU_NAMEPLATE_PIC"} 贴到rabbit 客户端中, 程序开始疯狂报错 " Execution of Rabbit message listener failed ", 无奈只能删除queue,再次像下图中粘贴消息,还是会报同样...
  • H5 postMessage方法 以及监听

    千次阅读 2019-08-22 11:12:35
    html5 postMessage解决跨域、跨窗口消息传递 一些麻烦事儿 平时做web开发的时候关于消息传递,除了客户端与服务器传值还有几个经常会...postMessage() 这些问题都有一些解决办法,但html5引入的message的API可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,069,537
精华内容 827,814
关键字:

message