ActiveMQ源码解析(二):聊聊客户端和broker的通讯

ActiveMQ支持以下几种通讯协议:

协议   备注
HTTP/HTTPS基于http协议
TCP默认协议
UDP 性能更好,但不可靠
SSL 安全链接
NIO基于tcp,使用异步非阻塞方式使性能得到提升,具有更好的扩展性
VM如果客户端和代理运行在同一个vm中就直接通讯不占用网络带宽

     对SSL的支持主要是在https包中,它主要扩展了keberos认证协议。tcp包和udp包与http类似类似提供了对响应的协议的支持

HTTP

主要的代码在http包中,它依赖于apache httpclient,首先来看看客户端类HttpClientTransport,主要介绍它的三个方法,oneway()、run()和start().

oneway:简单的一次http post请求传输数据,这里顺带科普一下,activemq中connector负责broker与broker之间的通讯,transport负责broker和客户端之间的通讯。

public void oneway(Object command) throws IOException {
        //如果已经关闭抛出异常
        if (isStopped()) {
            throw new IOException("stopped.");
        }
        //post请求
        HttpPost httpMethod = new HttpPost(getRemoteUrl().toString());
        //注册请求头
        configureMethod(httpMethod);
        //格式化需要传递的数据
        String data = getTextWireFormat().marshalText(command);
       //utf8解码
        byte[] bytes = data.getBytes("UTF-8");
        //如果允许压缩数据则压缩数据传送
        if (useCompression && canSendCompressed && bytes.length > minSendAsCompressedSize) {
            //输出流
            ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
            //gzip输出流
            GZIPOutputStream stream = new GZIPOutputStream(bytesOut);
            //向流中写入数据
            stream.write(bytes);
            stream.close();
            //添加头部信息
            httpMethod.addHeader("Content-Type", "application/x-gzip");
            //打印日志
            if (LOG.isTraceEnabled()) {
                LOG.trace("Sending compressed, size = " + bytes.length + ", compressed size = " + bytesOut.size());
            }
            bytes = bytesOut.toByteArray();
        }
        ByteArrayEntity entity = new ByteArrayEntity(bytes);
        httpMethod.setEntity(entity);

        HttpClient client = null;
        HttpResponse answer = null;
        try {
            //创建客户端
            client = getSendHttpClient();
            HttpParams params = client.getParams();
            HttpConnectionParams.setSoTimeout(params, soTimeout);
            answer = client.execute(httpMethod);
            int status = answer.getStatusLine().getStatusCode();
            if (status != HttpStatus.SC_OK) {
                throw new IOException("Failed to post command: " + command + " as response was: " + answer);
            }
            if (command instanceof ShutdownInfo) {
                try {
                    stop();
                } catch (Exception e) {
                    LOG.warn("Error trying to stop HTTP client: "+ e, e);
                }
            }
        } catch (IOException e) {
            throw IOExceptionSupport.create("Could not post command: " + command + " due to: " + e, e);
        } finally {
            if (answer != null) {
                EntityUtils.consume(answer.getEntity());
            }
        }
    }


run方法:不断发送http get请求访问remoteurl,如果连接不上则抛异常,如果连接上则使用注册的listener消费消息

public void run() {
       //打印日志
        if (LOG.isTraceEnabled()) {
            LOG.trace("HTTP GET consumer thread starting: " + this);
        }
        //客户端
        HttpClient httpClient = getReceiveHttpClient();
        URI remoteUrl = getRemoteUrl();

        while (!isStopped() && !isStopping()) {

            httpMethod = new HttpGet(remoteUrl.toString());
            configureMethod(httpMethod);
            HttpResponse answer = null;

            try {
                answer = httpClient.execute(httpMethod);
                int status = answer.getStatusLine().getStatusCode();
                if (status != HttpStatus.SC_OK) {
                    if (status == HttpStatus.SC_REQUEST_TIMEOUT) {
                        LOG.debug("GET timed out");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            onException(new InterruptedIOException());
                            Thread.currentThread().interrupt();
                            break;
                        }
                    } else {
                        onException(new IOException("Failed to perform GET on: " + remoteUrl + " as response was: " + answer));
                        break;
                    }
                } else {
                    receiveCounter++;
                    DataInputStream stream = createDataInputStream(answer);
                    Object command = getTextWireFormat().unmarshal(stream);
                    if (command == null) {
                        LOG.debug("Received null command from url: " + remoteUrl);
                    } else {
                        //消费消息
                        doConsume(command);
                    }
                    stream.close();
                }
            } catch (IOException e) {
                onException(IOExceptionSupport.create("Failed to perform GET on: " + remoteUrl + " Reason: " + e.getMessage(), e));
                break;
            } finally {
                if (answer != null) {
                    try {
                        EntityUtils.consume(answer.getEntity());
                    } catch (IOException e) {
                    }
                }
            }
        }
    }

start方法:发送一个http head检查broker是否能连通和一个http options请求确认是否支持压缩传输

