The Performance Zone is supported by New Relic and AppDynamics. Both are leaders in the APM space with high-profile customers and massive cost reductions for those users.
I have been playing around with Tyrus,
the reference implementation of the JSR 356 WebSocket for Java spec.
Because I was looking at test tooling I was interested in running both
the client and the server side in Java. So no HTML5 in this blog post I
am afraid.
In this example we want to sent JSON back and forth and because I am old fashioned like that I want to be able to bind to a POJO object. I am going to use Jackson for this so my maven file looks like this:
So the first things we need to do is to define an implementations of the Encode/Decoder interfaces to do this work for us. This is going to do some simple reflection to workout what the bean class is. Like with JAX-WS it is easier to put them on the same class. Note that we use the streaming version of the interface and are only handling text content. (Ignoring the ability to send binary data for the moment)
The bean class is really quite simple with a static subclass of the Coder that we can use later.
So new we need to implement our server endpoint, you can go one of two
way either annotating a POJO or extending Endpoint. I am going with the
first for the server and the second for the client. Really all this
service does is to post the message back to the client. Note the
registration of the encode and decoder. The same class in this case.
Lets look at a client bean, this time extending the standard Endpoint
class and adding a specific listener for a message. In this case when
the message is received the connection is simply closed to make our test
case simple. In the real world managing this connection would obviously
be more complicated.
Now running the WebSocket standalone is really quite straightforward
with Tyrus, you simple instantiate a Server and start it. Be aware this
starts daemon threads so you need to make sure if this is in a main
method that you do something to keep the JVM alive.
So the client is relatively simple; but as we are doing the declarative
method we need to explicitly register the encoders and decoders when
registering the client class.
Now the output of this looks like the following:
Interestingly the first time this is run the there is a pause, I suspect this is due to Jackson setting itself up but I haven't had time to profile. I did find that this long delay on occurred on the first post - although obviously this is going to be slower than just passing plain text messages in general. Whether the different is significant to you depends on your application.
It would be interesting to compare the performance of the plain text with a JSON stream API such as that provided by the new JSR and of course the version that binds those values to a JSON POJO. Something for another day perhaps.
Published at DZone with permission of Gerard Davison, author and DZone MVB. (source)In this example we want to sent JSON back and forth and because I am old fashioned like that I want to be able to bind to a POJO object. I am going to use Jackson for this so my maven file looks like this:
01.
<dependencies>
02.
<dependency>
03.
<groupId>javax.websocket</groupId>
04.
<artifactId>javax.websocket-api</artifactId>
05.
<version>
1.0
-rc3</version>
06.
</dependency>
07.
08.
<dependency>
09.
<groupId>org.glassfish.tyrus</groupId>
10.
<artifactId>tyrus-client</artifactId>
11.
<version>
1.0
-rc3</version>
12.
</dependency>
13.
14.
<dependency>
15.
<groupId>org.glassfish.tyrus</groupId>
16.
<artifactId>tyrus-server</artifactId>
17.
<version>
1.0
-rc3</version>
18.
</dependency>
19.
20.
<dependency>
21.
<groupId>org.glassfish.tyrus</groupId>
22.
<artifactId>tyrus-container-grizzly</artifactId>
23.
<version>
1.0
-rc3</version>
24.
</dependency>
25.
26.
27.
<dependency>
28.
<groupId>com.fasterxml.jackson.core</groupId>
29.
<artifactId>jackson-databind</artifactId>
30.
<version>
2.2
.
0
</version>
31.
</dependency>
32.
33.
<dependency>
34.
<groupId>com.fasterxml.jackson.core</groupId>
35.
<artifactId>jackson-annotations</artifactId>
36.
<version>
2.2
.
0
</version>
37.
</dependency>
38.
39.
<dependency>
40.
<groupId>com.fasterxml.jackson.core</groupId>
41.
<artifactId>jackson-core</artifactId>
42.
<version>
2.2
.
0
</version>
43.
</dependency>
44.
45.
</dependencies>
So the first things we need to do is to define an implementations of the Encode/Decoder interfaces to do this work for us. This is going to do some simple reflection to workout what the bean class is. Like with JAX-WS it is easier to put them on the same class. Note that we use the streaming version of the interface and are only handling text content. (Ignoring the ability to send binary data for the moment)
01.
package
websocket;
02.
03.
import
com.fasterxml.jackson.databind.ObjectMapper;
04.
05.
import
java.io.IOException;
06.
import
java.io.Reader;
07.
import
java.io.Writer;
08.
09.
import
java.lang.reflect.ParameterizedType;
10.
import
java.lang.reflect.Type;
11.
12.
import
javax.websocket.DecodeException;
13.
import
javax.websocket.Decoder;
14.
import
javax.websocket.EncodeException;
15.
import
javax.websocket.Encoder;
16.
import
javax.websocket.EndpointConfig;
17.
18.
public
abstract
class
JSONCoder<T>
19.
implements
Encoder.TextStream<T>, Decoder.TextStream<T>{
20.
21.
22.
private
Class<T> _type;
23.
24.
// When configured my read in that ObjectMapper is not thread safe
25.
//
26.
private
ThreadLocal<ObjectMapper> _mapper =
new
ThreadLocal<ObjectMapper>() {
27.
28.
@Override
29.
protected
ObjectMapper initialValue() {
30.
return
new
ObjectMapper();
31.
}
32.
};
33.
34.
35.
@Override
36.
public
void
init(EndpointConfig endpointConfig) {
37.
38.
ParameterizedType $thisClass = (ParameterizedType)
this
.getClass().getGenericSuperclass();
39.
Type $T = $thisClass.getActualTypeArguments()[
0
];
40.
if
($T
instanceof
Class) {
41.
_type = (Class<T>)$T;
42.
}
43.
else
if
($T
instanceof
ParameterizedType) {
44.
_type = (Class<T>)((ParameterizedType)$T).getRawType();
45.
}
46.
}
47.
48.
@Override
49.
public
void
encode(T object, Writer writer)
throws
EncodeException, IOException {
50.
_mapper.get().writeValue(writer, object);
51.
}
52.
53.
@Override
54.
public
T decode(Reader reader)
throws
DecodeException, IOException {
55.
return
_mapper.get().readValue(reader, _type);
56.
}
57.
58.
@Override
59.
public
void
destroy() {
60.
61.
}
62.
63.
}
01.
package
websocket;
02.
03.
public
class
EchoBean {
04.
05.
06.
public
static
class
EchoBeanCode
extends
07.
JSONCoder<EchoBean> {
08.
09.
}
10.
11.
12.
private
String _message;
13.
private
String _reply;
14.
15.
16.
public
EchoBean() {
17.
18.
}
19.
20.
public
EchoBean(String _message) {
21.
super
();
22.
this
._message = _message;
23.
}
24.
25.
26.
public
void
setMessage(String _message) {
27.
this
._message = _message;
28.
}
29.
30.
public
String getMessage() {
31.
return
_message;
32.
}
33.
34.
35.
public
void
setReply(String _reply) {
36.
this
._reply = _reply;
37.
}
38.
39.
public
String getReply() {
40.
return
_reply;
41.
}
42.
43.
}
01.
package
websocket;
02.
03.
import
java.io.IOException;
04.
05.
import
javax.websocket.EncodeException;
06.
import
javax.websocket.EndpointConfig;
07.
import
javax.websocket.OnMessage;
08.
import
javax.websocket.OnOpen;
09.
import
javax.websocket.Session;
10.
import
javax.websocket.server.ServerEndpoint;
11.
import
static
java.lang.System.out;
12.
13.
@ServerEndpoint
(value=
"/echo"
,
14.
encoders = {EchoBean.EchoBeanCode.
class
},
15.
decoders = {EchoBean.EchoBeanCode.
class
})
16.
public
class
EchoBeanService
17.
{
18.
19.
@OnMessage
20.
public
void
echo (EchoBean bean, Session peer)
throws
IOException, EncodeException {
21.
//
22.
bean.setReply(
"Server says "
+ bean.getMessage());
23.
out.println(
"Sending message to client"
);
24.
peer.getBasicRemote().sendObject(bean);
25.
}
26.
27.
@OnOpen
28.
public
void
onOpen(
final
Session session, EndpointConfig endpointConfig) {
29.
out.println(
"Server connected "
+ session +
" "
+ endpointConfig);
30.
}
31.
}
01.
package
websocket;
02.
03.
import
java.io.IOException;
04.
05.
import
javax.websocket.ClientEndpoint;
06.
import
javax.websocket.CloseReason;
07.
import
javax.websocket.EncodeException;
08.
import
javax.websocket.Endpoint;
09.
import
javax.websocket.EndpointConfig;
10.
import
javax.websocket.MessageHandler;
11.
import
javax.websocket.Session;
12.
13.
import
static
java.lang.System.out;
14.
15.
@ClientEndpoint
(encoders = {EchoBean.EchoBeanCode.
class
},
16.
decoders = {EchoBean.EchoBeanCode.
class
})
17.
public
class
EchoBeanClient
18.
extends
Endpoint
19.
{
20.
public
void
onOpen(
final
Session session, EndpointConfig endpointConfig) {
21.
22.
out.println(
"Client Connection open "
+ session +
" "
+ endpointConfig);
23.
24.
// Add a listener to capture the returning event
25.
//
26.
27.
session.addMessageHandler(
new
MessageHandler.Whole<echobean>() {
28.
29.
@Override
30.
public
void
onMessage(EchoBean bean) {
31.
out.println(
"Message from server : "
+ bean.getReply());
32.
33.
out.println(
"Closing connection"
);
34.
try
{
35.
session.close(
new
CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,
"All fine"
));
36.
}
catch
(IOException e) {
37.
e.printStackTrace();
38.
}
39.
}
40.
});
41.
42.
// Once we are connected we can now safely send out initial message to the server
43.
//
44.
45.
out.println(
"Sending message to server"
);
46.
try
{
47.
EchoBean bean =
new
EchoBean(
"Hello"
);
48.
session.getBasicRemote().sendObject(bean);
49.
}
catch
(IOException e) {
50.
e.printStackTrace();
51.
}
catch
(EncodeException e) {
52.
e.printStackTrace();
53.
}
54.
55.
}
56.
}
57.
</echobean>
1.
import
org.glassfish.tyrus.server.Server;
2.
3.
Server server =
new
Server(
"localhost"
,
8025
,
"/"
, EchoBeanService.
class
);
4.
server.start();
01.
import
javax.websocket.ClientEndpointConfig;
02.
import
javax.websocket.Decoder;
03.
import
javax.websocket.Encoder;
04.
import
javax.websocket.Session;
05.
06.
import
org.glassfish.tyrus.client.ClientManager;
07.
08.
09.
// Right now we have to create a client, which will send a message then close
10.
// when it has received a reply
11.
//
12.
13.
ClientManager client = ClientManager.createClient();
14.
EchoBeanClient beanClient =
new
EchoBeanClient();
15.
16.
Session session = client.connectToServer(
17.
beanClient,
18.
ClientEndpointConfig.Builder.create()
19.
.encoders(Arrays.<Class<?
extends
Encoder>>asList(EchoBean.EchoBeanCode.
class
))
20.
.decoders(Arrays.<Class<?
extends
Decoder>>asList(EchoBean.EchoBeanCode.
class
))
21.
.build(),
22.
URI.create(
"ws://localhost:8025/echo"
));
23.
24.
25.
// Wait until things are closed down
26.
27.
while
(session.isOpen()) {
28.
out.println(
"Waiting"
);
29.
TimeUnit.MILLISECONDS.sleep(
10
);
30.
}
01.
Server connected SessionImpl{uri=/echo, id=
'e7739cc8-1ce5-4c26-ad5f-88a24c688799'
, endpoint=EndpointWrapper{endpointClass=
null
, endpoint=org.glassfish.tyrus.core.AnnotatedEndpoint
@1ce5bc9
, uri=
'/echo'
, contextPath=
'/'
}} javax.websocket.server.DefaultServerEndpointConfig
@ec120d
02.
Waiting
03.
Client Connection open SessionImpl{uri=ws:
//localhost:8025/echo,
id='7428be2b-6f8a-4c40-a0c4-b1c8b22e1338',
endpoint=EndpointWrapper{endpointClass=null,
endpoint=websocket.EchoBeanClient@404c85, uri='ws://localhost:8025/echo', contextPath='ws://localhost:8025/echo'}} javax.websocket.DefaultClientEndpointConfig@15fdf14
04.
Sending message to server
05.
Waiting
06.
Waiting
07.
Waiting
08.
Waiting
09.
Waiting
10.
Waiting
11.
Waiting
12.
Waiting
13.
Waiting
14.
Waiting
15.
Sending message to client
16.
Message from server : Server says Hello
17.
Closing connection
18.
Waiting
Interestingly the first time this is run the there is a pause, I suspect this is due to Jackson setting itself up but I haven't had time to profile. I did find that this long delay on occurred on the first post - although obviously this is going to be slower than just passing plain text messages in general. Whether the different is significant to you depends on your application.
It would be interesting to compare the performance of the plain text with a JSON stream API such as that provided by the new JSR and of course the version that binds those values to a JSON POJO. Something for another day perhaps.
(Note:
Opinions expressed in this article and its replies are the opinions of
their respective authors and not those of DZone, Inc.)
Scalability and better performance are constant concerns for the developer and operations manager. New Relic and AppDynamics are dedicated to performance education as the supporters of the Performance Zone. Try both AppDynamics' free lite performance tool for Java & .NET, or New Relic's free lite version to see which tool is the solution for your organization. One thing you can't afford, is no monitoring at all.
No comments:
Post a Comment