消息传输协议分析
传输协议启动分析
在activemq中,有很多地方用到网络传输。比如说,消息的收发,其他broker的发现以及broker中的交互等。这里,我们重点分析下消息的传输。集群中的网络传输将在集群部分进行分析。
要分析消息传输协议,先要从网络连接适配器的启动开始分析。
我们来看 brokerService中的startAllConnectors。这里
这里通过spring的ioc将配置文件中的transportConnectors配置的传输连接器都注入到了BrokerService中。
下面方法就是通过 getTransportConnectors方法获得了配置中所有的连接器。
在connector启动过程中,需要注册一个连接监听器。
getServer().setAcceptListener(newTransportAcceptListener() {
/**
* 接收一个连接请求时候的处理
* @paramtransport
*/
@Override
public voidonAccept(final Transport transport) {
try {
brokerService.getTaskRunnerFactory().execute(newRunnable() {
@Override
public voidrun() {
try {
//构建一个连接,并开启其生命周期
Connectionconnection = createConnection(transport);
connection.start();
} catch(Exception e) {
StringremoteHost = transport.getRemoteAddress();
ServiceSupport.dispose(transport);
onAcceptError(e,remoteHost);
}
}
});
} catch(Exception e) {
String remoteHost =transport.getRemoteAddress();
ServiceSupport.dispose(transport);
onAcceptError(e,remoteHost);
}
}
也就是说每次有连接进来,就创建一个connection对象,并且开启其生命周期。
Connectionconnection = createConnection(transport);
connection.start();
TransportConnector, TransportServer ,TransportConnection, Transport关系
TransportConnector
连接器。我们在activemq配置文件中配置的多个连接地址,每一个连接地址生成一个连接器。
TransportServer
连接服务类,是connector的一个属性,也是连接核心类。根据URI地址生成不同的transportServer,该类持有serverSocket,从而可以创建服务器套接字,从而可以接受客户端的连接请求,根据不同的URI,将客户端请求生成套接字并封装成不同的transport。
Transport继承关系如下:传输层基本还是使用tcp协议。应用层有多种实现。
构建
我们下面就以最常用的tcptransportServer进行分析如何构建一个transportServer的。我们看TcpTransportFactory 中的doBind方法。构建了一个ServerSocketFactory,并且传递到TransportServer中。所以,server肯定是有创建服务器监听的能力。
server.setWireFormatFactory(createWireFormatFactory(options));并且,下面设置了一个formatFactory,明显是用于各个协议之间的相互转换的。所以,server是传输中非常核心的一个类。
Map<String, String> options = newHashMap<String, String>(URISupport.parseParameters(location));
//创建一个默认的套接字工厂
ServerSocketFactoryserverSocketFactory = createServerSocketFactory();
TcpTransportServer server= createTcpTransportServer(location, serverSocketFactory);
server.setWireFormatFactory(createWireFormatFactory(options));
IntrospectionSupport.setProperties(server, options);
Map<String, Object> transportOptions = IntrospectionSupport.extractProperties(options,"transport.");
server.setTransportOption(transportOptions);
server.bind();
启动
server启动后,执行下面方法:
socket = serverSocket.accept();
if (socket !=null) {
if (isStopped() || getAcceptListener() ==null) {
socket.close();
} else {
if (useQueueForAccept) {
socketQueue.put(socket);
} else {
handleSocket(socket);
}
}
}
其中 handleSocket方法中调用了在connector中注册到transportserver中的
getAcceptListener().onAccept(configuredTransport);
Transport
传输器。TransportServer获得socket套接字之后,将其封装成一个transport,该类主要用于接收客户端消息并发送给服务器后期处理以及将服务器接收到的消息发出去。
传输器有多种传输协议的实现。传输器就是通过配置文件中的URI地址,生成对应的transport并且注入到connection中。比如根据URI地址,amqp://0.0.0.0:5672生成AmqpTransportFilter。Transport继承关系如下:
TransportConnection
由于在transportserver中注册了监听器,如下:
所以,一旦有客户端连接进来,生成的transport将会回调监听器方法,生成一个connection,并且,connector持有所有的connection。
连接器连接启动处理
上面已经分析到了接收到服务器端接收客户端连接之后,根据socket生成一个transport。并且将该transport封装成一个connection。并且启动connection
Connection connection = createConnection(transport);
connection.start();
其实connection的start方法主要完成了一下几件事情
transport.start();
这里我们一tcpTransport进行分析,在dostart方法中调用了connect方法。通过以下方法初配置socket以及将socket包装。
initialiseSocket(socket);
initializeStreams();
dispatchAsync(info);
该方法主要是讲brokerInfo发送到客户端。一旦有客户端连接服务器端,则服务器端将服务器信息发送给客户端,那么客户端就能够获得服务器端的信息。这个应该主要是在服务器集群的时候使用。
connector.onStarted(this);
这个比较简单,就是将生成的connection 放到connector中的list中。
线程的启动
因为TcpTransport 是一个实现了runable接口的类,所以,可以将其理解成一个线程类。
所以,在transport启动的过程中,有这样一行代码:
super.doStart();
这里将会调用其父类的dostart方法,也就是TransportThreadSupport的dostart方法
runner = newThread(null,this, "ActiveMQTransport: " + toString(), stackSize);
runner.setDaemon(daemon);
runner.start();
这里呢,因为是runable实现类
所以,线程一旦start,就是择机执行run函数了实质上也就是执行 dorun函数。
这里我们能看到,其实就是读取命令以及命令消费
Object command = readCommand();
doConsume(command);
读取消息其实就是通过wireFormat来进行消息的解析。
进行消息消费就是通过transportListener 的onCommand方法进行消息处理。
transportListener.onCommand(command);
消息的处理
消息的处理主要是在TransportConnection 中的构造函数中,将其属性transport中注入了新构建的实现了DefaultTransportListener 接口的实例。该实例中的onCommand中对命令进行处理。我们来看下该方法:
public voidonCommand(Object o) {
serviceLock.readLock().lock();
try {
if (!(oinstanceofCommand)) {
throw newRuntimeException("Protocol violation - Command corrupted:" + o.toString());
}
Command command = (Command) o;
Response response = service(command);
if (response !=null&& !brokerService.isStopping() ) {
dispatchSync(response);
}
} finally {
serviceLock.readLock().unlock();
}
}
方法刚开始执行的时候,使用了一个JUC中的ReentrantReadWriteLock 重入读写锁来进行锁定。
然后通过下面方法进行处理
Responseresponse = service(command);
if (response != null&& !brokerService.isStopping() ) {
dispatchSync(response);
}
当我们连接服务器的时候,Command命令我们接受到的是:
ConnectionInfo {commandId = 0,responseRequired = true, connectionId =ID:Daemon-PC-52193-1415239207249-2:1, clientId = 1001, clientIp =null,userName = null, password = *****, brokerPath =null, brokerMasterConnector= false, manageable = false,clientMaster =true, faultTolerant = false,failoverReconnect = false}