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

来源:互联网 发布:淘宝我的爱车 编辑:程序博客网 时间:2024/05/16 10:29

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节点配置。






0 0
原创粉丝点击