模拟聊天的例子
在Professional Java for Web Applications中给通书的例子中给出如何利用WebSocket实现聊天室的例子,作为案例,学习一下。用户可以进入聊天室聊天,HTTPSession终结,WebSocket也相应关闭。因此小例子中Server Endpoint是承载在一个HttpSessionListener之上。
在sessionDestroyed()中,关闭WebScoket,向聊天的对方提示状态。
Endpoint的annotation
我们先看看@ServerEndpoint和@ClientEndpoint的例子
@ServerEndpoint(
value = "/sample",
decoders = ChatDecoder.class,
encoders = DisconnectResponseEncoder.class,
subprotocols = {"subprtotocol1", "subprotocol2"},
configurator = Configurator.class )
@ClientEndpoint(
decoders = SampleDecoder.class,
encoders = SampleEncoder.class,
subprotocols = {"subprtotocol1", "subprotocol2"},
configurator = ClientConfigurator.class)
subprotocols就是握手过程中HTTP header Sec-WebSocket-Protocol。网上有很个很好的教程 https://tyrus.java.net/documentation/1.4/index/websocket-api.html
configurator
//我们重点学习这种写法:在@ServerEndpoint或@ClientEndpoint中
// (1)指定消息的编码器和解码器,可以在onMessage接口中直接获得对象,通过RemoteEndpoint.Basic|Async.sendObject()发送对象
// (2)设定configurator,通过对ServerEndpointConfig.Configurator的继承,可以将一些request的属性或参数,放入WebSocket.Session中
@ServerEndpoint(value = "/chat/{sessionId}",
encoders = ChatMessageCodec.class,
decoders = ChatMessageCodec.class,
configurator = ChatEndpoint.EndpointConfigurator.class)
@WebListener
public class ChatEndpoint implements HttpSessionListener {
private static final String HTTP_SESSION_PROPERTY = "cn.wei.flowingflying.chat.HTTP_SESSION";
... ...
@OnOpen
public void onOpen(Session session, @PathParam("sessionId") long sessionId){
//【2】获取通过configurator存放在Session中的属性
HttpSession httpSession = (HttpSession)session.getUserProperties().get(ChatEndpoint.HTTP_SESSION_PROPERTY);
... ...
}
@OnMessage
public void onMessage(Session session, ChatMessage message) {
... ...
}
@OnClose
public void onClose(Session session, CloseReason reason){
... ...
}
@OnError
public void onError(Session session, Throwable e){
... ...
}
/**
* @see HttpSessionListener#sessionCreated(HttpSessionEvent)
*/
public void sessionCreated(HttpSessionEvent se) { /* do nothing */ }
/**
* @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
*/
public void sessionDestroyed(HttpSessionEvent event) {
... ...
}
//【1】设定configurator:本例子将HttpSession放入Session的属性中。modifyHandshake也就是握手阶段HTTP交换。
public static class EndpointConfigurator extends ServerEndpointConfig.Configurator{
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(config, request, response);
config.getUserProperties().put(ChatEndpoint.HTTP_SESSION_PROPERTY, request.getHttpSession());
}
}
}
encoder和decoder
例子中消息ChatMessage将采用JSON的格式进行传递。下面演示了一个含有enum类型的处理:
public class ChatMessage {
private OffsetDateTime timestamp;
private Type type;
private String user;
private String content;
... 作为一个POJO的getters 和 setters,略...
public static enum Type {
STARTED, JOINED, ERROR, LEFT, TEXT
}
}
下面是一个消息的例子
{"user":"wei","content":"Hello, world!","timestamp":1490235820.560000000,"type":"JOINED"}
Encoder和decoder虽然有文本和二进制两种方式,本例子采用二进制方式,虽然JSON常使用String格式。对于String的方式,可以参考Websockets: using Encoders and Decoders
//实现Encode.BinaryStream和Decoder.BinaryStream,其他格式还有 Encode.Binary/Decoder.Binary,Encoder.Text/Decoder.Text 或者 Encoder.TextStream/Decoder.TextStream
public class ChatMessageCodec implements Encoder.BinaryStream<ChatMessage>,Decoder.BinaryStream<ChatMessage>{
//【1】定义JSON的Mapper及其相关属性
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.findAndRegisterModules();
MAPPER.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
}
@Override
public void init(EndpointConfig config) { }
@Override
public void destroy() { }
//【2】实现decoder
@Override
public ChatMessage decode(InputStream inputStream) throws DecodeException, IOException {
try{
return ChatMessageCodec.MAPPER.readValue(inputStream, ChatMessage.class);
}catch(JsonGenerationException | JsonMappingException e){
throw new DecodeException((ByteBuffer)null, e.getMessage(), e);
}
}
//【3】实现encoder
@Override
public void encode(ChatMessage chatMessage, OutputStream outputStream) throws EncodeException, IOException {
try{
ChatMessageCodec.MAPPER.writeValue(outputStream, chatMessage);
}catch(JsonGenerationException | JsonMappingException e){
throw new EncodeException(chatMessage, e.getMessage(), e);
}
}
}
Client:发送JSON的二进制消息
下面是相关的JavaScript:
send = function() {
if(server == null) {
... ...
} else {
var message = {
timestamp: new Date(), type: 'TEXT', user: username,
content: messageArea.get(0).value
};
try {
var json = JSON.stringify(message);
var length = json.length;
var buffer = new ArrayBuffer(length);
var array = new Uint8Array(buffer);
for(var i = 0; i < length; i++) {
array[i] = json.charCodeAt(i);
}
server.send(buffer);
messageArea.get(0).value = '';
} catch(error) {
modalErrorBody.text(error);
modalError.modal('show');
}
}
};
相关链接: 我的Professional Java for Web Applications相关文章