欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JavaEE7+Websockets+GlassFish4打造聊天室

程序员文章站 2024-03-05 19:14:37
在客户机和服务器之间建立单一的双向连接,这就意味着客户只需要发送一个请求到服务端,那么服务端则会进行处理,处理好后则将其返回给客户端,客户端则可以在等待这个时间继续去做其他...

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

效果图

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

JavaEE7+Websockets+GlassFish4打造聊天室

准备工作

我们使用的是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打造聊天室

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