JavaEE7+Websockets+GlassFish4打造聊天室
在客戶機(jī)和服務(wù)器之間建立單一的雙向連接,這就意味著客戶只需要發(fā)送一個(gè)請(qǐng)求到服務(wù)端,那么服務(wù)端則會(huì)進(jìn)行處理,處理好后則將其返回給客戶端,客戶端則可以在等待這個(gè)時(shí)間繼續(xù)去做其他工作,整個(gè)過程是異步的。在本系列教程中,將指導(dǎo)用戶如何在JAVA EE 7的容器GlassFish 4中,使用JAVA EE 7中的全新的解析Json API(JSR-353),以及綜合運(yùn)用jQuery和Bootstrap。本文要求讀者有一定的HTML 5 Websocket的基礎(chǔ)原理知識(shí)。
效果圖
我們先來看下在完成這個(gè)教程后的效果圖,如下所示:

準(zhǔn)備工作
我們使用的是JDK 7 和MAVN 3進(jìn)行庫(kù)的構(gòu)建工作,首先看pom.xml中關(guān)于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>
同時(shí),為了能使用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>
設(shè)置Websocket的Endpoint
我們先來看服務(wù)端Websocket的代碼如下,然后再做進(jìn)一步解析:
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定義一個(gè)新的endpoint,其中的值指定了URL并且可以使用PathParams參數(shù),就象在JAX-RS中的用法一樣。
所以值“/chat/{room}”允許用戶通過如下形式的URL去連接某個(gè)聊天室:ws://0.0.0.0:8080/hascode/chat/java
在大括號(hào)中的值(即room),可以通過使用javax.websocket.server.PathParam,在endpoint的生命周期回調(diào)方法中以參數(shù)的方式注入。
此外,我們要使用一個(gè)編碼和解碼的類,因?yàn)槲覀兪褂玫氖且粋€(gè)DTO形式的類,用于在服務(wù)端和客戶端傳送數(shù)據(jù)。
當(dāng)用戶第一次連接到服務(wù)端,輸入要進(jìn)入聊天室的房號(hào),則這個(gè)房號(hào)以參數(shù)的方式注入提交,并且使用session.getUserProperties將值保存在用戶的屬性map中。
當(dāng)一個(gè)聊天參與者通過tcp連接發(fā)送信息到服務(wù)端,則循環(huán)遍歷所有已打開的session,每個(gè)session被綁定到指定的聊天室中,并且接收編碼和解碼的信息。
如果我們想發(fā)送簡(jiǎn)單的文本信息或和二進(jìn)制格式的信息,則可以使用session.getBasicRemote().sendBinary() 或session.getBasicRemote().sendText()
接下來我們看下用于代表信息傳遞實(shí)體(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方法
}
聊天消息的轉(zhuǎn)換
在這個(gè)應(yīng)用中,將編寫一個(gè)編碼和解碼類,用于在聊天信息和JSON格式間進(jìn)行轉(zhuǎn)換。
先來看下解碼類的實(shí)現(xiàn),這將會(huì)把傳遞到服務(wù)端的聊天信息轉(zhuǎn)換為ChatMessage實(shí)體類。在這里,使用的是Java API for JSON Processing(JSR353)規(guī)范去將JSON格式的信息轉(zhuǎn)換為實(shí)體類,代碼如下,其中重寫的willDecode方法,這里默認(rèn)返回為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;
}
}
同樣再看下編碼類的代碼,這個(gè)類相反,是將ChatMessage類轉(zhuǎn)換為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的強(qiáng)大威力,只需要調(diào)用Json.createObjectBuilder就可以輕易把一個(gè)DTO對(duì)象轉(zhuǎn)化為JSON了。
通過Bootstrap、Javacsript搭建簡(jiǎn)易客戶端
最后,我們綜合運(yùn)用著名的Bootstrap、jQuery框架和Javascript設(shè)計(jì)一個(gè)簡(jiǎn)易的客戶端。我們?cè)趕rc/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>
在上面的代碼中,要注意如下幾點(diǎn):
在Javascript端要調(diào)用websocket的話,要用如下的方式發(fā)起連接即可:ws://IP:PORT/CONTEXT_PATH/ENDPOINT_URL e.g ws://0.0.0.0:8080/hascode/chat/java
創(chuàng)建一個(gè)Websocket連接的方法很簡(jiǎn)單,使用的是var wsocket = new WebSocket(‘ws://0.0.0.0:8080/hascode/chat/java');
要獲得來自服務(wù)端返回的信息,只需要在回調(diào)函數(shù)wsocket.onmessage中設(shè)置對(duì)應(yīng)的獲取返回信息的方法即可。
發(fā)送一個(gè)Websocket消息到服務(wù)端,使用的方法是wsocket.send(),其中可以發(fā)送的消息可以文本或者二進(jìn)制數(shù)據(jù)。
關(guān)閉連接使用的是wsocket.close()。
最后,我們通過mvn package embedded-glassfish:run進(jìn)行代碼的部署,然后就可以看到本文開始部分截圖的效果。

以上就是用JavaEE7、Websockets和GlassFish4實(shí)現(xiàn)的聊天室,希望對(duì)大家的學(xué)習(xí)有所幫助。
- java socket實(shí)現(xiàn)聊天室 java實(shí)現(xiàn)多人聊天功能
- java利用Socket實(shí)現(xiàn)聊天室功能實(shí)例
- 基于Tomcat7、Java、WebSocket的服務(wù)器推送聊天室實(shí)例
- 用java WebSocket做一個(gè)聊天室
- Java Socket聊天室編程(一)之利用socket實(shí)現(xiàn)聊天之消息推送
- Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室
- 使用Java和WebSocket實(shí)現(xiàn)網(wǎng)頁(yè)聊天室實(shí)例代碼
- java實(shí)現(xiàn)一個(gè)簡(jiǎn)單TCPSocket聊天室功能分享
- Java基于socket實(shí)現(xiàn)簡(jiǎn)易聊天室實(shí)例
- Java Socket模擬實(shí)現(xiàn)聊天室
相關(guān)文章
Java實(shí)現(xiàn)在Word模板指定位置添加二維碼并生成?PDF
在實(shí)際業(yè)務(wù)場(chǎng)景中,我們常常需要在?Word?模板的指定位置貼上二維碼,然后將其轉(zhuǎn)換為?PDF?電子憑證文檔,下面我們就來看看具體實(shí)現(xiàn)方法吧2025-02-02
MyBatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)類型和Java類型的轉(zhuǎn)換
MyBatis 在處理數(shù)據(jù)庫(kù)查詢結(jié)果或傳遞參數(shù)時(shí),需要將數(shù)據(jù)庫(kù)類型與 Java 類型之間進(jìn)行轉(zhuǎn)換,本文就給大家介紹MyBatis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)類型和 Java 類型的轉(zhuǎn)換的,需要的朋友可以參考下2024-09-09
spring中WebClient如何設(shè)置連接超時(shí)時(shí)間以及讀取超時(shí)時(shí)間
這篇文章主要給大家介紹了關(guān)于spring中WebClient如何設(shè)置連接超時(shí)時(shí)間以及讀取超時(shí)時(shí)間的相關(guān)資料,WebClient是Spring框架5.0引入的基于響應(yīng)式編程模型的HTTP客戶端,它提供一種簡(jiǎn)便的方式來處理HTTP請(qǐng)求和響應(yīng),需要的朋友可以參考下2024-08-08
Java基礎(chǔ)之內(nèi)部類與代理知識(shí)總結(jié)
今天帶大家復(fù)習(xí)Java的基礎(chǔ)知識(shí),文中有非常詳細(xì)的介紹及圖文示例,對(duì)正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06

