amq 源码分析之demo分析1


1:构建activemq源码

这里采用的是maven构建的。这里不作为重点了。

2:以marketdata publisher为例分析

我们这里就以market data publisher 作为整个程序的入口开始进行分析。

整个连接主要是讲请求发送到PortfolioPublishServlet 这个servlet来进行处理。该servlet还是比较简单的。就是通过webclient来发送消息。

protected voidsendMessage(WebClient client, String[] stocks) throwsJMSException {

       Session session = client.getSession();

 

        int idx= 0;

        while (true) {

           idx = (int)Math.round(stocks.length *Math.random());

            if (idx< stocks.length) {

               break;

            }

        }

        Stringstock = stocks[idx];

       Destination destination = session.createTopic("STOCKS." +stock);

        StringstockText = createStockText(stock);

        log("Sending:" + stockText + " on destination: " +destination);

       Message message = session.createTextMessage(stockText);

       client.send(destination, message);

    }

在这里我们可以看到,主要是调用的webclient的send方法来进行消息的发送。看来,在这个应用上,webclient应该是核心的类了。

3:webclient的初始化

那么,我们就进入webclient,看看他是如何进行初始化的。

ProtfolioPublishServlet是继承自MessageServletSupport的。Webclient正是在这个servlet中进行初始化的。

WebClient.initContext(getServletContext())中的initConnectionFactory 方法从字面上理解就是初始化连接工厂啦。

String brokerURL = servletContext.getInitParameter(BROKER_URL_INIT_PARAM);

这里得到的brokerURL是vm://amq-broker。Broker就是JMS的中心啦。VM的意思就是虚拟机内通讯。AMQ支持多种通讯协议。我们最常用的其实是TCP协议。采用VM是如果多个应用如果运行在一个JVM上,那么就没有必要采用网络通讯了。直接在VM内部通讯就可以。这里算是一种优化吧。关于TCP后面会分析。这里就先看VM。

ActiveMQConnectionFactory amqfactory = new ActiveMQConnectionFactory(brokerURL);

链接工厂类,他实现了QueueConnectionFactory 接口和TopicConnectionFactory 。所以,他可以构建 QueueConnections 和 TopicConnections.

在JMS规范中,有两种消息模型,一种是点对点模型,也就是ptp。还有一种是发布订阅模型。

点对点模型:


发布订阅模型:


这两种模型我们会在后面也会详细的进行分析。

生成链接工厂之后,将连接工厂放置到context中,一遍后续的使用。

4:对于发送请求的处理

前面说过,对于请求的处理都是通过webclient来处理的。

public static WebClientgetWebClient(HttpServletRequest request) {

       HttpSession session = request.getSession(true);

       WebClient client = getWebClient(session);

        if(client == null || client.isClosed()) {

           client = WebClient.createWebClient(request);

           session.setAttribute(WEB_CLIENT_ATTRIBUTE,client);

        }

 

        return client;

    }

这里可以看到,webclient生成之后是放到session中的。

WebClient client = newWebClient();

        Stringauth = request.getHeader("Authorization");

        if(auth != null) {

           String[] tokens = auth.split(" ");

            if(tokens.length == 2) {

               String encoded = tokens[1].trim();

               String credentials = new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded));

               String[] creds = credentials.split(":");

               if (creds.length ==2) {

                   client.setUsername(creds[0]);

                   client.setPassword(creds[1]);

               }

            }

        }

这里我们可以看到,JMS是支持验证的。知道支持JAAS验证,至于其他的验证方式是否支持还不清楚。后续会慢慢研究。

5:消息通过什么发送的

 

protected voidsendMessage(WebClient client, String[] stocks) throwsJMSException {

       Session session = client.getSession();

 

        int idx= 0;

        while (true) {

            idx = (int)Math.round(stocks.length *Math.random());

            if (idx< stocks.length) {

               break;

            }

        }

        String stock =stocks[idx];

       Destination destination = session.createTopic("STOCKS." +stock);

        String stockText =createStockText(stock);

        log("Sending:" + stockText + " on destination: " +destination);

        Message message =session.createTextMessage(stockText);

       client.send(destination, message);

    }

