Files
IM-server-new/broker/src/main/java/io/moquette/server/Server.java
T

522 lines
21 KiB
Java
Executable File

/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server;
import cn.wildfirechat.push.PushServer;
import cn.wildfirechat.server.ThreadPoolExecutorWrapper;
import com.hazelcast.config.ClasspathXmlConfig;
import com.hazelcast.config.Config;
import com.hazelcast.config.FileSystemXmlConfig;
import com.hazelcast.core.*;
import com.xiaoleilu.loServer.LoServer;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.admin.AdminAction;
import io.moquette.BrokerConstants;
import io.moquette.persistence.*;
import io.moquette.connections.IConnectionsManager;
import io.moquette.interception.*;
import io.moquette.server.config.*;
import io.moquette.server.netty.NettyAcceptor;
import io.moquette.spi.IStore;
import io.moquette.spi.impl.ProtocolProcessor;
import io.moquette.spi.impl.ProtocolProcessorBootstrapper;
import io.moquette.spi.impl.security.AES;
import io.moquette.spi.impl.subscriptions.Token;
import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.ISslContextCreator;
import io.moquette.spi.security.Tokenor;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.DBUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.MessageShardingUtil;
import win.liyufan.im.Utility;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import static io.moquette.BrokerConstants.*;
import static io.moquette.logging.LoggingUtils.getInterceptorIds;
/**
* Launch a configured version of the server.
*/
public class Server {
private final static String BANNER =
" _ _ _ __ _ _ _ \n" +
" __ __(_)| | __| | / _|(_) _ __ ___ ___ | |__ __ _ | |_ \n" +
" \\ \\ /\\ / /| || | / _` || |_ | || '__|/ _ \\ / __|| '_ \\ / _` || __|\n" +
" \\ V V / | || || (_| || _|| || | | __/ | (__ | | | || (_| || |_ \n" +
" \\_/\\_/ |_||_| \\__,_||_| |_||_| \\___| \\___||_| |_| \\__,_| \\__|\n";
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private static Server instance;
public static Server getServer() {
return instance;
}
private ServerAcceptor m_acceptor;
private boolean m_shutdowning = false;
public volatile boolean m_initialized;
private ProtocolProcessor m_processor;
private HazelcastInstance hazelcastInstance;
private ProtocolProcessorBootstrapper m_processorBootstrapper;
private ThreadPoolExecutorWrapper dbScheduler;
private ThreadPoolExecutorWrapper imBusinessScheduler;
private ThreadPoolExecutorWrapper callbackScheduler;
private IConfig mConfig;
private IStore m_store;
static {
System.out.println(BANNER);
}
public static void start(String[] args) throws IOException {
instance = new Server();
final IConfig config = defaultConfig();
System.setProperty("hazelcast.logging.type", "none" );
instance.mConfig = config;
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
instance.startServer(config);
int httpLocalPort = Integer.parseInt(config.getProperty(BrokerConstants.HTTP_LOCAL_PORT));
int httpAdminPort = Integer.parseInt(config.getProperty(BrokerConstants.HTTP_ADMIN_PORT));
AdminAction.setSecretKey(config.getProperty(HTTP_SERVER_SECRET_KEY));
AdminAction.setNoCheckTime(config.getProperty(HTTP_SERVER_API_NO_CHECK_TIME));
final LoServer httpServer = new LoServer(httpLocalPort, httpAdminPort, instance.m_processor.getMessagesStore(), instance.m_store.sessionsStore());
try {
httpServer.start();
} catch (InterruptedException e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
}
final PushServer pushServer = PushServer.getServer();
pushServer.init(config, instance.getStore().sessionsStore());
//Bind a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(instance::stopServer));
Runtime.getRuntime().addShutdownHook(new Thread(httpServer::shutdown));
System.out.println("Wildfire IM server start success...");
}
/**
* Starts Moquette bringing the configuration from the file located at m_config/wildfirechat.conf
*
* @throws IOException
* in case of any IO error.
*/
public void startServer() throws IOException {
final IConfig config = defaultConfig();
startServer(config);
}
public static IConfig defaultConfig() {
File defaultConfigurationFile = defaultConfigFile();
LOG.info("Starting Moquette server. Configuration file path={}", defaultConfigurationFile.getAbsolutePath());
IResourceLoader filesystemLoader = new FileResourceLoader(defaultConfigurationFile);
return new ResourceLoaderConfig(filesystemLoader);
}
private static File defaultConfigFile() {
String configPath = System.getProperty("wildfirechat.path", null);
return new File(configPath, IConfig.DEFAULT_CONFIG);
}
/**
* Starts Moquette bringing the configuration from the given file
*
* @param configFile
* text file that contains the configuration.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(File configFile) throws IOException {
LOG.info("Starting Moquette server. Configuration file path={}", configFile.getAbsolutePath());
IResourceLoader filesystemLoader = new FileResourceLoader(configFile);
final IConfig config = new ResourceLoaderConfig(filesystemLoader);
startServer(config);
}
/**
* Starts the server with the given properties.
*
*
* @param configProps
* the properties map to use as configuration.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(Properties configProps) throws IOException {
LOG.info("Starting Moquette server using properties object");
final IConfig config = new MemoryConfig(configProps);
startServer(config);
}
/**
* Starts Moquette bringing the configuration files from the given Config implementation.
*
* @param config
* the configuration to use to start the broker.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(IConfig config) throws IOException {
LOG.info("Starting Moquette server using IConfig instance...");
startServer(config, null);
}
/**
* Starts Moquette with config provided by an implementation of IConfig class and with the set
* of InterceptHandler.
*
* @param config
* the configuration to use to start the broker.
* @param handlers
* the handlers to install in the broker.
* @throws IOException
* in case of any IO Error.
*/
public void startServer(IConfig config, List<? extends InterceptHandler> handlers) throws IOException {
LOG.info("Starting moquette server using IConfig instance and intercept handlers");
startServer(config, handlers, null, null, null);
}
public void startServer(IConfig config, List<? extends InterceptHandler> handlers, ISslContextCreator sslCtxCreator,
IAuthenticator authenticator, IAuthorizator authorizator) throws IOException {
if (handlers == null) {
handlers = Collections.emptyList();
}
DBUtil.init(config);
String strKey = "0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F";
String[] strs = strKey.split(",");
if(strs.length != 16) {
LOG.error("Key error, it length should be 16");
}
byte[] keys = new byte[16];
for (int i = 0; i < 16; i++) {
keys[i] = (byte)(Integer.parseInt(strs[i].replace("0X", "").replace("0x", ""), 16));
}
AES.init(keys);
LOG.info("Starting Moquette Server. MQTT message interceptors={}", getInterceptorIds(handlers));
int threadNum = Runtime.getRuntime().availableProcessors() * 2;
dbScheduler = new ThreadPoolExecutorWrapper(Executors.newScheduledThreadPool(threadNum), threadNum, "db");
imBusinessScheduler = new ThreadPoolExecutorWrapper(Executors.newScheduledThreadPool(threadNum), threadNum, "business");
callbackScheduler = new ThreadPoolExecutorWrapper(Executors.newScheduledThreadPool(1), 1, "callback");
final String handlerProp = System.getProperty(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME);
if (handlerProp != null) {
config.setProperty(BrokerConstants.INTERCEPT_HANDLER_PROPERTY_NAME, handlerProp);
}
initMediaServerConfig(config);
String monitorEventAddress = config.getProperty(MONITOR_Exception_Event_Address);
Utility.setMonitorEventAddress(monitorEventAddress);
final String persistencePath = config.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME);
LOG.info("Configuring Using persistent store file, path={}", persistencePath);
m_store = initStore(config, this);
m_processorBootstrapper = new ProtocolProcessorBootstrapper();
boolean configured = configureCluster(config);
m_store.initStore();
final ProtocolProcessor processor = m_processorBootstrapper.init(config, handlers, authenticator, authorizator,
this, m_store);
LOG.info("Initialized MQTT protocol processor");
if (sslCtxCreator == null) {
LOG.warn("Using default SSL context creator");
sslCtxCreator = new DefaultMoquetteSslContextCreator(config);
}
m_processor = processor;
LOG.info("Binding server to the configured ports");
m_acceptor = new NettyAcceptor();
m_acceptor.initialize(processor, config, sslCtxCreator);
LOG.info("Moquette server has been initialized successfully");
m_initialized = configured;
}
private IStore initStore(IConfig props, Server server) {
LOG.info("Initializing messages and sessions stores...");
IStore store = instantiateConfiguredStore(props, server.getDbScheduler(), server);
if (store == null) {
throw new IllegalArgumentException("Can't start the persistence layer");
}
return store;
}
private IStore instantiateConfiguredStore(IConfig props,
ThreadPoolExecutorWrapper scheduledExecutor, Server server) {
return new MemoryStorageService(props, scheduledExecutor, server);
}
public IStore getStore() {
return m_store;
}
private void initMediaServerConfig(IConfig config) {
MediaServerConfig.QINIU_ACCESS_KEY = config.getProperty(BrokerConstants.QINIU_ACCESS_KEY, MediaServerConfig.QINIU_ACCESS_KEY);
MediaServerConfig.QINIU_SECRET_KEY = config.getProperty(BrokerConstants.QINIU_SECRET_KEY, MediaServerConfig.QINIU_SECRET_KEY);
MediaServerConfig.QINIU_SERVER_URL = config.getProperty(BrokerConstants.QINIU_SERVER_URL, MediaServerConfig.QINIU_SERVER_URL);
if (MediaServerConfig.QINIU_SERVER_URL.contains("//")) {
MediaServerConfig.QINIU_SERVER_URL = MediaServerConfig.QINIU_SERVER_URL.substring(MediaServerConfig.QINIU_SERVER_URL.indexOf("//") + 2);
}
MediaServerConfig.QINIU_BUCKET_GENERAL_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_GENERAL_NAME);
MediaServerConfig.QINIU_BUCKET_GENERAL_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_GENERAL_DOMAIN);
MediaServerConfig.QINIU_BUCKET_IMAGE_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_IMAGE_NAME);
MediaServerConfig.QINIU_BUCKET_IMAGE_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_IMAGE_DOMAIN);
MediaServerConfig.QINIU_BUCKET_VOICE_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_VOICE_NAME);
MediaServerConfig.QINIU_BUCKET_VOICE_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_VOICE_DOMAIN);
MediaServerConfig.QINIU_BUCKET_VIDEO_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_VIDEO_NAME);
MediaServerConfig.QINIU_BUCKET_VIDEO_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_VIDEO_DOMAIN);
MediaServerConfig.QINIU_BUCKET_FILE_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_FILE_NAME);
MediaServerConfig.QINIU_BUCKET_FILE_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_FILE_DOMAIN);
MediaServerConfig.QINIU_BUCKET_STICKER_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_STICKER_NAME);
MediaServerConfig.QINIU_BUCKET_STICKER_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_STICKER_DOMAIN);
MediaServerConfig.QINIU_BUCKET_MOMENTS_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_MOMENTS_NAME);
MediaServerConfig.QINIU_BUCKET_MOMENTS_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_MOMENTS_DOMAIN);
MediaServerConfig.QINIU_BUCKET_PORTRAIT_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_PORTRAIT_NAME);
MediaServerConfig.QINIU_BUCKET_PORTRAIT_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_PORTRAIT_DOMAIN);
MediaServerConfig.QINIU_BUCKET_FAVORITE_NAME = config.getProperty(BrokerConstants.QINIU_BUCKET_FAVORITE_NAME);
MediaServerConfig.QINIU_BUCKET_FAVORITE_DOMAIN = config.getProperty(BrokerConstants.QINIU_BUCKET_FAVORITE_DOMAIN);
MediaServerConfig.SERVER_IP = getServerIp(config);
MediaServerConfig.HTTP_SERVER_PORT = Integer.parseInt(config.getProperty(BrokerConstants.HTTP_SERVER_PORT));
MediaServerConfig.FILE_STROAGE_ROOT = config.getProperty(BrokerConstants.FILE_STORAGE_ROOT, MediaServerConfig.FILE_STROAGE_ROOT);
File file = new File(MediaServerConfig.FILE_STROAGE_ROOT);
if (!file.exists()) {
file.mkdirs();
}
ServerSetting.setRoot(file);
MediaServerConfig.USER_QINIU = Integer.parseInt(config.getProperty(BrokerConstants.USER_QINIU)) > 0;
}
private String getServerIp(IConfig config) {
String serverIp = config.getProperty(BrokerConstants.SERVER_IP_PROPERTY_NAME);
if (serverIp == null || serverIp.equals("0.0.0.0")) {
serverIp = Utility.getLocalAddress().getHostAddress();
}
return serverIp;
}
private boolean configureCluster(IConfig config) throws FileNotFoundException {
LOG.info("Configuring embedded Hazelcast instance");
String serverIp = getServerIp(config);
String hzConfigPath = config.getProperty(BrokerConstants.HAZELCAST_CONFIGURATION);
String hzClientIp = config.getProperty(BrokerConstants.HAZELCAST_CLIENT_IP, "localhost");
String hzClientPort = config.getProperty(BrokerConstants.HAZELCAST_CLIENT_PORT, "5703");
if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath
? new ClasspathXmlConfig(hzConfigPath)
: new FileSystemXmlConfig(hzConfigPath);
LOG.info("Starting Hazelcast instance. ConfigurationFile={}", hzconfig);
hazelcastInstance = Hazelcast.newHazelcastInstance(hzconfig);
} else {
LOG.info("Starting Hazelcast instance with default configuration");
hazelcastInstance = Hazelcast.newHazelcastInstance();
}
String longPort = config.getProperty(BrokerConstants.PORT_PROPERTY_NAME);
String shortPort = config.getProperty(BrokerConstants.HTTP_SERVER_PORT);
String nodeIdStr = config.getProperty(BrokerConstants.NODE_ID);
ISet<Integer> nodeIdSet = hazelcastInstance.getSet(BrokerConstants.NODE_IDS);
int nodeId;
try {
nodeId = Integer.parseInt(nodeIdStr);
}catch (Exception e){
throw new IllegalArgumentException("nodeId error: " + nodeIdStr);
}
if (nodeIdSet != null && nodeIdSet.contains(nodeId)){
LOG.error("只允许一个实例运行,多个实例会引起冲突,进程终止");
System.exit(-1);
}
MessageShardingUtil.setNodeId(nodeId);
nodeIdSet.add(nodeId);
hazelcastInstance.getCluster().getLocalMember().setStringAttribute(HZ_Cluster_Node_External_Long_Port, longPort);
hazelcastInstance.getCluster().getLocalMember().setStringAttribute(HZ_Cluster_Node_External_Short_Port, shortPort);
hazelcastInstance.getCluster().getLocalMember().setIntAttribute(HZ_Cluster_Node_ID, nodeId);
hazelcastInstance.getCluster().getLocalMember().setStringAttribute(HZ_Cluster_Node_External_IP, serverIp);
Tokenor.setKey(config.getProperty(BrokerConstants.TOKEN_SECRET_KEY));
String expirTimeStr = config.getProperty(TOKEN_EXPIRE_TIME);
if (!StringUtil.isNullOrEmpty(expirTimeStr)) {
try {
Tokenor.setExpiredTime(Long.parseLong(expirTimeStr));
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
RPCCenter.getInstance().init(this);
return true;
}
public HazelcastInstance getHazelcastInstance() {
return hazelcastInstance;
}
public void internalRpcMsg(String fromUser, String clientId, byte[] message, int messageId, String from, String request, boolean isAdmin) {
if (!m_initialized) {
LOG.error("Moquette is not started, internal message cannot be notify");
return;
}
LOG.debug("internalNotifyMsg");
m_processor.onRpcMsg(fromUser, clientId, message, messageId, from, request, isAdmin);
}
public boolean isShutdowning() {
return m_shutdowning;
}
public void stopServer() {
System.out.println("Server will flush data to db before shutting down, please wait 5 seconds!");
LOG.info("Unbinding server from the configured ports");
m_shutdowning = true;
DBUtil.SystemExiting = true;
m_acceptor.close();
LOG.trace("Stopping MQTT protocol processor");
m_processorBootstrapper.shutdown();
m_initialized = false;
if (hazelcastInstance != null) {
LOG.trace("Stopping embedded Hazelcast instance");
try {
hazelcastInstance.shutdown();
} catch (HazelcastInstanceNotActiveException e) {
LOG.warn("embedded Hazelcast instance is already shut down.");
}
}
dbScheduler.shutdown();
imBusinessScheduler.shutdown();
callbackScheduler.shutdown();
LOG.info("Moquette server has been stopped.");
}
/**
* SPI method used by Broker embedded applications to add intercept handlers.
*
* @param interceptHandler
* the handler to add.
*/
public void addInterceptHandler(InterceptHandler interceptHandler) {
if (!m_initialized) {
LOG.error("Moquette is not started, MQTT message interceptor cannot be added. InterceptorId={}",
interceptHandler.getID());
throw new IllegalStateException("Can't register interceptors on a server that is not yet started");
}
LOG.info("Adding MQTT message interceptor. InterceptorId={}", interceptHandler.getID());
m_processor.addInterceptHandler(interceptHandler);
}
/**
* SPI method used by Broker embedded applications to remove intercept handlers.
*
* @param interceptHandler
* the handler to remove.
*/
public void removeInterceptHandler(InterceptHandler interceptHandler) {
if (!m_initialized) {
LOG.error("Moquette is not started, MQTT message interceptor cannot be removed. InterceptorId={}",
interceptHandler.getID());
throw new IllegalStateException("Can't deregister interceptors from a server that is not yet started");
}
LOG.info("Removing MQTT message interceptor. InterceptorId={}", interceptHandler.getID());
m_processor.removeInterceptHandler(interceptHandler);
}
public IConfig getConfig() {
return mConfig;
}
/**
* Returns the connections manager of this broker.
*
* @return IConnectionsManager the instance used bt the broker.
*/
public IConnectionsManager getConnectionsManager() {
return m_processorBootstrapper.getConnectionDescriptors();
}
public ProtocolProcessor getProcessor() {
return m_processor;
}
public ThreadPoolExecutorWrapper getDbScheduler() {
return dbScheduler;
}
public ThreadPoolExecutorWrapper getImBusinessScheduler() {
return imBusinessScheduler;
}
public ThreadPoolExecutorWrapper getCallbackScheduler() {
return callbackScheduler;
}
}