JavaEE7+Websockets+GlassFish4打造聊天室

前端技术 2023/09/03 Java

在客户机和服务器之间建立单一的双向连接,这就意味着客户只需要发送一个请求到服务端,那么服务端则会进行处理,处理好后则将其返回给客户端,客户端则可以在等待这个时间继续去做其他工作,整个过程是异步的。在本系列教程中,将指导用户如何在JAVA EE 7的容器GlassFish 4中,使用JAVA EE 7中的全新的解析Json API(JSR-353),以及综合运用jQuery和Bootstrap。本文要求读者有一定的HTML 5 Websocket的基础原理知识。

效果图

我们先来看下在完成这个教程后的效果图,如下所示:

准备工作

我们使用的是JDK 7 和MAVN 3进行库的构建工作,首先看pom.xml中关于Jave EE 7的部分:

 <properties> 
 <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> 
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
</properties> 
 
<dependencies> 
 <dependency> 
  <groupId>javax</groupId> 
  <artifactId>javaee-api</artifactId> 
  <version>7.0</version> 
  <scope>provided</scope> 
 </dependency> 
</dependencies> 
 
<build> 
 <plugins> 
  <plugin> 
   <groupId>org.apache.maven.plugins</groupId> 
   <artifactId>maven-compiler-plugin</artifactId> 
   <version>3.1</version> 
   <configuration> 
    <source>1.7</source> 
    <target>1.7</target> 
    <compilerArguments> 
     <endorseddirs>${endorsed.dir}</endorseddirs> 
    </compilerArguments> 
   </configuration> 
  </plugin> 
  <plugin> 
   <groupId>org.apache.maven.plugins</groupId> 
   <artifactId>maven-war-plugin</artifactId> 
   <version>2.3</version> 
   <configuration> 
    <failOnMissingWebXml>false</failOnMissingWebXml> 
   </configuration> 
  </plugin> 
  <plugin> 
   <groupId>org.apache.maven.plugins</groupId> 
   <artifactId>maven-dependency-plugin</artifactId> 
   <version>2.6</version> 
   [..] 
  </plugin> 
 </plugins> 
</build> 

同时,为了能使用GlassFish 4,需要增加如下的插件:

plugin> 
 <groupId>org.glassfish.embedded</groupId> 
 <artifactId>maven-embedded-glassfish-plugin</artifactId> 
 <version>4.0</version> 
 <configuration> 
  <goalPrefix>embedded-glassfish</goalPrefix> 
  <app>${basedir}/target/${project.artifactId}-${project.version}.war</app> 
  <autoDelete>true</autoDelete> 
  <port>8080</port> 
  <name>${project.artifactId}</name> 
  <contextRoot>hascode</contextRoot> 
 </configuration> 
 <executions> 
  <execution> 
   <goals> 
    <goal>deploy</goal> 
   </goals> 
  </execution> 
 </executions> 
</plugin> 

设置Websocket的Endpoint

我们先来看服务端Websocket的代码如下,然后再做进一步解析:

package com.hascode.tutorial; 
 
import java.io.IOException; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
 
import javax.websocket.EncodeException; 
import javax.websocket.OnMessage; 
import javax.websocket.OnOpen; 
import javax.websocket.Session; 
import javax.websocket.server.PathParam; 
import javax.websocket.server.ServerEndpoint; 
 
