使用 tomcat8 開發 WebSocket 服務端非常簡單,大致有如下兩種方式。
1、使用注解方式開發,被 @ServerEndpoint 修飾的 Java 類即可作為 WebSocket 服務端
2、繼承 Endpoint 基類實現 WebSocket 服務端
開發被 @ServerEndpoint 修飾的類之后,該類中還可以定義如下方法。
被 @OnOpen 修飾的方法:當客戶端與該 WebSocket 服務端建立連接時激發該方法
被 @OnClose 修飾的方法:當客戶端與該 WebSocket 服務端斷開連接時激發該方法
被 @OnMessage 修飾的方法:當 WebSocket 服務端收到客戶端消息時激發該方法
被 @OnError 修飾的方法:當客戶端與該 WebSocket 服務端連接出現錯誤時激發該方法。
下面將基於 WebSocket 開發一個多人實時聊天的程序,該程序思路很簡單 -- 在這個程序中,每個客戶所用的瀏覽器都與服務器建立一個 WebSocket,從而保持實時連接,這樣客戶端的瀏覽器可以隨時把數據發送到服務器端;當服務器收到任何一個瀏覽器發送來的消息之后,將該消息依次向每個客戶端瀏覽器發送一遍。
按如下步驟開發 WebSocket 服務端程序即可
1、定義 @OnOpen 修飾的方法,每當客戶端連接進來時激發該方法,程序使用集合保存所有連接進來的客戶端
2、定義 @OnMessage 修飾的方法,每當該服務端收到客戶端消息時激發該方法,服務端收到消息之后遍歷保存客戶端的集合,並將消息逐個發給所有客戶端
3、定義 @OnClose 修飾的方法,每當客戶端斷開與該服務端連接時激發該方法,程序將該客戶端從集合中刪除。
ChatEndpoint.java
package com.baiguiren;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.OnError;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value="/websocket/chat")
public class ChatEndpoint
{
private static final String GUEST_PREFIX = "訪客";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
// 定義一個集合,用於保存所有接入的 WebSocket 客戶端
private static final Set<ChatEndpoint> clientSet = new CopyOnWriteArraySet<>();
// 定義一個成員變量,記錄 WebSocket 客戶端的聊天昵稱
private final String nickname;
// 定義一個成員變量,記錄與 WebSocket 之間的會話
private Session session;
public ChatEndpoint()
{
nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
}
// 當客戶端連接進來時自動激發該方法
@OnOpen
public void start(Session session)
{
this.session = session;
// 將 WebSocket 客戶端會話添加到集合中
clientSet.add(this);
String message = String.format("[%s %s]", nickname, "加入了聊天室");
// 發送消息
broadcast(message);
}
// 當客戶端斷開連接時自動激發該方法
@OnClose
public void end()
{
clientSet.remove(this);
String message = String.format("[%s %s]", nickname, "離開了聊天室!");
// 發送消息
broadcast(message);
}
// 每當收到客戶端消息時自動激發該方法
@OnMessage
public void incoming(String message)
{
String filteredMessage = String.format("%s: %s", nickname, filter(message));
// 發送消息
broadcast(filteredMessage);
}
// 當客戶端通信出現錯誤時激發該方法
@OnError
public void onError(Throwable t) throws Throwable
{
System.out.println("WebSocket 服務端錯誤" + t);
}
// 實現廣播消息的工具方法
private static void broadcast(String msg)
{
// 遍歷服務器關聯的所有客戶端
for (ChatEndpoint client : clientSet)
{
try {
synchronized (client)
{
// 發送消息
client.session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
System.out.println("聊天錯誤,向客戶端" + client + "發送消息出現錯誤。");
clientSet.remove(client);
try {
client.session.close();
} catch (IOException el) {}
String message = String.format("[%s %s]", client.nickname, "已經被斷開了連接");
broadcast(message);
}
}
}
// 定義一個工具方法,用於對字符串中的 HTML 字符標簽進行轉義
private static String filter(String message)
{
if (message == null)
return null;
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++)
{
// 控制對尖括號等特殊字符轉義
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
以上文件需要導入 javaee-api-7.0.jar
需要說明的是,該 CharEndpoint 類並不是真正的 WebSocket 服務端,它只實現了 WebSocket 服務端的核心功能,Tomcat 會調用它的方法作為 WebSocket 服務端。因此,Tomcat 會為每個 WebSocket 客戶端創建一個 ChatEndpoint 對象,也就是說,有一個 WebSocket 服務端,程序就有一個 ChatEndpoint 對象。所以上面程序中的 clientSet 集合保存了多個 ChatEndpoint 對象,其中每個 ChatEndpoint 對象對應一個 WebSocket 客戶端。
chat.html
<html>
<head>
<title>使用 WebSocket 通信</title>
</head>
<body>
<div style="width: 600px; height:240px;overflow-y: auto;border: 1px solid #333;" id="show">
</div>
<input type="text" size="80" id="msg" name="msg" placeholder="請輸入聊天內容"/ >
<input type="button" value="發送" id="sendBtn" name="sendBtn" />
<script>
window.onload = function() {
// 創建 WebSocket 對象
var webSocket = new WebSocket("ws://127.0.0.1:8080/jsp/websocket/chat");
var sendMsg = function() {
var inputElement = document.getElementById('msg');
// 發送消息
webSocket.send(inputElement.value);
// 清空單行文本框
inputElement.value = "";
};
var send = function(event) {
if (event.keyCode == 13) {
sendMsg();
}
};
webSocket.onopen = function() {
// 為 onmessage 事件綁定監聽器,接收消息
webSocket.onmessage = function(event) {
var show = document.getElementById('show');
// 接收並顯示消息
show.innerHTML += event.data + "<br/>";
show.scrollTop = show.scrollHeight;
};
document.getElementById('msg').onkeydown = send;
document.getElementById('sendBtn').onclick = sendMsg;
};
webSocket.onclose = function() {
// document.getElementById('msg').onkeydown = null;
// document.getElementById('sendBtn').onclick = null;
console.log('WebSocket已經被關閉');
};
}
</script>
</body>
</html>
