|
JavaEE7+Websockets+GlassFish4打造聊天室
JavaEE7已经发布很久了,新增加了很多新的功能和特性,如新增或更新了不少的JSR标准。其中特别受到关注的是Websockets。它的一个好处之一是减少了不必要的网络流量。它主要是用于在客户机和服务器之间建立单一的双向连接。
在客户机和服务器之间建立单一的双向连接,这就意味着客户只需要发送一个请求到服务端,那么服务端则会进行处理,处理好后则将其返回给客户端,客户端则可以在等待这个时间继续去做其他工作,整个过程是异步的。在本系列教程中,将指导用户如何在JAVAEE7的容器GlassFish4中,使用JAVAEE7中的全新的解析JsonAPI(JSR-353),以及综合运用jQuery和Bootstrap。本文要求读者有一定的HTML5Websocket的基础原理知识。
效果图
我们先来看下在完成这个教程后的效果图,如下所示:
准备工作
我们使用的是JDK7和MAVN3进行库的构建工作,首先看pom.xml中关于JaveEE7的部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
${project.build.directory}/endorsed
UTF-8
javax
javaee-api
7.0
provided
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.7
1.7
${endorsed.dir}
org.apache.maven.plugins
maven-war-plugin
2.3
false
org.apache.maven.plugins
maven-dependency-plugin
2.6
[..]
同时,为了能使用GlassFish4,需要增加如下的插件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 plugin>
org.glassfish.embedded
maven-embedded-glassfish-plugin
4.0
embedded-glassfish
${basedir}/target/${project.artifactId}-${project.version}.war
true
8080
${project.artifactId}
hascode
deploy
设置Websocket的Endpoint
我们先来看服务端Websocket的代码如下,然后再做进一步解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 packagecom.hascode.tutorial;
importjava.io.IOException;
importjava.util.logging.Level;
importjava.util.logging.Logger;
importjavax.websocket.EncodeException;
importjavax.websocket.OnMessage;
importjavax.websocket.OnOpen;
importjavax.websocket.Session;
importjavax.websocket.server.PathParam;
importjavax.websocket.server.ServerEndpoint;
@ServerEndpoint(value="/chat/{room}",encoders=ChatMessageEncoder.class,decoders=ChatMessageDecoder.class)
publicclassChatEndpoint{
privatefinalLoggerlog=Logger.getLogger(getClass().getName());
@OnOpen
publicvoidopen(finalSessionsession,@PathParam("room")finalStringroom){
log.info("sessionopenendwww.hunanwang.netandboundtoroom:"+room);
session.getUserProperties().put("room",room);
}
@OnMessage
publicvoidonMessage(finalSessionsession,finalChatMessagechatMessage){
Stringroom=(String)session.getUserProperties().get("room");
try{
for(Sessions:session.getOpenSessions()){
if(s.isOpen()
&&room.equals(s.getUserProperties().get("room"))){
s.getBasicRemote().sendObject(chatMessage);
}
}
}catch(IOException|EncodeExceptione){
log.log(Level.WARNING,"onMessagefailed",e);
}
}
} 下面分析下上面的代码:
使用@ServerEndpoint定义一个新的endpoint,其中的值指定了URL并且可以使用PathParams参数,就象在JAX-RS中的用法一样。
所以值“/chat/{room}”允许用户通过如下形式的URL去连接某个聊天室:ws://0.0.0.0:8080/hascode/chat/java
在大括号中的值(即room),可以通过使用javax.websocket.www.visa158.com.server.PathParam,在endpoint的生命周期回调方法中以参数的方式注入。
此外,我们要使用一个编码和解码的类,因为我们使用的是一个DTO形式的类,用于在服务端和客户端传送数据。
当用户第一次连接到服务端,输入要进入聊天室的房号,则这个房号以参数的方式注入提交,并且使用session.getUserProperties将值保存在用户的属性map中。
当一个聊天参与者通过tcp连接发送信息到服务端,则循环遍历所有已打开的session,每个session被绑定到指定的聊天室中,并且接收编码和解码的信息。
如果我们想发送简单的文本信息或和二进制格式的信息,则可以使用session.getBasicRemote().sendBinary()或session.getBasicRemote().sendText()
接下来我们看下用于代表信息传递实体(DTO:DataTransferObject)的代码,如下:
1
2
3
4
5
6
7
8
9
10
11 packagecom.hascode.tutorial;
importjava.util.Date;
publicclassChatMessage{
privateStringmessage;
privateStringsender;
privateDatereceived;
//其他getter,setter方法
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 packagecom.hascode.tutorial;
importjava.io.StringReader;
importjava.util.Date;
importjavax.json.Json;
importjavax.json.JsonObject;
importjavax.websocket.DecodeException;
importjavax.websocket.Decoder;
importjavax.websocket.EndpointConfig;
publicclassChatMessageDecoderimplementsDecoder.Text{
@Override
publicvoidinit(finalEndpointConfigconfig){
}
@Override
publicvoiddestroy(){
}
@Override
publicChatMessagedecode(finalStringtextMessage)throwsDecodeException{
ChatMessagechatMessage=newChatMessage();
JsonObjectobj=Json.createReader(newStringReader(textMessage))
.readObject();
chatMessage.setMessage(obj.getString("message"));
chatMessage.setSender(obj.getString("sender"));
chatMessage.setReceived(newDate());
returnchatMessage;
}
@Override
publicbooleanwillDecode(finalStrings){
returntrue;
}
} 同样再看下编码类的代码,这个类相反,是将ChatMessage类转换为Json格式,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 packagecom.hascode.tutorial;
importjavax.json.Json;
importjavax.websocket.EncodeException;
importjavax.websocket.Encoder;
importjavax.websocket.EndpointConfig;
publicclassChatMessageEncoderimplementsEncoder.Text{
@Override
publicvoidinit(finalEndpointConfigconfig){
}
@Override
publicvoiddestroy(){
}
@Override
publicStringencode(finalChatMessagechatMessage)throwsEncodeException{
returnJson.createObjectBuilder()
.add("message",chatMessage.getMessage())
.add("sender",chatMessage.getSender())
.add("received",chatMessage.getReceived().toString()).build()
.toString();
}
} 这里可以看到JSR-353的强大威力,只需要调用Json.createObjectBuilder就可以轻易把一个DTO对象转化为JSON了。
通过Bootstrap、Javacsript搭建简易客户端
最后,我们综合运用著名的Bootstrap、jQuery框架和Javascript设计一个简易的客户端。我们在src/main/weapp目录下新建立index.html文件,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
[..]
Chatsignin
Nickname class="input-block-level"placeholder="Nickname"id="nickname">
Chatroom id="chatroom">
id="enterRoom">Signin
|
| |