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());
}
}
}
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();
}
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节点配置。