Mqtt 应用
物联网和手机app通信等。介绍 略
基于Netty实现 mqtt
Mqtt 有发布订阅。怎么通过netty 实现?
Netty 怎么捕获连接事件。
基于Netty channel Session管理?
Topic 和 channel之间的关系
- 消息 Qos 等级
- 怎么集群。
简单的Mqtt 看实现
public static void main(String[] args) {
MqttTsServer server = new MqttTsServer();
server.run();
}
public void run(){
// 监听端口号
int port = 1883;
// 构建主线程-用于分发socket请求
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
// 构建工作线程-用于处理请求处理
EventLoopGroup workGroup = new NioEventLoopGroup(4);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
// .childOption(ChannelOption.SO_BACKLOG,1024) //等待队列
.childOption(ChannelOption.SO_REUSEADDR,true) //快速复用
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 这个地方注意,如果客户端发送请求体超过此设置值,会抛异常
socketChannel.pipeline().addLast(new MqttDecoder(1024*1024));
//增加Mqtt的 Decoder and Encoder
socketChannel.pipeline().addLast( MqttEncoder.INSTANCE);
// 加载MQTT编解码协议,包含业务逻辑对象
socketChannel.pipeline().addLast(new TestMqttHandler());
}
});
//启动服务
serverBootstrap.bind(port).addListener(future -> {
log.info("服务端成功绑定端口号={}",port);
});
}catch (Exception e){
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
log.error("mqttServer启动失败:{}",e);
}
}
启动类看,初始化channel 增加了MQTT的协议。
注意增加了一个TestMqttHandler() 重要。所有的Mqtt处理都是通过这个Handler
Handler处理类分析
@Slf4j
@ChannelHandler.Sharable
public class TestMqttHandler extends ChannelInboundHandlerAdapter {
private static final Collection<Channel> clientList = new HashSet();
private static final Map<String,Object> msgMap = new HashMap<>();
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
protocolProcess.disConnect().processDisConnect(ctx.channel(), null);
logger.info("------disconnection------非正常关闭的连接");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof MqttMessage) {
Channel channel = ctx.channel();
MqttMessage message = (MqttMessage) msg;
MqttMessageType messageType = message.fixedHeader().messageType();
log.info("MQTT接收到的发送类型===》{}",messageType);
switch (messageType) {
// 建立连接
case CONNECT:
try {
this.connect(channel, (MqttConnectMessage) message);
}catch (Exception e){
//如果用户密码,客户端ID校验不成功,会二次建立CONNECT类型连接
//但是没有实际意义
}
break;
// 发布消息
case PUBLISH:
this.publish(channel, (MqttPublishMessage) message);
break;
// 订阅主题
case SUBSCRIBE:
this.subscribe(channel, (MqttSubscribeMessage) message);
break;
// 退订主题
case UNSUBSCRIBE:
this.unSubscribe(channel, (MqttUnsubscribeMessage) message);
break;
// 心跳包
case PINGREQ:
this.pingReq(channel, message);
break;
// 断开连接
case DISCONNECT:
this.disConnect(channel, message);
break;
// 确认收到响应报文,用于服务器向客户端推送qos1/qos2后,客户端返回服务器的响应
case PUBACK:
this.puback(channel, message);
break;
// qos2类型,发布收到
case PUBREC:
this.pubrec(channel, message);
break;
// qos2类型,发布释放响应
case PUBREL:
this.pubrel(channel, message);
break;
// qos2类型,发布完成
case PUBCOMP:
this.pubcomp(channel, message);
break;
default:
if (log.isDebugEnabled()) {
log.debug("Nonsupport server message type of '{}'.", messageType);
}
break;
}
}
}
继承extends ChannelInboundHandlerAdapter 类 重写channelRead(ChannelHandlerContext ctx, Object msg)
通过判断 if (msg instanceof MqttMessage) { 。。。switch (messageType) { 分析消息事件
建立连接事件,需要将channel放到List中即Session管理channel.writeAndFlush(okResp); clientList.add(channel); channel需要writeAndFlush 输出应答。
输出应答的枚举 io.netty.handler.codec.mqtt.MqttConnectReturnCode
MqttMessage 枚举事件 io.netty.handler.codec.mqtt.MqttMessageType
CONNECT(1), CONNACK(2), PUBLISH(3)。。。
连接connect
InetSocketAddress address = (InetSocketAddress)contextBo.getHandlerContext().channel().remoteAddress();
String host = address.getAddress().getHostAddress();
String clientId = msg.payload().clientIdentifier();
String username = msg.payload().userName();
String password = msg.payload().passwordInBytes() == null ? null : new String(msg.payload().passwordInBytes(), CharsetUtil.UTF_8);
if (!authManager.checkValid(clientId, username, password)) {
MqttConnAckMessage connAckMessage = (MqttConnAckMessage) MqttMessageFactory.newMessage(
new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0),
new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, false), null);
SendManager.responseMsg(
contextBo,
connAckMessage,
null,
true);
return false;
}
return true;
}
发布消息
// 发布消息
case PUBLISH:
this.publish(channel, (MqttPublishMessage) message);
break;
public void publish(Channel channel, MqttPublishMessage msg) {
log.info("qos类型是{}",msg.fixedHeader().qosLevel());
String topic = msg.variableHeader().topicName();
log.info("订阅主题:{}",topic);
ByteBuf buf = msg.content().duplicate();
byte[] tmp = new byte[buf.readableBytes()];
buf.readBytes(tmp);
String content = null;
try {
content = new String(tmp,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//校验传入的数据是否符合要求
if(StringUtils.isBlank(content)){
log.error("MQTT接收到的数据包为空===》{}",content);
puback(channel,msg,"MQTT接收到的数据包为空");
return;
}
log.info("MQTT读取到的客户端发送信息===>{}",content);
// 如果是qos1或者qos2类型都需要响应
puback(channel,msg,content);
// 推送主题消息
log.info("推送客户端客户端消息:{}",content);
if (AT_LEAST_ONCE == msg.fixedHeader().qosLevel() || AT_MOST_ONCE == msg.fixedHeader().qosLevel()) {
for (Channel channel1 : clientList) {
// TODO: 同一个broke下所有的通道都发送,只有订阅的才能收到,这里可以通过Map找到订阅topic与channel的关系
try {
send(channel1,topic,msg.fixedHeader().qosLevel(),content);
发布保留消息
mqtt retain消息
一、什么是MQTT Retain
发布者发布消息时,如果Retained标记被设置为true,则该消息即是MQTT中的保留消息(Retained Message)。MQTT服务器会为每个主题存储最新一条保留消息,以方便消息发布后才上线的客户端在订阅主题时仍可以接收到该消息。
MQTT Retain是MQTT协议中一种消息保留的机制。它可以确保MQTT broker在收到retain消息之后,可以将该消息保留,并在对应的topic有订阅者时,把该消息发送给订阅者。同时,retain消息还可以保留最新的消息状态,这对于一些需要时刻知道最新状态的应用场景,如设备状态监测、报警通知、遥测等至关重要。
MQTT Retain使用中需要注意的地方
1、Retain消息只能被设置一次,一旦设置,永久生效。并不是离线可以发送的意思。
2、在订阅retain topic之前,无法接收到此topic的retain信息。
3、retain消息仅适用于QoS级别为0或1的消息。
4、使用retain消息可能会导致一些不必要的信息流量,因此在使用前应仔细考虑是否真正需要使用该机制。
何时使用MQTT保留消息?
发布订阅模式虽然能让消息的发布者与订阅者充分解耦,但也存在一个缺点,即订阅者无法主动向发布者请求消息。订阅者何时收到消息完全依赖于发布者何时发布消息,这在某些场景中就产生了不便。
保留消息将保存多久?如何删除?
服务器只会为每个主题保存最新一条保留消息,保留消息的保存时间与服务器的设置有关。若服务器设置保留消息存储在内存,则MQTT服务器重启后消息即会丢失;若存储在磁盘,则服务器重启后保留消息仍然存在。
保留消息虽然存储在服务端中,但它并不属于会话的一部分。也就是说,即便发布这个保留消息的会话已结束,保留消息也不会被删除。删除保留消息有以下几种方式:
-
客户端往某个主题发送一个Payload为空的保留消息,服务端就会删除这个主题下的保留消息;
-
在MQTT服务器上删除,如EMQX MQTT服务器提供了在Dashboard上删除保留消息的功能;
-
MQTT 5.0新增了消息过期间隔属性,发布时可使用该属性设置消息的过期时间,
业务中需要自己设置过期时间。,一直保存着。
MQTT Retain是一种可以保留消息的机制,通过保留传输的信息,可以节约网络数据传输资源,同时保持topic的最新状态。在使用时需要注意retain只能被设置一次,并且可能会导致不必要的信息流量,需要慎重使用。
三个QoS等级
-
QoS 0,最多交付一次。
-
QoS 1,至少交付一次。
-
QoS 2,只交付一次。
其中,使用QoS 0可能丢失消息,使用QoS 1可以保证收到消息,但消息可能重复,使用QoS 2可以保证消息既不丢失也不重复。QoS等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
QoS 1-至少交付一次
为了保证消息到达,QoS 1加入了应答与重传机制,发送方只有在收到接收方的PUBACK报文以后,才能认为消息投递成功,在此之前,发送方需要存储该PUBLISH报文以便下次重传。注意要存储消息
QoS 2 自己百度,这里略。
-
发送topic消息案例
/**
* @param topic 发送传过来的 topic
* @param mqttQoS qos类型
* @param messageBytes 消息 16进制
* @param retain
* @param dup
*/
private void sendPublishMessage(String topic, MqttQoS mqttQoS, byte[] messageBytes, boolean retain, boolean dup) {
// 找到对应的订阅的topic集合 然后一一发送消息。
List<SubscribeStore> subscribeStores = subscribeStoreService.search(topic);
subscribeStores.forEach(subscribeStore -> {
if (sessionStoreService.containsKey(subscribeStore.getClientId())) {
// 订阅者收到MQTT消息的QoS级别, 最终取决于发布消息的QoS和主题订阅的QoS
MqttQoS respQoS = mqttQoS.value() > subscribeStore.getMqttQoS() ? MqttQoS.valueOf(subscribeStore.getMqttQoS()) : mqttQoS;
if (respQoS == MqttQoS.AT_MOST_ONCE) {
MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage(
new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0),
new MqttPublishVariableHeader(topic, 0), Unpooled.buffer().writeBytes(messageBytes));
LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}", subscribeStore.getClientId(), topic, respQoS.value());
sessionStoreService.get(subscribeStore.getClientId()).getChannel().writeAndFlush(publishMessage);
}
if (respQoS == MqttQoS.AT_LEAST_ONCE) {
int messageId = messageIdService.getNextMessageId();
MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage(
new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0),
new MqttPublishVariableHeader(topic, messageId), Unpooled.buffer().writeBytes(messageBytes));
LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", subscribeStore.getClientId(), topic, respQoS.value(), messageId);
DupPublishMessageStore dupPublishMessageStore = new DupPublishMessageStore().setClientId(subscribeStore.getClientId())
.setTopic(topic).setMqttQoS(respQoS.value()).setMessageBytes(messageBytes);
dupPublishMessageStoreService.put(subscribeStore.getClientId(), dupPublishMessageStore);
sessionStoreService.get(subscribeStore.getClientId()).getChannel().writeAndFlush(publishMessage);
}
if (respQoS == MqttQoS.EXACTLY_ONCE) {
int messageId = messageIdService.getNextMessageId();
MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage(
new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0),
new MqttPublishVariableHeader(topic, messageId), Unpooled.buffer().writeBytes(messageBytes));
LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", subscribeStore.getClientId(), topic, respQoS.value(), messageId);
DupPublishMessageStore dupPublishMessageStore = new DupPublishMessageStore().setClientId(subscribeStore.getClientId())
.setTopic(topic).setMqttQoS(respQoS.value()).setMessageBytes(messageBytes);
dupPublishMessageStoreService.put(subscribeStore.getClientId(), dupPublishMessageStore);
sessionStoreService.get(subscribeStore.getClientId()).getChannel().writeAndFlush(publishMessage);
}
}
});
}
@Override
public List<SubscribeStore> search(String topic) {
List<SubscribeStore> subscribeStores = new ArrayList<SubscribeStore>();
if (subscribeNotWildcardCache.containsKey(topic)) {
ConcurrentHashMap<String, SubscribeStore> map = subscribeNotWildcardCache.get(topic);
Collection<SubscribeStore> collection = map.values();
List<SubscribeStore> list = new ArrayList<SubscribeStore>(collection);
subscribeStores.addAll(list);
}
subscribeWildcardCache.forEach(entry -> {
String topicFilter = entry.getKey();
if (StrUtil.split(topic, '/').size() >= StrUtil.split(topicFilter, '/').size()) {
List<String> splitTopics = StrUtil.split(topic, '/');
List<String> spliteTopicFilters = StrUtil.split(topicFilter, '/');
String newTopicFilter = "";
for (int i = 0; i < spliteTopicFilters.size(); i++) {
String value = spliteTopicFilters.get(i);
if (value.equals("+")) {
newTopicFilter = newTopicFilter + "+/";
} else if (value.equals("#")) {
newTopicFilter = newTopicFilter + "#/";
break;
} else {
newTopicFilter = newTopicFilter + splitTopics.get(i) + "/";
}
}
newTopicFilter = StrUtil.removeSuffix(newTopicFilter, "/");
if (topicFilter.equals(newTopicFilter)) {
ConcurrentHashMap<String, SubscribeStore> map = entry.getValue();
Collection<SubscribeStore> collection = map.values();
List<SubscribeStore> list = new ArrayList<SubscribeStore>(collection);
subscribeStores.addAll(list);
}
}
});
return subscribeStores;
}
Mqtt 实现 SSL 单向和双向认证
如何生成证书?
第一步: 生成Netty服务端私钥和证书(高版本的java可能会提示升级转换之类的,复制他的提示,直接运行会产生因版本的证书)
keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=boke" -keypass 123456 -storepass 123456 -keystore sHan.jks
第二步:生成Netty服务端自签名证书
keytool -export -alias securechat -keystore sHan.jks -storepass 123456 -file sHan.cer
第三步:生成客户端的**对和证书仓库,用于将服务端的证书保存到客户端的授信证书仓库中
keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=boke" -keypass 123456 -storepass 123456 -keystore cHan.jks
第四步:将Netty服务端证书导入到客户端的证书仓库中
keytool -import -trustcacerts -alias securechat -file sHan.cer -storepass 123456 -keystore cHan.jks
如果你只做单向认证,则到此就可以结束了,如果是双响认证,则还需继续往下走
keytool -export -alias smcc -keystore cHan.jks -storepass 123456 -file cHan.cer
最后一步:将客户端的自签名证书导入到服务端的信任证书仓库中:
keytool -import -trustcacerts -alias smcc -file cHan.cer -storepass 123456 -keystore sHan.jks
以上生成的两个后缀为jks的文件,两个后缀为cer的文件,我们将cChat.jks作为客户端生成sslcontext的文件,sChat.jks作为服务器生成sslContext的文件,即可。双向认证需要各自的cer证书。
server代码部分
/**
* @param isAuth false为单向认证,true为双向认证
* @param pkInputStream
* @param caInputStream
* @param passwd
* @return
*/
public static SSLEngine getSslServerEngine(boolean isAuth,InputStream pkInputStream,InputStream caInputStream, String passwd ) {
SSLEngine sslEngine = null;
if (isAuth) {
sslEngine = getServerContext(pkInputStream,
caInputStream, passwd)
.createSSLEngine();
} else {
sslEngine = getServerContext(pkInputStream,
passwd).createSSLEngine();
}
sslEngine.setUseClientMode(false);
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
// false为单向认证,true为双向认证
sslEngine.setNeedClientAuth(isAuth);
return sslEngine;
}
if (serverCreator.isSslAuth()) {
InputStream caInputStream=null;
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(serverCreator.getSslConfig().getKeystorePath());
if(serverCreator.getSslConfig().isEnable()){
caInputStream = this.getClass().getClassLoader().getResourceAsStream(serverCreator.getSslConfig().getTruststorePath());
}
SSLEngine sslServerEngine = SSLContextFactory.getSslServerEngine(serverCreator.getSslConfig().isEnable(), inputStream, caInputStream, serverCreator.getSslConfig().getKeystorePwd());
channelPipeline.addLast("ssl", new SslHandler(sslEngine));// 不要ssl就去掉
package com.todostudy.iot.mqtt.server.ssl;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
/**
* 包括单向认证和双向认证
*/
public class SSLContextFactory {
private static final String PROTOCOL = "TLS";
private static final String JKS = "JKS";
private static final String SunX509="SunX509";
private static SSLContext SERVER_CONTEXT;// 服务器安全套接字协议
private static SslContext openSslContext;
private static SSLContext CLIENT_CONTEXT;// 客户端安全套接字协议
private static SslContext openSslClientContext;
/**
* @param isAuth false为单向认证,true为双向认证
* @param pkInputStream
* @param caInputStream
* @param passwd
* @return
*/
public static SSLEngine getSslServerEngine(boolean isAuth,InputStream pkInputStream,InputStream caInputStream, String passwd ) {
SSLEngine sslEngine = null;
if (isAuth) {
sslEngine = getServerContext(pkInputStream,
caInputStream, passwd)
.createSSLEngine();
} else {
sslEngine = getServerContext(pkInputStream,
passwd).createSSLEngine();
}
sslEngine.setUseClientMode(false);
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
// false为单向认证,true为双向认证
sslEngine.setNeedClientAuth(isAuth);
return sslEngine;
}
public static SSLContext getServerContext(InputStream pkInputStream, String passwd) {
if (SERVER_CONTEXT != null)
return SERVER_CONTEXT;
InputStream in = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance(JKS);
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
// 获取安全套接字协议(TLS协议)的对象
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
SERVER_CONTEXT.init(kmf.getKeyManagers(), null, null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext",
e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return SERVER_CONTEXT;
}
public static SslContext getOpenSslServerContext(InputStream pkInputStream,
String passwd) {
if (openSslContext != null) {
return openSslContext;
}
try {
// 密钥管理器
KeyManagerFactory kmf = null;
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance(JKS);
// 加载服务端证书
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
openSslContext = SslContextBuilder.forServer(kmf)
.sslProvider(SslProvider.OPENSSL).build();
return openSslContext;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static SSLContext getClientContext(InputStream pkInputStream, String passwd) {
if (CLIENT_CONTEXT != null)
return CLIENT_CONTEXT;
try {
// 信任库
TrustManagerFactory tf = null;
// 密钥库KeyStore
KeyStore tks = KeyStore.getInstance(JKS);
// 加载客户端证书
tks.load(pkInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
// 初始化信任库
tf.init(tks);
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 设置信任证书
CLIENT_CONTEXT.init(null,
tf == null ? null : tf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the client-side SSLContext");
}
return CLIENT_CONTEXT;
}
public static SslContext getOpenSslClientContext(InputStream pkInputStream,
String passwd) {
if (openSslClientContext != null) {
return openSslClientContext;
}
try {
// 信任库
TrustManagerFactory tf = null;
// 密钥库KeyStore
KeyStore tks = KeyStore.getInstance(JKS);
// 加载客户端证书
tks.load(pkInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
// 初始化信任库
tf.init(tks);
openSslClientContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL).trustManager(tf).build();
return openSslClientContext;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Description: 生成sslContext
*
* @param caInputStream
* @param passwd
* @return
* @see
*/
public static SSLContext getServerContext(InputStream pkInputStream, InputStream caInputStream,
String passwd) {
if (SERVER_CONTEXT != null)
return SERVER_CONTEXT;
InputStream tIN = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
KeyStore ks = KeyStore.getInstance(JKS);
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
kmf.init(ks, passwd.toCharArray());
// 信任库
TrustManagerFactory tf = null;
KeyStore tks = KeyStore.getInstance(JKS);
tks.load(caInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
tf.init(tks);
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
// 单向认证?无需验证客户端证书
if (tf == null) {
SERVER_CONTEXT.init(kmf.getKeyManagers(), null, null);
}
// 双向认证,需要验证客户端证书
else {
SERVER_CONTEXT.init(kmf.getKeyManagers(),
tf.getTrustManagers(), null);
}
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext",
e);
}
return SERVER_CONTEXT;
}
public static SslContext getOpenSslServerContext(InputStream pkInputStream,
InputStream caInputStream, String passwd) {
if (openSslContext != null)
return openSslContext;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance(JKS);
// 加载服务端证书
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
// 信任库
TrustManagerFactory tf = null;
KeyStore tks = KeyStore.getInstance(JKS);
tks.load(caInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
tf.init(tks);
openSslContext = SslContextBuilder.forServer(kmf).trustManager(tf)
.sslProvider(SslProvider.OPENSSL).build();
return openSslContext;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static SSLContext getClientContext(InputStream pkInputStream,
InputStream caInputStream,
String passwd) {
if (CLIENT_CONTEXT != null)
return CLIENT_CONTEXT;
try {
KeyManagerFactory kmf = null;
KeyStore ks = KeyStore.getInstance(JKS);
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
kmf.init(ks, passwd.toCharArray());
TrustManagerFactory tf = null;
KeyStore tks = KeyStore.getInstance(JKS);
tks.load(caInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
tf.init(tks);
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
CLIENT_CONTEXT.init(kmf.getKeyManagers(), tf.getTrustManagers(),
null);
} catch (Exception e) {
throw new Error("Failed to initialize the client-side SSLContext");
}
return CLIENT_CONTEXT;
}
public static SslContext getOpenSslClientContext(InputStream pkInputStream,
InputStream caInputStream, String passwd) {
if (openSslClientContext != null) {
return openSslClientContext;
}
try {
// 密钥管理器
KeyManagerFactory kmf = null;
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance(JKS);
// 加载服务端证书
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(pkInputStream, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance(SunX509);
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
// 信任库
TrustManagerFactory tf = null;
KeyStore tks = KeyStore.getInstance(JKS);
tks.load(caInputStream, passwd.toCharArray());
tf = TrustManagerFactory.getInstance(SunX509);
tf.init(tks);
openSslClientContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL).keyManager(kmf)
.trustManager(tf).build();
return openSslClientContext;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static SSLEngine getSslClientEngine(InputStream pkInputStream,
InputStream caInputStream,
String passwd, boolean isNeedClientAuth) {
SSLEngine sslEngine = null;
if (isNeedClientAuth) {
sslEngine = getClientContext(pkInputStream, caInputStream, passwd)
.createSSLEngine();
} else {
sslEngine = getClientContext(pkInputStream, passwd).createSSLEngine();
}
sslEngine.setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1",
"TLSv1.2"});
sslEngine.setUseClientMode(true);
return sslEngine;
}
public static SSLEngine getOpenSslClientEngine(InputStream pkInputStream,
InputStream caInputStream, String passwd, ByteBufAllocator alloc,
boolean isNeedClientAuth) {
SSLEngine sslEngine = null;
if (isNeedClientAuth) {
sslEngine = getOpenSslClientContext(pkInputStream, caInputStream, passwd)
.newEngine(alloc);
} else {
sslEngine = getOpenSslClientContext(pkInputStream, passwd)
.newEngine(alloc);
}
sslEngine.setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1",
"TLSv1.2"});
sslEngine.setUseClientMode(true);
return sslEngine;
}
}
关于mqtt集群问题
mqtt 是长连接。也就是前面加个负载服务器比如nginx 即可以。但是订阅的topic 和一些qos等级的数据,需要共享数据。
方案很简单加个缓存服务器,比如redis 即可以集群啦。
githut代码:GitHub - laichun/han-tools: han-tools工具类
开源不易 get me a 赞 ,本代码目前在一个企业物联网中使用,发现bugger会不定期同步代码。欢迎加入。