I want to implement a MQTT client which can auto-reply when it get a message.
for example:
client1 send a "request" message to client2 (by topic "/toClient2").
client2 get the "request" message and reply a "response" message to client1 (by topic "/toClient1").
I use org.eclipse.paho.client.mqttv3 v1.2.5 and mosquitto as broker to implement it. And it works when client1 send 1 request to client2 in one time. (client2 can reply "response" correctly)
But I found a problem that if client1 sent a lot (e.g. 100, 10000) "request" Continuously to client2, client2 can't publish message or receive message.
Then client2 would disconnect.
my code is here:
package test;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class Client implements MqttCallback {
private final int id;
private MqttClient mqttClient;
private int qos = 1;
private int numGet = 0;
private int numSend = 0;
public Client(int id) {
this.id = id;
String HOST = "tcp://127.0.0.1:1883";
MqttConnectOptions options = new MqttConnectOptions();
try {
mqttClient = new MqttClient(HOST, String.valueOf(id), new MemoryPersistence());
options.setCleanSession(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(20);
options.setMaxInflight(1000);
mqttClient.setCallback(this);
mqttClient.connect(options);
System.out.println("Connected to MQTT Broker");
} catch (MqttException e) {
e.printStackTrace();
}
}
private void subscribe(String topic) {
try {
mqttClient.subscribe(topic, qos);
} catch (Exception e) {e.printStackTrace();}
}
public void publish(String topic, String payload) {
try {
boolean retained = false;
mqttClient.publish(topic, payload.getBytes(), qos, retained);
} catch (Exception e) {e.printStackTrace();}
}
#Override
public void connectionLost(Throwable cause) {}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
System.out.println(topic + " get: " + message);
if (id == 2)
publish("/toClient1", "Response");
numGet++;
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) { numSend++; }
public static void main(String[] args) throws InterruptedException {
Client client1 = new Client(1);
Client client2 = new Client(2);
String topic1 = "/toClient1";
String topic2 = "/toClient2";
client1.subscribe(topic1);
client2.subscribe(topic2);
int n = 100;
for (int i = 0; i < n; i++) {
client1.publish(topic2, "request");
}
Thread.sleep(1000);
System.out.println("Client1 sent messages: " + client1.numSend);
System.out.println("Client2 sent messages: " + client2.numSend);
}
}
I got the output like this:
Connected to MQTT Broker
Connected to MQTT Broker
/toClient2 get: request
/toClient1 get: Response
Client1 sent messages: 100
Client2 sent messages: 0
1月 29, 2023 1:02:10 下午 org.eclipse.paho.client.mqttv3.internal.ClientState checkForActivity
严重: 2: Timed out as no activity, keepAlive=20,000,000,000 lastOutboundActivity=6,831,960,648,000 lastInboundActivity=6,811,951,900,600 time=6,851,960,579,800 lastPing=6,831,960,650,700
等待来自服务器的响应时超时 (32000)
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:31)
at org.eclipse.paho.client.mqttv3.internal.ClientState.checkForActivity(ClientState.java:747)
at org.eclipse.paho.client.mqttv3.internal.ClientComms.checkForActivity(ClientComms.java:818)
at org.eclipse.paho.client.mqttv3.internal.ClientComms.checkForActivity(ClientComms.java:804)
at org.eclipse.paho.client.mqttv3.TimerPingSender$PingTask.run(TimerPingSender.java:79)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
I suspect that I should not do publish() in public void messageArrived(String topic, MqttMessage message) callback, so I try to use a queue to enqueue the "request" message and use a new Thread to consume the queue and do publish(). But it get the same result.
Related
I have the following code snipped that creates a TCPServer, and attaches a ChannelHandler to the channel in the doOnChannelInit() function. The server is to process byte data from an embedded device.
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private final NettyServerHandler nettyServerHandler;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES))
.addLast(new ByteArrayDecoder())
.addLast(new ByteArrayEncoder())
.addLast(nettyServerHandler);
});
server.bindNow();
log.info("Server running");
}
}
Channel handler
#Component
#RequiredArgsConstructor
#ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<byte[]> {
Logger log = LoggerFactory.getLogger(NettyServerHandler.class);
private final AttributeKey<byte[]> dataKey = AttributeKey.valueOf("dataBuf");
private final AttributeKey<Integer> dataLen = AttributeKey.valueOf("dataBufLen");
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("New Meter connection from : " + ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel() != null) {
log.info(String.format("Meter/Client Disconnected. No: %s ; Channel : %s", meterNo, ctx.channel()));
}
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
log.info("Message received: " + new String(msg);
ctx.channel().read();
}
}
I'm able to connect to the server, but when i send a message, nothing happens, the log statements are not triggered.
I'm not sure what I'm missing here, would appreciate some help.
Thanks
There is no need to add custom handlers to the Netty pipeline. The example above can be written like this:
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES));
})
.handle((in, out) ->
in.receive()
.asString()
.doOnNext(s -> log.info("Message received: " + s))
.then());
server.bindNow();
log.info("Server running");
}
}
Consider checking the Reference Documentation
The incoming data can be transformed to String with (asString), to byte[] with (asByteArray) etc. If there is no suitable transformation you can use map(byteBuf -> ...) and transform the ByteBuf to the needed abstraction.
Following is the MQTT configuration to listening event.
For high message load around 100 message per second I noticed messages not received realtime on handler.
public class VehicleEventMqttConfig {
#Value("${mqtt.auto-startup.vehicleEvent:false}")
private boolean autoStartup;
#Value("${mqtt.completion-timeout.vehicleEvent:30000}")
private int completionTimeout;
#Bean
public MessageChannel vehicleMqttInputChannel() {
return new DirectChannel();
}
#Bean
public MessageProducer inboundVehicleEvent(
final MqttPahoClientFactory mqttPahoClientFactory,
final MqttAdapters adapters,
#Value("${mqtt.topic.vehicleEvent}") final String topic) {
log.info("Register vehicleEvent mqtt");
if (StringUtils.isEmpty(topic)) {
log.warn("vehicleEvent disabled!");
return null;
}
final MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
getClientIdWithHost("inboundVehicleEvent"), mqttPahoClientFactory, topic);
adapter.setCompletionTimeout(completionTimeout);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setOutputChannel(vehicleMqttInputChannel());
adapter.setAutoStartup(autoStartup);
adapter.setQos(1);
adapters.add(adapter);
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "vehicleMqttInputChannel")
public MessageHandler vehicleEventHandler() {
return new VehicleEventMessageHandler();
}
}
I am trying to connect to a Docker UNIX domain socket using Netty. Here's my attempt so far.
#PostConstruct
public void init() throws Exception {
io.netty.bootstrap.Bootstrap bootstrap = new io.netty.bootstrap.Bootstrap();
bootstrap
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(new DomainSocketAddress("/var/run/docker.sock"))
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel
.pipeline()
.addLast(new SimpleChannelInboundHandler<HttpObject>() {
#Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
System.out.println(httpObject);
}
});
}
});
final Channel channel = bootstrap.connect().sync().channel();
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/services", Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
channel.writeAndFlush(request);
channel.closeFuture().sync();
System.out.println("DONE");
}
At the moment I am getting
Caused by: java.nio.channels.UnsupportedAddressTypeException: null
Is there an example on how to do HTTP connections to UDS using Netty? So far I only found raw UDS and TCP HTTP but not combined.
Here's a working implementation.
io.netty.bootstrap.Bootstrap bootstrap = new io.netty.bootstrap.Bootstrap();
final EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup();
try {
bootstrap
.group(epollEventLoopGroup)
.channel(EpollDomainSocketChannel.class)
.handler(new ChannelInitializer<UnixChannel>() {
#Override
public void initChannel(UnixChannel ch) throws Exception {
ch
.pipeline()
.addLast(new HttpClientCodec())
.addLast(new HttpContentDecompressor())
.addLast(new SimpleChannelInboundHandler<HttpObject>() {
private StringBuilder messageBuilder = new StringBuilder();
#Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
messageBuilder.append(content.content().toString(StandardCharsets.UTF_8));
if (msg instanceof LastHttpContent) {
System.out.println(messageBuilder);
}
} else {
System.out.println(msg.getClass());
}
}
});
}
});
final Channel channel = bootstrap.connect(new DomainSocketAddress("/var/run/docker.sock")).sync().channel();
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/services", Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.HOST, "daemon");
channel.writeAndFlush(request);
channel.closeFuture().sync();
} finally {
epollEventLoopGroup.shutdownGracefully();
}
Few things to note:
Use the EpollEventLoopGroup and EpollDomainSocketChannel with a ChannelInitializer<UnixChannel>.
HTTP requires new HttpCodec() in the pipeline to use the Netty HTTP objects.
The data may be chunked so you need to assemble it and wait for the LastHttpContent object
https://github.com/trajano/netty-docker-daemon-socket
I have a Broadcast receiver that checks WIFI_STATE_CHANGE to see if I have connected to a certain WiFi network. For example if I am coming home, I want a certain MQTT message to be sent. The problem I have is that it connects and sends the MQTT message, only when run the app the first time.
Process:
If I build the application and run it on the device and it recognised my home WiFi it sends the message.
I turn off Wifi from the device, and turn it back on again.
I get "Failure" which is a message when the MQTT connection to the server could not be established.
What I would need is that after I reconnect to the network, instead of "Failure" to get "Connected" but somehow it never happens...what could be wrong?
PS. I think it has to do with the fact that when WiFi is detected, the Broadcast Receiver runs the connection code, although Internet is not available at that point of time (obtaining IP etc.)
Here is the code of the Broadcast receiver:
package me.app.comehomedemo;
import ...
public class SynchronizeBroadcastReceiver extends BroadcastReceiver {
MqttAndroidClient client;
static String MQTTHOST = "myhost";
static String USERNAME = "myusername";
static String PASSWORD = "mypassword";
static String topicStr = "/topic/mac/control";
static String payload = "1";
#Override
public void onReceive(final Context context, Intent intent) {
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (info.isConnected()) {
WifiManager wifiManager = ( WifiManager ) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ip = wifiInfo.getIpAddress();
Toast.makeText(context, String.valueOf(ip), Toast.LENGTH_SHORT).show();
String ssid = wifiInfo.getSSID();
if (ssid.equals("\"mySSID\"")) {
String clientId = MqttClient.generateClientId();
client = new MqttAndroidClient(context.getApplicationContext(), MQTTHOST, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
// options.setAutomaticReconnect(true);
try {
IMqttToken token = client.connect(options);
token.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
// We are connected
Toast.makeText(context, "Connected", Toast.LENGTH_SHORT).show();
try {
client.publish(topicStr, payload.getBytes(), 0, false);
} catch (MqttException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
// Something went wrong e.g. connection timeout or firewall problems
Toast.makeText(context, "Failure", Toast.LENGTH_SHORT).show();
}
});
} catch (MqttException e) {
e.printStackTrace();
}
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
MediaPlayer mp = MediaPlayer.create(context.getApplicationContext(), notification);
mp.start();
}
}
}
}
I have managed to solve it by waiting 2 seconds and then running the task. Used this solution and it worked. I had to wait for the Internet connection to get ready!
Since waiting 2 seconds has solved your problem, then it might be that the Wifi broadcast comes too early, before there is a connection established (like DHCP gives your phone IP and establishes the routes) for the MQTT connect and publish packets to be properly delivered.
But what happens if some other user needs to wait 10 and not 2 seconds?
My suggestion is to set the automatic reconnect option in MqttConnectOptions and then use the connection callback to publish the needed info to the broker and finally disconnect in publish callback:
private IMqttActionListener mConnectCallback = new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken token) {
try {
client.publish(topicStr, new MqttMessage(payload.getBytes()), null, mPublishCallback);
} catch (Exception ex) {
ex.printStackTrace();
}
}
#Override
public void onFailure(IMqttToken token, Throwable ex) {
}
};
private IMqttActionListener mPublishCallback = new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken token) {
// TODO disconnect
}
#Override
public void onFailure(IMqttToken token, Throwable ex) {
}
};
MqttAndroidClient client = new MqttAndroidClient(context, MQTTHOST, "my_id");
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
options.setAutomaticReconnect(true);
client.connect(options, null, mConnectCallback);
I need to send IQ packets to XMPP server for retrieving message archive.
How do i create, send and receive XMPP IQ packets?
Thanks
PS: This question may seem to be duplicate of How Can I create,send and receive iq packets using smack(java), its not. Smack API has been changed a lot in recent years.
MyCustomIQ iq = new MyCustomIQ();
iq.setType(IQ.Type.set);
mConnection.sendIqWithResponseCallback(iq, new PacketListener() {
#Override
public void processPacket(Packet packet) throws SmackException.NotConnectedException {
Log.i("Send IQ with Response", "****** message " + packet);
}
}, new ExceptionCallback() {
#Override
public void processException(Exception exception) {
exception.printStackTrace();
Log.i("IO archjieve Exception",""+ exception.getMessage());
}
}, 5000);
mConnection.sendPacket(new Presence(Presence.Type.available));
PacketTypeFilter filter=new PacketTypeFilter(org.jivesoftware.smack.packet.Message.class);
PacketListener myListener=new PacketListener(){
public void processPacket(Packet packet){
if(((Message) packet).getType().equals(Message.Type.chat))
{
((Message) packet).getBody();
}
else if(((Message) packet).getType().equals(Message.Type.normal))
{
DefaultPacketExtension pacExten=PacketUtil.packetExtensionfromCollection(packet.getExtensions(), "result", "urn:xmpp:mam:0");
String strMsg=pacExten.getValue("body");
}
}
}
;
mConnection.addPacketListener(myListener, filter);
//My Custom IQ
class MyCustomIQ extends IQ {
String token;
protected MyCustomIQ() {
super("query","urn:xmpp:mam:0");
}
#Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
// String queryId = prefix + Long.toString(new AtomicLong().incrementAndGet());
xml.attribute("queryid",queryId);
xml.rightAngleBracket();
return xml;
}
}
//You may get the response in PacketListerener sometimes so put debug that also