这里有一个session的概念。这里的session和j2ee中的session和hibernate中的session有些像。是JMS规范中的一部分。

他主要有一下作用:

•  它是MessageProducer和MessageConsumer的工厂。

• 它是TemporaryTopic和TemporaryQueue的工厂。

• 它为需要动态操纵提供商专有目的地名字的客户端提供了一种创建Queue或Topic对象的途径。

• 它提供了提供优化后的消息工厂。

• 它支持事务串,这些事务将跨会话生产者和消费者的工作组合成原子单元。

• 它为它消费的消息和它生产的消息定义了一个连续的顺序。

• 它保留它消费的消息直到这些消息被确认。

• 它序列化注册到它的MessageListener的执行。

•它是QueueBrowser的工厂。

既然说到了session,就不能不说connection了。

Connection用作几个目的:

• 它封装了一个与JMS提供商的连接。他通常代表一个在提供商服务域和客户端间的打开的TCP/IP Socket。

• 在客户端授权时创建它。

• 它可以指定一个唯一的客户端标识。

• 它创建Session对象。

• 它提供ConnectionMetaData。

•它支持可选的ExceptionListener。

Connection作为重量级的对象,一般在客户端只建立一个对象。当然连接到不同的broker的时候,需要建立多个connection了。

消息发送就先简单介绍到这里,这里其实更多的是通过这个小demo,熟悉JMS规范。消息的发送和接收将重点在网络部分进行介绍。后续会分成几个部分开始博文:网络,存储,安全,集群等几个方面吧。

       

