My back end java has the goal to receive mobile messages using GCM XMPP. My web applications have spring 4.1.4 and smack 4.1.4.
Smack dependencies:
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-tcp</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-extensions</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-java7</artifactId>
<version>4.0.1</version>
</dependency>
Bean for XMPP Connection:
#Component("CcsClientImpl")
public class CcsClientImpl {
private static final String GCM_ELEMENT_NAME = "gcm";
private static final String GCM_NAMESPACE = "google:mobile:data";
private static XMPPTCPConnection connection;
/**
* Indicates whether the connection is in draining state, which means that it
* will not accept any new downstream messages.
*/
protected static volatile boolean connectionDraining = false;
private static final Logger logger = LoggerFactory.getLogger("CcsClientImpl");
#Value("${sender.id}")
private String mSenderId;
#Value("${server.api.key}")
private String mServerApiKey;
#Value("${gcm.xmpp.host}")
private String mHost;
#Value("${gcm.xmpp.port}")
private int mPort;
#Value("${gcm.xmpp.debuggable}")
private boolean mDebuggable;
#Autowired
private ProcessorFactory processorFactory;
//#Autowired
public CcsClientImpl() {
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new ExtensionElementProvider<ExtensionElement>() {
#Override
public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException, IOException {
String json = parser.nextText();
return new GcmPacketExtension(json);
}
});
try {
connect(mSenderId, mServerApiKey);
} catch (XMPPException ex) {
logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
} catch (SmackException ex) {
logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
} catch (IOException ex) {
logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
}
}
/**
* Sends a downstream message to GCM.
*
* #return true if the message has been successfully sent.
*/
public boolean sendDownstreamMessage(String jsonRequest) throws
NotConnectedException {
if (!connectionDraining) {
send(jsonRequest);
return true;
}
logger.info("Dropping downstream message since the connection is draining");
return false;
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>Note: This is generated by a pseudo random number generator for
* illustration purpose, and is not guaranteed to be unique.
*/
public String nextMessageId() {
return "m-" + UUID.randomUUID().toString();
}
/**
* Sends a packet with contents provided.
*/
protected void send(String jsonRequest) throws NotConnectedException {
Stanza request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendStanza(request);
}
/// new: customized version of the standard handleIncomingDateMessage method
/**
* Handles an upstream data message from a device application.
*/
public void handleIncomingDataMessage(CcsMessage msg) {
if (msg.getPayload().get("action") != null) {
PayloadProcessor processor = processorFactory.getProcessor(msg.getPayload().get("action"));
processor.handleMessage(msg);
}
}
/**
* Handles an ACK.
*
* <p>Logs a INFO message, but subclasses could override it to
* properly handle ACKs.
*/
protected void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
logger.info("handleAckReceipt() from: " + from + ",messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>Logs a INFO message, but subclasses could override it to
* properly handle NACKs.
*/
protected void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
logger.info("handleNackReceipt() from: " + from + ",messageId: " + messageId);
}
protected void handleControlMessage(Map<String, Object> jsonObject) {
logger.info("handleControlMessage(): " + jsonObject);
String controlType = (String) jsonObject.get("control_type");
if ("CONNECTION_DRAINING".equals(controlType)) {
connectionDraining = true;
} else {
logger.info("Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.",
controlType);
}
}
/**
* Creates a JSON encoded GCM message.
*
* #param to RegistrationId of the target device (Required).
* #param messageId Unique messageId for which CCS sends an
* "ack/nack" (Required).
* #param payload Message content intended for the application. (Optional).
* #param collapseKey GCM collapse_key parameter (Optional).
* #param timeToLive GCM time_to_live parameter (Optional).
* #param delayWhileIdle GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received
* from an application.
*
* #param to RegistrationId of the device who sent the upstream message.
* #param messageId messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
protected static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param senderId Your GCM project number
* #param apiKey API Key of your project
*/
public void connect(String senderId, String serverApiKey)
throws XMPPException, IOException, SmackException {
XMPPTCPConnectionConfiguration config =
XMPPTCPConnectionConfiguration.builder()
.setServiceName(mHost)
.setHost(mHost)
.setCompressionEnabled(false)
.setPort(mPort)
.setConnectTimeout(30000)
.setSecurityMode(SecurityMode.disabled)
.setSendPresence(false)
.setDebuggerEnabled(mDebuggable)
.setSocketFactory(SSLSocketFactory.getDefault())
.build();
connection = new XMPPTCPConnection(config);
//disable Roster as I don't think this is supported by GCM
Roster roster = Roster.getInstanceFor(connection);
roster.setRosterLoadedAtLogin(false);
logger.info("Connecting...");
connection.connect();
connection.addConnectionListener(new LoggingConnectionListener());
// Handle incoming packets
connection.addAsyncStanzaListener(new MyStanzaListener() , new MyStanzaFilter() );
// Log all outgoing packets
connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter() );
connection.login(senderId + "#gcm.googleapis.com" , serverApiKey);
logger.info("Logged in: " + mSenderId);
}
private CcsMessage getMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// unique id of this message
String messageId = jsonObject.get("message_id").toString();
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
CcsMessage msg = new CcsMessage(from, category, messageId, payload);
return msg;
}
private class MyStanzaFilter implements StanzaFilter {
#Override
public boolean accept(Stanza arg0) {
// TODO Auto-generated method stub
if (arg0.getClass() == Stanza.class) {
return true;
} else {
if (arg0.getTo() != null) {
if (arg0.getTo().startsWith(mSenderId)) {
return true;
}
}
}
return false;
}
}
private class MyStanzaListener implements StanzaListener{
#Override
public void processPacket(Stanza packet) {
logger.info("Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket =
(GcmPacketExtension) incomingMessage.
getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject =
(Map<String, Object>) JSONValue.
parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
CcsMessage msg = getMessage(jsonObject);
handleIncomingDataMessage(msg);
// Send ACK to CCS
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else if ("control".equals(messageType.toString())) {
// Process control message
handleControlMessage(jsonObject);
} else {
logger.warn("Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.info("Error parsing JSON " + json, e);
} catch (Exception e) {
logger.info("Failed to process packet", e);
}
}
}
private class MyStanzaInterceptor implements StanzaListener
{
#Override
public void processPacket(Stanza packet) {
logger.info("Sent: {0}", packet.toXML());
}
}
// public static void main(String[] args) throws Exception {
//
// SmackCcsClient ccsClient = new SmackCcsClient();
//
// ccsClient.connect(YOUR_PROJECT_ID, YOUR_API_KEY);
//
// // Send a sample hello downstream message to a device.
// String messageId = ccsClient.nextMessageId();
// Map<String, String> payload = new HashMap<String, String>();
// payload.put("Message", "Ahha, it works!");
// payload.put("CCS", "Dummy Message");
// payload.put("EmbeddedMessageId", messageId);
// String collapseKey = "sample";
// Long timeToLive = 10000L;
// String message = createJsonMessage(YOUR_PHONE_REG_ID, messageId, payload,
// collapseKey, timeToLive, true);
//
// ccsClient.sendDownstreamMessage(message);
// logger.info("Message sent.");
//
// //crude loop to keep connection open for receiving messages
// while(true)
// {;}
// }
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
private static final class GcmPacketExtension extends DefaultExtensionElement {
private final String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>",
GCM_ELEMENT_NAME, GCM_NAMESPACE,
StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
}
public Stanza toPacket() {
Message message = new Message();
message.addExtension(this);
return message;
}
}
private static final class LoggingConnectionListener
implements ConnectionListener {
#Override
public void connected(XMPPConnection xmppConnection) {
logger.info("Connected.");
}
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.info("Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.info("Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.info("Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
#Override
public void authenticated(XMPPConnection arg0, boolean arg1) {
// TODO Auto-generated method stub
}
}
#PreDestroy
public void cleanUp() throws Exception {
logger.info("Bean do cliente XMPP está sendo destruído...");
if (connection.isConnected()) {
logger.info("Conexão GCM XMPP está aberta. Desconectando...");
connection.disconnect();
}
}
}
When starts tomcat happens the error:
Caused by: java.lang.NoClassDefFoundError: org/jivesoftware/smack/initializer/SmackAndOsgiInitializer
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2476)
at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:857)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1282)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.jivesoftware.smack.SmackInitialization.loadSmackClass(SmackInitialization.java:213)
at org.jivesoftware.smack.SmackInitialization.parseClassesToLoad(SmackInitialization.java:193)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:163)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:148)
at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:116)
at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.java:96)
at org.jivesoftware.smack.provider.ProviderManager.<clinit>(ProviderManager.java:121)
at br.com.soma.service.gcm.xmpp.CcsClientImpl.<init>(CcsClientImpl.java:73)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 60 more
Caused by: java.lang.ClassNotFoundException: org.jivesoftware.smack.initializer.SmackAndOsgiInitializer
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1313)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
... 82 more
Can someone help me ? Thanks!
It works fine, now. I just set a variable for smack as you suggested. Thanks!!
<properties>
<smack.version>4.1.4</smack.version>
</properties>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>${smack.version}</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-tcp</artifactId>
<version>${smack.version}</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-extensions</artifactId>
<version>${smack.version}</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-java7</artifactId>
<version>${smack.version}</version>
</dependency>
Related
I am having issues with the Amazon MQ implementation of topics. When I use the in memory SimpleMessageBroker clients receive only messages that they have subscribed to. However with the externalized message broker, all clients receive all messages broadcast through the SimpMessagingTemplate.
relevant code:
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
if ("true".equalsIgnoreCase(environment.getProperty("aws.activemq.enabled")))
config.setApplicationDestinationPrefixes("/app")
.enableStompBrokerRelay("/topic")
.setUserDestinationBroadcast("/user")
.setRelayHost(this.activeMQCreds.getHost())
.setRelayPort(this.activeMQCreds.getPort())
.setClientLogin(this.activeMQCreds.getUsername())
.setClientPasscode(this.activeMQCreds.getPassword())
.setAutoStartup(true)
.setSystemLogin(this.activeMQCreds.getUsername())
.setSystemPasscode(this.activeMQCreds.getPassword())
.setTcpClient(this.createClient());
else config.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/topic");
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketSessionChannelInterceptor);
}
private TcpOperations<byte[]> createClient() {
return new ReactorNettyTcpClient<>(
(client) -> client.remoteAddress(this::getAddress).secure(), new StompReactorNettyCodec());
}
private SocketAddress getAddress() {
try {
InetAddress address =
InetAddress.getByName(this.activeMQCreds.getHost().replace("stomp+ssl://", ""));
return new InetSocketAddress(address, this.activeMQCreds.getPort());
} catch (UnknownHostException e) {
log.error("Exception", e);
return null;
}
}
}
How we are handling subscriptions:
public class WebSocketSessionChannelInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
log.info("Message: {}", message);
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
try {
final String destination = accessor.getDestination();
if (StompCommand.CONNECT == accessor.getCommand()) {
final String token = accessor.getFirstNativeHeader("Authorization");
final UsernamePasswordAuthenticationToken user =
webSocketAuthenticatorService.getAuthenticatedOrFail(token);
if (user != null) SecurityContextHolder.getContext().setAuthentication(user);
accessor.setUser(user);
} else if (StompCommand.SUBSCRIBE == accessor.getCommand()
&& StringUtils.isNotEmpty(destination)
&& destination.contains("conversations?orgId=")) {
String orgId = destination.substring(destination.lastIndexOf('=') + 1);
if (WebSocketAuthenticatorService.hasPermissionToOrgId(orgId, (Authentication) accessor.getUser()))
return message;
else
throw new BadCredentialsException(
String.format("you do not have privilege to this organization: %s", orgId));
}
return message;
} catch (Exception e) {
log.error("Exception: ", e);
return null;
}
}
#Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, #Nullable Exception ex) {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
final String destination = accessor.getDestination();
if (StompCommand.SUBSCRIBE == accessor.getCommand()
&& StringUtils.isNotEmpty(destination)
&& destination.contains("conversations?orgId=")) {
String orgId = destination.substring(destination.lastIndexOf('=') + 1);
if (WebSocketAuthenticatorService.hasPermissionToOrgId(orgId, (Authentication) accessor.getUser()))
webSocketUtils.publishConversationsForOrgId(orgId);
}
}
}
How we are sending messages to clients:
this.simpMessagingTemplate.convertAndSend(
ApplicationConstants.ORG_WEBSOCKET_TOPIC_PATH + orgId, conversationBO);
With Xamarin, We usually use Message-Center to send a message to any page when Application is running (start App or app is background).
With Flutter, have we any ways that send a message same with Message-Center of Xamarin.Forms?
Based on my research, you can use Channel to achieve that.
BasicMessageChannel is a similar channel.
Native part.
private final Activity activity;
private final BasicMessageChannel<String> messageChannel;
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
private BasicMessageChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
this.messageChannel = new BasicMessageChannel<>(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE);
//Set up a message handler to handle messages from Dart
messageChannel.setMessageHandler(this);
}
#Override
public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {//Handle messages from Dart
reply.reply("BasicMessageChannel:" + s);//Can reply by `reply`
if (activity instanceof IShowMessage) {
((IShowMessage) activity).onShowMessage(s);
}
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
/**
* Send Dart a message and receive feedback from Dart
*
* #param message Content of the message to send to Dart
* #param callback Feedback from Dart
*/
void send(String message, BasicMessageChannel.Reply<String> callback) {
messageChannel.send(message, callback);
}
#Override
public void reply(String s) {
}
}
Dart Part
import 'package:flutter/services.dart';
static const BasicMessageChannel _basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec());
//Use BasicMessageChannel to receive messages from Native and reply to Native
_basicMessageChannel
.setMessageHandler((String message) => Future<String>(() {
setState(() {
showMessage = message;
});
return "receive Native's message:" + message;
}));
//use BasicMessageChannelsend message to Native And accept Native's reply
String response;
try {
response = await _basicMessageChannel.send(value);
} on PlatformException catch (e) {
print(e);
}
Here is blog about it.
https://stablekernel.com/flutter-platform-channels-quick-start/
I've been searching the same implementation in Flutter. So far I haven't found any.
This is my simple implementation
import 'package:meta/meta.dart';
abstract class MessagingService {
void subscribe(Object subscriber, {#required String channel, #required void Function(Object) action});
void unsubscribe(Object subscriber, {#required String channel});
void send({#required String channel, Object parameter});
}
class MessagingServiceImpl implements MessagingService {
static final _map = <String, Map<String, void Function(Object)>>{};
#override
void subscribe(Object subscriber, {#required String channel, #required void Function(Object) action}) {
assert(subscriber != null);
assert(channel != null);
assert(channel.isNotEmpty);
assert(action != null);
if (!_map.containsKey(channel)) {
_map[channel] = {};
}
_map[channel].putIfAbsent(subscriber.hashCode.toString(), () => action);
}
#override
void send({#required String channel, Object parameter}) {
assert(channel != null);
assert(channel.isNotEmpty);
if (_map.containsKey(channel)) {
for (final action in _map[channel].values) {
action(parameter);
}
}
}
#override
void unsubscribe(Object subscriber, {#required String channel}) {
if (_map.containsKey(channel)) {
_map[channel].removeWhere((k, v) => k == subscriber.hashCode.toString());
}
}
}
I have searched everywhere but I haven't found the solution to my problem. I've tried all solutions involving changing the community name, certain different set classes and I still can't get the set to work for SNMP4j. The code executes fine and can be accessed from iReasoning MIB browser but will not change the value of the OID after the set executes. The SNMP classes and tester classes are below.
SNMPManager code:
public class SNMPManager {
Snmp snmp = null;
String address = null;
/**
* Constructor
*
* #param add
*/
public SNMPManager(String add) {
address = add;
}
public static void main(String[] args) throws IOException {
/**
* Port 161 is used for Read and Other operations Port 162 is used for the trap
* generation
*/
SNMPManager client = new SNMPManager("udp:127.0.0.1/161");
client.start();
/**
* OID - .1.3.6.1.2.1.1.1.0 => SysDec OID - .1.3.6.1.2.1.1.5.0 => SysName => MIB
* explorer will be usefull here, as discussed in previous article
*/
String sysDescr = client.getAsString(new OID(".1.3.6.1.2.1.1.1.0"));
System.out.println(sysDescr);
}
/**
* Start the Snmp session. If you forget the listen() method you will not get
* any answers because the communication is asynchronous and the listen() method
* listens for answers.
*
* #throws IOException
*/
public void start() throws IOException {
TransportMapping<?> transport = new DefaultUdpTransportMapping();
snmp = new Snmp(transport);
// Do not forget this line!
transport.listen();
}
/**
* Method which takes a single OID and returns the response from the agent as a
* String.
*
* #param oid
* #return
* #throws IOException
*/
public String getAsString(OID oid) throws IOException {
ResponseEvent event = get(new OID[] { oid });
return event.getResponse().get(0).getVariable().toString();
}
/**
* This method is capable of handling multiple OIDs
*
* #param oids
* #return
* #throws IOException
*/
public ResponseEvent get(OID oids[]) throws IOException {
PDU pdu = new PDU();
for (OID oid : oids) {
pdu.add(new VariableBinding(oid));
}
pdu.setType(PDU.GET);
ResponseEvent event = snmp.send(pdu, getTarget(), null);
if (event != null) {
return event;
}
throw new RuntimeException("GET timed out");
}
public ResponseEvent set(OID oid, String val) throws IOException {
PDU pdu = new PDU();
VariableBinding varBind = new VariableBinding(oid, new OctetString(val));
pdu.add(varBind);
pdu.setType(PDU.SET);
//pdu.setRequestID(new Integer32(1));
Target target = getTarget();
ResponseEvent event = snmp.set(pdu, target);
if (event != null) {
System.out.println("\nResponse:\nGot Snmp Set Response from Agent");
System.out.println("Snmp Set Request = " + event.getRequest().getVariableBindings());
PDU responsePDU = event.getResponse();
System.out.println("\nresponsePDU = " + responsePDU);
if (responsePDU != null) {
int errorStatus = responsePDU.getErrorStatus();
int errorIndex = responsePDU.getErrorIndex();
String errorStatusText = responsePDU.getErrorStatusText();
System.out.println("\nresponsePDU = " + responsePDU);
if (errorStatus == PDU.noError) {
System.out.println("Snmp Set Response = " + responsePDU.getVariableBindings());
} else {
System.out.println("errorStatus = " + responsePDU);
System.out.println("Error: Request Failed");
System.out.println("Error Status = " + errorStatus);
System.out.println("Error Index = " + errorIndex);
System.out.println("Error Status Text = " + errorStatusText);
}
}
return event;
}
throw new RuntimeException("SET timed out");
}
/**
* This method returns a Target, which contains information about where the data
* should be fetched and how.
*
* #return
*/
private Target getTarget() {
Address targetAddress = GenericAddress.parse(address);
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString("public"));
target.setAddress(targetAddress);
target.setRetries(2);
target.setTimeout(1500);
target.setVersion(SnmpConstants.version2c);
return target;
}
}
SNMPAgent code:
public class SNMPAgent extends BaseAgent {
private String address;
/**
*
* #param address
* #throws IOException
*/
public SNMPAgent(String address) throws IOException {
/**
* Creates a base agent with boot-counter, config file, and a CommandProcessor
* for processing SNMP requests. Parameters: "bootCounterFile" - a file with
* serialized boot-counter information (read/write). If the file does not exist
* it is created on shutdown of the agent. "configFile" - a file with serialized
* configuration information (read/write). If the file does not exist it is
* created on shutdown of the agent. "commandProcessor" - the CommandProcessor
* instance that handles the SNMP requests.
*/
super(new File("conf.agent"), new File("bootCounter.agent"),
new CommandProcessor(new OctetString(MPv3.createLocalEngineID())));
this.address = address;
}
/**
* Adds community to security name mappings needed for SNMPv1 and SNMPv2c.
*/
#Override
protected void addCommunities(SnmpCommunityMIB communityMIB) {
Variable[] com2sec = new Variable[] { new OctetString("public"), new OctetString("cpublic"), // security name
getAgent().getContextEngineID(), // local engine ID
new OctetString("public"), // default context name
new OctetString(), // transport tag
new Integer32(StorageType.nonVolatile), // storage type
new Integer32(RowStatus.active) // row status
};
MOTableRow<?> row = communityMIB.getSnmpCommunityEntry()
.createRow(new OctetString("public2public").toSubIndex(true), com2sec);
communityMIB.getSnmpCommunityEntry().addRow((SnmpCommunityEntryRow) row);
}
/**
* Adds initial notification targets and filters.
*/
#Override
protected void addNotificationTargets(SnmpTargetMIB arg0, SnmpNotificationMIB arg1) {
// TODO Auto-generated method stub
}
/**
* Adds all the necessary initial users to the USM.
*/
#Override
protected void addUsmUser(USM arg0) {
// TODO Auto-generated method stub
}
/**
* Adds initial VACM configuration.
*/
#Override
protected void addViews(VacmMIB vacm) {
vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString("cpublic"), new OctetString("v1v2group"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"), SecurityModel.SECURITY_MODEL_ANY,
SecurityLevel.NOAUTH_NOPRIV, MutableVACM.VACM_MATCH_EXACT, new OctetString("fullReadView"),
new OctetString("fullWriteView"), new OctetString("fullNotifyView"), StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"), new OctetString(),
VacmMIB.vacmViewIncluded, StorageType.nonVolatile);
}
/**
* Unregister the basic MIB modules from the agent's MOServer.
*/
#Override
protected void unregisterManagedObjects() {
// TODO Auto-generated method stub
}
/**
* Register additional managed objects at the agent's server.
*/
#Override
protected void registerManagedObjects() {
// TODO Auto-generated method stub
}
#SuppressWarnings("unchecked")
protected void initTransportMappings() throws IOException {
transportMappings = new TransportMapping[1];
Address addr = GenericAddress.parse(address);
TransportMapping<?> tm = TransportMappings.getInstance().createTransportMapping(addr);
transportMappings[0] = tm;
}
/**
* Start method invokes some initialization methods needed to start the agent
*
* #throws IOException
*/
public void start() throws IOException {
init();
// This method reads some old config from a file and causes
// unexpected behavior.
// loadConfig(ImportModes.REPLACE_CREATE);
addShutdownHook();
getServer().addContext(new OctetString("public"));
finishInit();
run();
sendColdStartNotification();
}
/**
* Clients can register the MO they need
*/
public void registerManagedObject(ManagedObject mo) {
try {
server.register(mo, null);
} catch (DuplicateRegistrationException ex) {
throw new RuntimeException(ex);
}
}
public void unregisterManagedObject(MOGroup moGroup) {
moGroup.unregisterMOs(server, getContext(moGroup));
}
}
MOCreator code:
public class MOCreator {
public static MOScalar<Variable> createReadOnly(OID oid,Object value ){
return new MOScalar<Variable>(oid,
MOAccessImpl.ACCESS_READ_WRITE,
getVariable(value));
}
private static Variable getVariable(Object value) {
if(value instanceof String) {
return new OctetString((String)value);
}
throw new IllegalArgumentException("Unmanaged Type: " + value.getClass());
}
}
Tester class:
public class TestSNMPAgent {
String siteOIDNumber = "1";
String dataOIDNumber = "1";
String sensorOIDNumber = "1";
OID newOIDdata = new OID("1.3.6.1.4.1.1234.5." + siteOIDNumber + ".2." + dataOIDNumber + "." + sensorOIDNumber + ".0");
OID newOIDdata2 = new OID("1.3.6.1.4.1.1234.5.1.2.1.2.0");
OID newOIDdata3 = new OID("1.3.6.1.4.1.1234.5.1.2.1.3.0");
public static void main(String[] args) throws IOException {
TestSNMPAgent client = new TestSNMPAgent("udp:127.0.0.1/161");
client.init();
}
SNMPAgent agent = null;
/**
* This is the client which we have created earlier
*/
SNMPManager client = null;
String address = null;
/**
* Constructor
*
* #param add
*/
public TestSNMPAgent(String add) {
address = add;
}
/**
* Initiates the testing of the SNMP Agent.
* #throws IOException
*/
private void init() throws IOException {
/*agent = new SNMPAgent("172.21.1.103/2001");*/
agent = new SNMPAgent("172.21.1.103/2010");
agent.start();
// Since BaseAgent registers some MIBs by default we need to unregister
// one before we register our own sysDescr. Normally you would
// override that method and register the MIBs that you need
agent.unregisterManagedObject(agent.getSnmpv2MIB());
//agent.registerManagedObject(MOCreator.createReadOnly(sysDescr,"This Description is set By KGrewe"));
agent.registerManagedObject(MOCreator.createReadOnly(newOIDdata, "50"));
agent.registerManagedObject(MOCreator.createReadOnly(newOIDdata2, "NaN"));
agent.registerManagedObject(MOCreator.createReadOnly(newOIDdata3, "3"));
SNMPManager client1 = new SNMPManager("udp:127.21.1.103/2010");
client1.start();
client1.set(newOIDdata, "30");
//System.out.println(newOIDdata);
// Get back Value which is set
//System.out.println(client1.getAsString(newOIDdata));
while(true) {
}
}
}
Thanks for the help in advance.
Netty 4.1 (on OpenJDK 1.6.0_32 and CentOS 6.4) message sending is strangely slow. According to the profiler, it is the DefaultChannelHandlerContext.writeAndFlush that makes the biggest percentage (60%) of the running time. Decoding process is not emphasized in the profiler. Small messages are being processed and maybe the bootstrap options are not set correctly (TCP_NODELAY is true and nothing improved)? DefaultEventExecutorGroup is used both in server and client to avoid blocking Netty's main event loop and to run 'ServerData' and 'ClientData' classes with business logic and sending of the messages is done from there through context.writeAndFlush(...). Is there a more proper/faster way? Using straight ByteBuf.writeBytes(..) serialization in the encoder and ReplayingDecoder in the decoder made no difference in encoding speed. Sorry for the lengthy code, neither 'Netty In Action' book nor the documentation helped.
JProfiler's call tree of the client side: http://i62.tinypic.com/dw4e43.jpg
The server class is:
public class NettyServer
{
EventLoopGroup incomingLoopGroup = null;
EventLoopGroup workerLoopGroup = null;
ServerBootstrap serverBootstrap = null;
int port;
DataServer dataServer = null;
DefaultEventExecutorGroup dataEventExecutorGroup = null;
DefaultEventExecutorGroup dataEventExecutorGroup2 = null;
public ChannelFuture serverChannelFuture = null;
public NettyServer(int port)
{
this.port = port;
DataServer = new DataServer(this);
}
public void run() throws Exception
{
incomingLoopGroup = new NioEventLoopGroup();
workerLoopGroup = new NioEventLoopGroup();
dataEventExecutorGroup = new DefaultEventExecutorGroup(5);
dataEventExecutorGroup2 = new DefaultEventExecutorGroup(5);
try
{
ChannelInitializer<SocketChannel> channelInitializer =
new ChannelInitializer<SocketChannel>()
{
#Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new MessageByteDecoder());
ch.pipeline().addLast(new MessageByteEncoder());
ch.pipeline().addLast(dataEventExecutorGroup, new DataServerInboundHandler(DataServer, NettyServer.this));
ch.pipeline().addLast(dataEventExecutorGroup2, new DataServerDataHandler(DataServer));
}
};
// bootstrap the server
serverBootstrap = new ServerBootstrap();
serverBootstrap.group(incomingLoopGroup, workerLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(channelInitializer)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024)
.childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
serverChannelFuture = serverBootstrap.bind(port).sync();
serverChannelFuture.channel().closeFuture().sync();
}
finally
{
incomingLoopGroup.shutdownGracefully();
workerLoopGroup.shutdownGracefully();
}
}
}
The client class:
public class NettyClient
{
Bootstrap clientBootstrap = null;
EventLoopGroup workerLoopGroup = null;
String serverHost = null;
int serverPort = -1;
ChannelFuture clientFutureChannel = null;
DataClient dataClient = null;
DefaultEventExecutorGroup dataEventExecutorGroup = new DefaultEventExecutorGroup(5);
DefaultEventExecutorGroup dataEventExecutorGroup2 = new DefaultEventExecutorGroup(5);
public NettyClient(String serverHost, int serverPort)
{
this.serverHost = serverHost;
this.serverPort = serverPort;
}
public void run() throws Exception
{
workerLoopGroup = new NioEventLoopGroup();
try
{
this.dataClient = new DataClient();
ChannelInitializer<SocketChannel> channelInitializer =
new ChannelInitializer<SocketChannel>()
{
#Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new MessageByteDecoder());
ch.pipeline().addLast(new MessageByteEncoder());
ch.pipeline().addLast(dataEventExecutorGroup, new ClientInboundHandler(dataClient, NettyClient.this)); ch.pipeline().addLast(dataEventExecutorGroup2, new ClientDataHandler(dataClient));
}
};
clientBootstrap = new Bootstrap();
clientBootstrap.group(workerLoopGroup);
clientBootstrap.channel(NioSocketChannel.class);
clientBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
clientBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
clientBootstrap.option(ChannelOption.TCP_NODELAY, true);
clientBootstrap.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);
clientBootstrap.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);
clientBootstrap.handler(channelInitializer);
clientFutureChannel = clientBootstrap.connect(serverHost, serverPort).sync();
clientFutureChannel.channel().closeFuture().sync();
}
finally
{
workerLoopGroup.shutdownGracefully();
}
}
}
The message class:
public class Message implements Serializable
{
public static final byte MSG_FIELD = 0;
public static final byte MSG_HELLO = 1;
public static final byte MSG_LOG = 2;
public static final byte MSG_FIELD_RESPONSE = 3;
public static final byte MSG_MAP_KEY_VALUE = 4;
public static final byte MSG_STATS_FILE = 5;
public static final byte MSG_SHUTDOWN = 6;
public byte msgID;
public byte msgType;
public String key;
public String value;
public byte method;
public byte id;
}
The decoder:
public class MessageByteDecoder extends ByteToMessageDecoder
{
private Kryo kryoCodec = new Kryo();
private int contentSize = 0;
#Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) //throws Exception
{
if (!buffer.isReadable() || buffer.readableBytes() < 4) // we need at least integer
return;
// read header
if (contentSize == 0) {
contentSize = buffer.readInt();
}
if (buffer.readableBytes() < contentSize)
return;
// read content
byte [] buf = new byte[contentSize];
buffer.readBytes(buf);
Input in = new Input(buf, 0, buf.length);
out.add(kryoCodec.readObject(in, Message.class));
contentSize = 0;
}
}
The encoder:
public class MessageByteEncoder extends MessageToByteEncoder<Message>
{
Kryo kryoCodec = new Kryo();
public MessageByteEncoder()
{
super(false);
}
#Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception
{
int offset = out.arrayOffset() + out.writerIndex();
byte [] inArray = out.array();
Output kryoOutput = new OutputWithOffset(inArray, inArray.length, offset + 4);
// serialize message content
kryoCodec.writeObject(kryoOutput, msg);
// write length of the message content at the beginning of the array
out.writeInt(kryoOutput.position());
out.writerIndex(out.writerIndex() + kryoOutput.position());
}
}
Client's business logic run in DefaultEventExecutorGroup:
public class DataClient
{
ChannelHandlerContext ctx;
// ...
public void processData()
{
// ...
while ((line = br.readLine()) != null)
{
// ...
process = new CountDownLatch(columns.size());
for(Column c : columns)
{
// sending column data to the server for processing
ctx.channel().eventLoop().execute(new Runnable() {
#Override
public void run() {
ctx.writeAndFlush(Message.createMessage(msgID, processID, c.key, c.value));
}});
}
// block until all the processed column fields of this row are returned from the server
process.await();
// write processed line to file ...
}
// ...
}
// ...
}
Client's message handling:
public class ClientInboundHandler extends ChannelInboundHandlerAdapter
{
DataClient dataClient = null;
// ...
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
// dispatch the message to the listeners
Message m = (Message) msg;
switch(m.msgType)
{
case Message.MSG_FIELD_RESPONSE: // message with processed data is received from the server
// decreases the 'process' CountDownLatch in the processData() method
dataClient.setProcessingResult(m.msgID, m.value);
break;
// ...
}
// forward the message to the pipeline
ctx.fireChannelRead(msg);
}
// ...
}
}
Server's message handling:
public class ServerInboundHandler extends ChannelInboundHandlerAdapter
{
private DataServer dataServer = null;
// ...
#Override
public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception
{
Message msg = (Message) obj;
switch(msg.msgType)
{
case Message.MSG_FIELD:
dataServer.processField(msg, ctx);
break;
// ...
}
ctx.fireChannelRead(msg);
}
//...
}
Server's business logic run in DefaultEventExecutorGroup:
public class DataServer
{
// ...
public void processField(final Message msg, final ChannelHandlerContext context)
{
context.executor().submit(new Runnable()
{
#Override
public void run()
{
String processedValue = (String) processField(msg.key, msg.value);
final Message responseToClient = Message.createResponseFieldMessage(msg.msgID, processedValue);
// send processed data to the client
context.channel().eventLoop().submit(new Runnable(){
#Override
public void run() {
context.writeAndFlush(responseToClient);
}
});
}
});
}
// ...
}
Please try using CentOS 7.0.
I've had similar problem:
The same Netty 4 program runs very fast on CentOS 7.0 (about 40k msg/s), but can't write more than about 8k msg/s on CentOS 6.3 and 6.5 (I haven't tried 6.4).
There is no need to submit stuff to the EventLoop. Just call Channel.writeAndFlush(...) directly in your DataClient and DataServer.
I followed the example to create a websocket server:
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
ServletContextHandler servletContextHandler = new ServletContextHandler(server, "/", true, false);
EventServlet es = injector.getInstance(EventServlet.class);
servletContextHandler.addServlet(new ServletHolder(es), "/events/*");
The EventServlet class looks like:
public class EventServlet extends WebSocketServlet {
#Override
public void configure(WebSocketServletFactory factory) {
factory.getPolicy().setIdleTimeout(10000);
factory.register(EventSocketCache.class);
}
}
The EventSocketCache looks like:
public class EventSocketCache extends WebSocketAdapter {
private static int i = 0;
private static int counter = 0;
private static Map<Integer, Session> sessionMap = new HashMap<>();
private final Cache<String, String> testCache;
#Inject
public EventSocketCache(Cache<String, String> testCache) {
this.testCache = testCache;
}
#Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
System.out.println("Socket Connected: " + session);
System.out.println("Connect: " + session.getRemoteAddress().getAddress());
try {
session.getRemote().sendString("Hello Webbrowser");
session.setIdleTimeout(50000);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("Received TEXT message: " + message);
}
#Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
byte[] newData = Arrays.copyOfRange(payload, offset, offset + len);
try {
Common.Success success = Common.Success.parseFrom(newData);
System.err.println("------> " + success.getIsSuccess());
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
#Override
public void onWebSocketClose(int statusCode, String reason) {
System.err.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
// Remove from the list here....
super.onWebSocketClose(statusCode, reason);
System.out.println("Socket Closed: [" + statusCode + "] " + reason);
}
#Override
public void onWebSocketError(Throwable cause) {
System.err.println("######################################");
super.onWebSocketError(cause);
cause.printStackTrace(System.err);
}
}
Now when I use my client and send a request, I end up getting:
org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.validateResponse(UpgradeConnection.java:249)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.read(UpgradeConnection.java:181)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.onFillable(UpgradeConnection.java:126)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
at java.lang.Thread.run(Thread.java:722)
Disconnected from the target VM, address: '127.0.0.1:63256', transport: 'socket'
java.util.concurrent.ExecutionException: org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.util.FuturePromise.get(FuturePromise.java:123)
at com.gamecenter.websockets.EventClient.main(EventClient.java:25)
Caused by: org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.validateResponse(UpgradeConnection.java:249)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.read(UpgradeConnection.java:181)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.onFillable(UpgradeConnection.java:126)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
at java.lang.Thread.run(Thread.java:722)
It seems as there is a problem creating an instance of EventSocketCache; if I don't have the constructor in there, everything works fine.
I'd like to know how to instantiate EventSocketCache properly and register it with EventServlet so things work?
I guess I've found a solution for your problem. You have to use a WebSocketCreator in your WebSocketServlet:
public class MenuServlet extends WebSocketServlet {
#Inject
private Injector injector;
#Override
public void configure(WebSocketServletFactory webSocketServletFactory) {
// Register your Adapater
webSocketServletFactory.register(MenuSocket.class);
// Get the current creator (for reuse)
final WebSocketCreator creator = webSocketServletFactory.getCreator();
// Set your custom Creator
webSocketServletFactory.setCreator(new WebSocketCreator() {
#Override
public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) {
Object webSocket = creator.createWebSocket(servletUpgradeRequest, servletUpgradeResponse);
// Use the object created by the default creator and inject your members
injector.injectMembers(webSocket);
return webSocket;
}
});
}
}
there you can inject your members into your WebSocketAdapater. This actually worked for me.