@ServerEndpoint(value = \"/chat/{room}\", encoders = ChatMessageEncoder.class, decoders = ChatMessageDecoder.class) 
public class ChatEndpoint { 
 private final Logger log = Logger.getLogger(getClass().getName()); 
 
 @OnOpen 
 public void open(final Session session, @PathParam(\"room\") final String room) { 
  log.info(\"session openend and bound to room: \" + room); 
  session.getUserProperties().put(\"room\", room); 
 } 
 
 @OnMessage 
 public void onMessage(final Session session, final ChatMessage chatMessage) { 
  String room = (String) session.getUserProperties().get(\"room\"); 
  try { 
   for (Session s : session.getOpenSessions()) { 
    if (s.isOpen() 
      && room.equals(s.getUserProperties().get(\"room\"))) { 
     s.getBasicRemote().sendObject(chatMessage); 
    } 
   } 
  } catch (IOException | EncodeException e) { 
   log.log(Level.WARNING, \"onMessage failed\", e); 
  } 
 } 
} 

下面分析下上面的代码:

使用@ ServerEndpoint定义一个新的endpoint,其中的值指定了URL并且可以使用PathParams参数,就象在JAX-RS中的用法一样。

所以值“/chat/{room}”允许用户通过如下形式的URL去连接某个聊天室:ws://0.0.0.0:8080/hascode/chat/java

在大括号中的值(即room),可以通过使用javax.websocket.server.PathParam,在endpoint的生命周期回调方法中以参数的方式注入。

此外,我们要使用一个编码和解码的类,因为我们使用的是一个DTO形式的类,用于在服务端和客户端传送数据。

当用户第一次连接到服务端,输入要进入聊天室的房号,则这个房号以参数的方式注入提交,并且使用session.getUserProperties将值保存在用户的属性map中。

当一个聊天参与者通过tcp连接发送信息到服务端,则循环遍历所有已打开的session,每个session被绑定到指定的聊天室中,并且接收编码和解码的信息。

如果我们想发送简单的文本信息或和二进制格式的信息,则可以使用session.getBasicRemote().sendBinary() 或session.getBasicRemote().sendText()

接下来我们看下用于代表信息传递实体(DTO:Data Transfer Object)的代码,如下:

package com.hascode.tutorial; 
 
import java.util.Date; 
 
public class ChatMessage { 
 private String message; 
 private String sender; 
 private Date received; 
 
 // 其他getter,setter方法 
} 

聊天消息的转换

在这个应用中,将编写一个编码和解码类,用于在聊天信息和JSON格式间进行转换。

先来看下解码类的实现,这将会把传递到服务端的聊天信息转换为ChatMessage实体类。在这里,使用的是Java API for JSON Processing(JSR353)规范去将JSON格式的信息转换为实体类,代码如下,其中重写的willDecode方法,这里默认返回为true。

package com.hascode.tutorial; 
 
import java.io.StringReader; 
import java.util.Date; 
 
import javax.json.Json; 
import javax.json.JsonObject; 
import javax.websocket.DecodeException; 
import javax.websocket.Decoder; 
import javax.websocket.EndpointConfig; 
 
public class ChatMessageDecoder implements Decoder.Text<ChatMessage> { 
 @Override 
 public void init(final EndpointConfig config) { 
 } 
 
 @Override 
 public void destroy() { 
 } 
 
 @Override 
 public ChatMessage decode(final String textMessage) throws DecodeException { 
  ChatMessage chatMessage = new ChatMessage(); 
  JsonObject obj = Json.createReader(new StringReader(textMessage)) 
    .readObject(); 
  chatMessage.setMessage(obj.getString(\"message\")); 
  chatMessage.setSender(obj.getString(\"sender\")); 
  chatMessage.setReceived(new Date()); 
  return chatMessage; 
 } 
 
 @Override 
 public boolean willDecode(final String s) { 
  return true; 
 } 
} 

同样再看下编码类的代码,这个类相反,是将ChatMessage类转换为Json格式,代码如下:

package com.hascode.tutorial; 
 
import javax.json.Json; 
import javax.websocket.EncodeException; 
import javax.websocket.Encoder; 
import javax.websocket.EndpointConfig; 
 
public class ChatMessageEncoder implements Encoder.Text<ChatMessage> { 
 @Override 
 public void init(final EndpointConfig config) { 
 } 
 
 @Override 
 public void destroy() { 
 } 
 
 @Override 
 public String encode(final ChatMessage chatMessage) throws EncodeException { 
  return Json.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文件,代码如下:

<!DOCTYPE html> 
<html lang=\"en\"> 
<head> 
[..] 
<script> 
 var wsocket; 
 var serviceLocation = \"ws://0.0.0.0:8080/hascode/chat/\"; 
 var $nickName; 
 var $message; 
 var $chatWindow; 
 var room = \'\'; 
 
 function onMessageReceived(evt) { 
  //var msg = eval(\'(\' + evt.data + \')\'); 
  var msg = JSON.parse(evt.data); // native API 
  var $messageLine = $(\'<tr><td class=\"received\">\' + msg.received 
    + \'</td><td class=\"user label label-info\">\' + msg.sender 
    + \'</td><td class=\"message badge\">\' + msg.message 
    + \'</td></tr>\'); 
  $chatWindow.append($messageLine); 
 } 
 function sendMessage() { 
  var msg = \'{\"message\":\"\' + $message.val() + \'\", \"sender\":\"\' 
    + $nickName.val() + \'\", \"received\":\"\"}\'; 
  wsocket.send(msg); 
  $message.val(\'\').focus(); 
 } 
 
 function connectToChatserver() { 
  room = $(\'#chatroom option:selected\').val(); 
  wsocket = new WebSocket(serviceLocation + room); 
  wsocket.onmessage = onMessageReceived; 
 } 
 
 function leaveRoom() { 
  wsocket.close(); 
  $chatWindow.empty(); 
  $(\'.chat-wrapper\').hide(); 
  $(\'.chat-signin\').show(); 
  $nickName.focus(); 
 } 
 
 $(document).ready(function() { 
  $nickName = $(\'#nickname\'); 
  $message = $(\'#message\'); 
  $chatWindow = $(\'#response\'); 
  $(\'.chat-wrapper\').hide(); 
  $nickName.focus(); 
 
  $(\'#enterRoom\').click(function(evt) { 
   evt.preventDefault(); 
   connectToChatserver(); 
   $(\'.chat-wrapper h2\').text(\'Chat # \'+$nickName.val() + \"@\" + room); 
   $(\'.chat-signin\').hide(); 
   $(\'.chat-wrapper\').show(); 
   $message.focus(); 
  }); 
  $(\'#do-chat\').submit(function(evt) { 
   evt.preventDefault(); 
   sendMessage() 
  }); 
 
  $(\'#leave-room\').click(function(){ 
   leaveRoom(); 
  }); 
 }); 
</script> 
</head> 
 
<body> 
 
 <div class=\"container chat-signin\"> 
  <form class=\"form-signin\"> 
   <h2 class=\"form-signin-heading\">Chat sign in</h2> 
   <label for=\"nickname\">Nickname</label> <input type=\"text\" 
    class=\"input-block-level\" placeholder=\"Nickname\" id=\"nickname\"> 
   <div class=\"btn-group\"> 
    <label for=\"chatroom\">Chatroom</label> <select size=\"1\" 
     id=\"chatroom\"> 
     <option>arduino</option> 
     <option>java</option> 
     <option>groovy</option> 
     <option>scala</option> 
    </select> 
   </div> 
   <button class=\"btn btn-large btn-primary\" type=\"submit\" 
    id=\"enterRoom\">Sign in</button> 
  </form> 
 </div> 
 <!-- /container --> 
 
 <div class=\"container chat-wrapper\"> 
  <form id=\"do-chat\"> 
   <h2 class=\"alert alert-success\"></h2> 
   <table id=\"response\" class=\"table table-bordered\"></table> 
   <fieldset> 
    <legend>Enter your message..</legend> 
    <div class=\"controls\"> 
     <input type=\"text\" class=\"input-block-level\" placeholder=\"Your message...\" id=\"message\" style=\"height:60px\"/> 
     <input type=\"submit\" class=\"btn btn-large btn-block btn-primary\" 
      value=\"Send message\" /> 
     <button class=\"btn btn-large btn-block\" type=\"button\" id=\"leave-room\">Leave 
      room</button> 
    </div> 
   </fieldset> 
  </form> 
 </div> 
</body> 
</html> 

在上面的代码中,要注意如下几点:

在Javascript端要调用websocket的话,要用如下的方式发起连接即可:ws://IP:PORT/CONTEXT_PATH/ENDPOINT_URL e.g ws://0.0.0.0:8080/hascode/chat/java

创建一个Websocket连接的方法很简单,使用的是var wsocket = new WebSocket(‘ws://0.0.0.0:8080/hascode/chat/java\');

要获得来自服务端返回的信息,只需要在回调函数wsocket.onmessage中设置对应的获取返回信息的方法即可。

发送一个Websocket消息到服务端,使用的方法是wsocket.send(),其中可以发送的消息可以文本或者二进制数据。

关闭连接使用的是wsocket.close()。

最后,我们通过mvn package embedded-glassfish:run进行代码的部署,然后就可以看到本文开始部分截图的效果。

以上就是用JavaEE7、Websockets和GlassFish4实现的聊天室,希望对大家的学习有所帮助。

本文地址:https://www.stayed.cn/item/8346

转载请注明出处。

本站部分内容来源于网络,如侵犯到您的权益,请 联系我

我的博客

人生若只如初见,何事秋风悲画扇。