现在我写了一个stomp客户端,用以跟服务端通信,我的连接和订阅的返回正常,如下: 10:08:22.915 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.RestTemplate - HTTP POST https://127.0.0.1:8081/api/vms/vms-8ff225f900a44a90a937143fe6774a98/ws/status/611/88a50e273a154f358a8a97a9fa3aff72/xhr_send 10:08:22.928 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.RestTemplate - Response 204 NO_CONTENT ************connected 10:08:22.937 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.RestTemplate - HTTP POST https://127.0.0.1:8081/api/vms/vms-8ff225f900a44a90a937143fe6774a98/ws/status/611/88a50e273a154f358a8a97a9fa3aff72/xhr_send 10:08:22.957 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.RestTemplate - Response 204 NO_CONTENT ****************收到订阅的 /user/exchange/amq.direct/trade 消息:{"errorCode":0,"message":"success.","result":null} 但是我通过postman发送http触发业务逻辑后,本该从服务端通过stomp发送过来的消息有点问题,客户端报错如下: STOMP Exception: No suitable converter for payload type [class java.lang.String] from handler type [class com.tplink.cloud.demo.websocket_client.WebsocketClientApplication$1$1] STOMP Exception: No suitable converter for payload type [class java.lang.String] from handler type [class com.tplink.cloud.demo.websocket_client.WebsocketClientApplication$1$1] STOMP Exception: No suitable converter for payload type [class java.lang.String] from handler type [class com.tplink.cloud.demo.websocket_client.WebsocketClientApplication$1$1] 客户端的代码如下: package com.tplink.cloud.demo.websocket_client; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompFrameHandler; import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.messaging.simp.stomp.StompSession; import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; import org.springframework.web.socket.WebSocketHttpHeaders; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.messaging.WebSocketStompClient; import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport; import org.springframework.web.socket.sockjs.client.SockJsClient; import org.springframework.web.socket.sockjs.client.Transport; import org.springframework.web.socket.sockjs.client.WebSocketTransport; @SpringBootApplication public class WebsocketClientApplication { public static void main(String[] args) throws InterruptedException { // 禁用SSL验证 SSLUtil.disableSSLVerification(); // WebSocketClient webSocketClient = new StandardWebSocketClient(); List<Transport> transports = new ArrayList<>(2); transports.add(new WebSocketTransport(new StandardWebSocketClient())); transports.add(new RestTemplateXhrTransport()); SockJsClient webSocketClient = new SockJsClient(transports); WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); stompClient.setMessageConverter(new StringMessageConverter()); // String url1 = "/api/vms/{vmsId}/ws/status"; String vmsId = "vms-8ff225f900a44a90a937143fe6774a98"; String url = String.format("wss://127.0.0.1:8081/api/vms/%s/ws/status", vmsId); // String url = "wss://127.0.0.1:8081/api/vms/vms-8ff225f900a44a90a937143fe6774a98/ws/status/230/xgydnmu0/websocket"; // 添加头,握手的那些http,比如cookie final WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); headers.set("Cookie", "JSESSIONID=1990891CA73E17ECE4DEC572A6C98990; VMS_SID=v-d9e8566c-7185-4be9-9a61-91690dc34510"); headers.set("SourceType", "vms_web"); headers.set("Content-Type", "application/json;charset=UTF-8"); // 头部字段 final StompHeaders connectHeaders = new StompHeaders(); connectHeaders.set("CSRF-TOKEN", "2b6c8a71a4e147709e72101d65e5e088"); connectHeaders.set("accept-version", "1.1,1.0"); // connectHeaders.set("heart-beat", "20000,20000"); // 链接到服务端 stompClient.connect(url, headers, connectHeaders, new StompSessionHandlerAdapter() { @Override public void afterConnected(StompSession session, StompHeaders connectedHeaders) { System.out.println("************connected"); // 订阅用户消息,"/topic/vms/{vmsId}/users/{userId}/device-add-progress/{uuid}" String subscribeUrl = "/topic/vms/{vmsId}/users/{userId}/device-add-progress/{uuid}"; session.subscribe(subscribeUrl, new StompFrameHandler() { @Override public Type getPayloadType(StompHeaders headers) { return String.class; } @Override public void handleFrame(StompHeaders headers, Object payload) { // String payloadStr; // if (payload instanceof byte[]) { // payloadStr = new String((byte[]) payload); // } else { // payloadStr = payload.toString(); // } System.out.println("****************收到订阅的 /user/exchange/amq.direct/trade 消息:" + payload); } }); // 发送更新消息 // session.send("/app/trade", "更新消息"); } @Override public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { System.err.println("STOMP Exception: " + exception.getMessage()); } @Override public void handleTransportError(StompSession session, Throwable exception) { System.err.println("Transport Error: " + exception.getMessage()); } }); TimeUnit.SECONDS.sleep(1000); } } 帮我分析解决这个问题
10-11
有一定改善,现在客户端的报错变成了这样 STOMP Exception: Could not read JSON: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])“{“errorCode”:0,“message”:“success.”,“result”:{“success”:1,“fail”:0,“total”:2,“status”:0,“failList”:[{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-0D”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775975628801}]}}”; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])“{“errorCode”:0,“message”:“success.”,“result”:{“success”:1,“fail”:0,“total”:2,“status”:0,“failList”:[{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-0D”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775975628801}]}}”; line: 1, column: 1] STOMP Exception: Could not read JSON: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])“{“errorCode”:0,“message”:“success.”,“result”:{“success”:2,“fail”:0,“total”:2,“status”:0,“failList”:[{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-0D”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775975628801},{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775979823105}]}}”; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])“{“errorCode”:0,“message”:“success.”,“result”:{“success”:2,“fail”:0,“total”:2,“status”:0,“failList”:[{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-0D”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775975628801},{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775979823105}]}}”; line: 1, column: 1] STOMP Exception: Could not read JSON: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])“{“errorCode”:-6,“message”:“success.”,“result”:{“success”:0,“fail”:2,“total”:2,“status”:1,“failList”:[{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775979823105},{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:-5217,“message”:“The device is not connected to VMS.”},{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.”[truncated 160 bytes]; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Object value (token JsonToken.START_OBJECT) at [Source: (byte[])”{“errorCode”:-6,“message”:“success.”,“result”:{“success”:0,“fail”:2,“total”:2,“status”:1,“failList”:[{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:0,“message”:“success.”,“deviceId”:1705775979823105},{“devId”:“6mu+jnkDM7”,“name”:“VIGI C330 1.0”,“ipString”:“127.0.0.1”,“mac”:“40-C7-BF-00-00-05”,“hasPassword”:true,“errorCode”:-5217,“message”:“The device is not connected to VMS.”},{“devId”:“moZ8KwOtE+”,“name”:“VIGI C330 1.”[truncated 160 bytes]; line: 1, column: 1] 目前的代码如下: package com.tplink.cloud.demo.websocket_client; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompFrameHandler; import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.messaging.simp.stomp.StompSession; import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; import org.springframework.web.socket.WebSocketHttpHeaders; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.messaging.WebSocketStompClient; import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport; import org.springframework.web.socket.sockjs.client.SockJsClient; import org.springframework.web.socket.sockjs.client.Transport; import org.springframework.web.socket.sockjs.client.WebSocketTransport; @SpringBootApplication public class WebsocketClientApplication { public static void main(String[] args) throws InterruptedException { // 禁用SSL验证 SSLUtil.disableSSLVerification(); // WebSocketClient webSocketClient = new StandardWebSocketClient(); List<Transport> transports = new ArrayList<>(2); transports.add(new WebSocketTransport(new StandardWebSocketClient())); transports.add(new RestTemplateXhrTransport()); SockJsClient webSocketClient = new SockJsClient(transports); WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); List<MessageConverter> converters = new ArrayList<>(); converters.add(new ByteArrayMessageConverter()); // 处理二进制负载 converters.add(new StringMessageConverter()); // 处理文本负载 converters.add(new MappingJackson2MessageConverter()); // 处理JSON负载 stompClient.setMessageConverter(new CompositeMessageConverter(converters)); // stompClient.setMessageConverter(new StringMessageConverter()); // String url1 = "/api/vms/{vmsId}/ws/status"; String vmsId = "vms-8ff225f900a44a90a937143fe6774a98"; String url = String.format("wss://127.0.0.1:8081/api/vms/%s/ws/status", vmsId); // String url = "wss://127.0.0.1:8081/api/vms/vms-8ff225f900a44a90a937143fe6774a98/ws/status/230/xgydnmu0/websocket"; // 添加头,握手的那些http,比如cookie final WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); headers.set("Cookie", "JSESSIONID=1990891CA73E17ECE4DEC572A6C98990; VMS_SID=v-ec60c98c-aa89-48b6-bed2-f6f016c10e71"); headers.set("SourceType", "vms_web"); headers.set("Content-Type", "application/json;charset=UTF-8"); // 头部字段 final StompHeaders connectHeaders = new StompHeaders(); connectHeaders.set("CSRF-TOKEN", "5d1cd317b2374b84922576e47c0222bf"); connectHeaders.set("accept-version", "1.1,1.0"); // connectHeaders.set("heart-beat", "20000,20000"); // 链接到服务端 stompClient.connect(url, headers, connectHeaders, new StompSessionHandlerAdapter() { @Override public void afterConnected(StompSession session, StompHeaders connectedHeaders) { System.out.println("************connected"); // 订阅用户消息,"/topic/vms/{vmsId}/users/{userId}/device-add-progress/{uuid}" String subscribeUrl = "/topic/vms/{vmsId}/users/{userId}/device-add-progress/{uuid}"; session.subscribe(subscribeUrl, new StompFrameHandler() { @Override public Type getPayloadType(StompHeaders headers) { return String.class; } @Override public void handleFrame(StompHeaders headers, Object payload) { if (payload instanceof byte[]) { String payloadStr = new String((byte[]) payload, StandardCharsets.UTF_8); System.out.println("Received message: " + payloadStr); } else if (payload instanceof String) { System.out.println("Received message: " + payload); } else { // 其他类型(如JSON映射的对象) System.out.println("Received message: " + payload.toString()); } System.out.println("****************收到订阅的 /user/exchange/amq.direct/trade 消息:" + payload); } }); // 发送更新消息 // session.send("/app/trade", "更新消息"); } @Override public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { System.err.println("STOMP Exception: " + exception.getMessage()); } @Override public void handleTransportError(StompSession session, Throwable exception) { System.err.println("Transport Error: " + exception.getMessage()); } }); TimeUnit.SECONDS.sleep(1000); } }
最新发布
10-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值