protected void doStart() throws Exception {

        if (LOG.isTraceEnabled()) {
            LOG.trace("HTTP GET consumer thread starting: " + this);
        }
        HttpClient httpClient = getReceiveHttpClient();
        URI remoteUrl = getRemoteUrl();

        HttpHead httpMethod = new HttpHead(remoteUrl.toString());
        configureMethod(httpMethod);

        // Request the options from the server so we can find out if the broker we are
        // talking to supports GZip compressed content.  If so and useCompression is on
        // then we can compress our POST data, otherwise we must send it uncompressed to
        // ensure backwards compatibility.
        HttpOptions optionsMethod = new HttpOptions(remoteUrl.toString());
        ResponseHandler<String> handler = new BasicResponseHandler() {
            @Override
            public String handleResponse(HttpResponse response) throws HttpResponseException, IOException {

                for(Header header : response.getAllHeaders()) {
                    if (header.getName().equals("Accepts-Encoding") && header.getValue().contains("gzip")) {
                        LOG.info("Broker Servlet supports GZip compression.");
                        canSendCompressed = true;
                        break;
                    }
                }

                return super.handleResponse(response);
            }
        };

        try {
            httpClient.execute(httpMethod, new BasicResponseHandler());
            httpClient.execute(optionsMethod, handler);
        } catch(Exception e) {
            throw new IOException("Failed to perform GET on: " + remoteUrl + " as response was: " + e.getMessage());
        }

        super.doStart();
    }


    前面说了客户端下面来谈谈服务器端即broker。它值得分析的方法只有start()。

start

protected void doStart() throws Exception {
        //创建jetty服务器端
        createServer();
        //如果连接器不存在则创建连接器,工厂方法
        if (connector == null) {
            connector = socketConnectorFactory.createConnector(server);
        }

        URI boundTo = bind();
        
        ServletContextHandler contextHandler =
            new ServletContextHandler(server, "/", ServletContextHandler.SECURITY);

        ServletHolder holder = new ServletHolder();
        //后面会介绍HttpTunnelServlet
        holder.setServlet(new HttpTunnelServlet());
        contextHandler.addServlet(holder, "/");

        contextHandler.setAttribute("acceptListener", getAcceptListener());
        contextHandler.setAttribute("wireFormat", getWireFormat());
        contextHandler.setAttribute("transportFactory", transportFactory);
        contextHandler.setAttribute("transportOptions", transportOptions);

        //AMQ-6182 - disabling trace by default
        configureTraceMethod((ConstraintSecurityHandler) contextHandler.getSecurityHandler(),
                httpOptions.isEnableTrace());

        addGzipHandler(contextHandler);

        server.start();

        // Update the Connect To URI with our actual location in case the configured port
        // was set to zero so that we report the actual port we are listening on.

        int port = boundTo.getPort();
        int p2 = getConnectorLocalPort();
        if (p2 != -1) {
            port = p2;
        }

        setConnectURI(new URI(boundTo.getScheme(),
                              boundTo.getUserInfo(),
                              boundTo.getHost(),
                              port,
                              boundTo.getPath(),
                              boundTo.getQuery(),
                              boundTo.getFragment()));
    }

     服务器端和客户端都讲完了,http包中还剩下一个HttpSpringEmbededTunnelServlet,它的父类是HttpEmbededTunnelServlet,再往上是HttpTunnelServlet。接下来从HttpTunnelServlet开始,依次讲讲doGet()方法、doPost()方法。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // lets return the next response
        Command packet = null;
        int count = 0;
        try {
            //阻塞式的传输通道
            BlockingQueueTransport transportChannel = getTransportChannel(request, response);
            if (transportChannel == null) {
                return;
            }
            //从阻塞队列中取出数据
            packet = (Command)transportChannel.getQueue().poll(requestTimeout, TimeUnit.MILLISECONDS);
            //获取输出流
            DataOutputStream stream = new DataOutputStream(response.getOutputStream());
            wireFormat.marshal(packet, stream);
            count++;
        } catch (InterruptedException ignore) {
        }

        if (count == 0) {
            response.setStatus(HttpServletResponse.SC_REQUEST_TIMEOUT);
        }
    }

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        InputStream stream = request.getInputStream();
        String contentType = request.getContentType();
        if (contentType != null && contentType.equals("application/x-gzip")) {
            stream = new GZIPInputStream(stream);
        }

        // Read the command directly from the reader, assuming UTF8 encoding
        Command command = (Command) wireFormat.unmarshalText(new InputStreamReader(stream, "UTF-8"));

        if (command instanceof WireFormatInfo) {
            WireFormatInfo info = (WireFormatInfo) command;
            if (!canProcessWireFormatVersion(info.getVersion())) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot process wire format of version: "
                        + info.getVersion());
            }

        } else {
            //根据clientId从transport容器中获取transport,clientId是在创建HtppClientTransport时Gennerator生成的
            BlockingQueueTransport transport = getTransportChannel(request, response);
            if (transport == null) {
                return;
            }
            
            if (command instanceof ConnectionInfo) {
                ((ConnectionInfo) command).setTransportContext(request.getAttribute("javax.servlet.request.X509Certificate"));
            }
            transport.doConsume(command);
        }
    }

小结

    transport中的http包中包含了客户端、服务器端。这里创建的server其实是一个jetty服务器,绑定80端口,它有个HttpTunnelServlet,消息存放在其中的BlockingQueueTransport中,doGet用于从服务端获取消息,doPost用于通知响应的listener消费消息。

VM

    主要的类是VMTransportServer,它创建了一个VMTransport做为客户端,另一个作为服务器端,当Server启动时会创建客户端,然后和server注册连接在一起相互通讯,它主要的作用就是用在broker和客户端都在一个vm中的情况,可以减少网络开销提高效率

总结

    transport包提供了对客户端和broker的多种通讯方式的支持,具体使用哪种通讯协议由用户在配置文件中在transportconnector节点配置。






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值