java websocket原理及其簡易實現
WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。
WebSocket協議支持(在受控環境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠程主機之間進行全雙工通信。用於此的安全模型是Web瀏覽器常用的基於原始的安全模式。 協議包括一個開放的握手以及隨後的TCP層上的消息幀。 該技術的目標是爲基於瀏覽器的、需要和服務器進行雙向通信的(服務器不能依賴於打開多個HTTP連接(例如,使用XMLHttpRequest或<iframe>和長輪詢))應用程序提供一種通信機制。
長久以來, 創建實現客戶端和用戶端之間雙工通訊的web app都會造成HTTP輪詢的濫用: 客戶端向主機不斷髮送不同的HTTP呼叫來進行詢問。
這會導致一系列的問題:
1.服務器被迫爲每個客戶端使用許多不同的底層TCP連接:一個用於向客戶端發送信息,其它用於接收每個傳入消息。
2.有線協議有很高的開銷,每一個客戶端和服務器之間都有HTTP頭。
3.客戶端腳本被迫維護從傳出連接到傳入連接的映射來追蹤回覆。
一個更簡單的解決方案是使用單個TCP連接雙向通信。 這就是WebSocket協議所提供的功能。 結合WebSocket API ,WebSocket協議提供了一個用來替代HTTP輪詢實現網頁到遠程主機的雙向通信的方法。
WebSocket協議被設計來取代用HTTP作爲傳輸層的雙向通訊技術,這些技術只能犧牲效率和可依賴性其中一方來提高另一方,因爲HTTP最初的目的不是爲了雙向通訊。
在HTML5中內置有一些API,用於響應應用程序發起的請求。基本API語句如下:
創建對象
var ws = new WebSocket(url,name);
url爲WebSocket服務器的地址,name爲發起握手的協議名稱,爲可選擇項。
發送文本消息
ws.send(msg);
msg爲文本消息,對於其他類型的可以通過二進制形式發送。
接收消息
ws.onmessage = (function(){...})();
錯誤處理
ws.onerror = (function(){...})();
關閉連接
ws.close();
簡易實現:客戶端:
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Java後端WebSocket的Tomcat實現</title>
</head>
<body>
WebSocket的Tomcat實現
<br />
<input id="text" type="text" />
<button onclick="send()">發送消息</button><br />
<button onclick="link()">建立連接</button><br />
<hr />
<button onclick="closeWebSocket()">關閉WebSocket連接</button>
<hr />
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/WebSoket/websocket");
}
else {
alert('當前瀏覽器 Not support websocket')
}
//連接發生錯誤的回調方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發生錯誤");
};
//連接成功建立的回調方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關閉的回調方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關閉");
}
//簡歷連接的回調方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket建立連接");
}
//監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在網頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//建立WebSocket連接
function link() {
websocket.onopen();
websocket = new WebSocket("ws://localhost:8080/WebSoket/websocket");
}
//發送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
簡易實現:服務器端
package com.soket;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
/**
* @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
* 註解的值將被用於監聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務器端
*/
@ServerEndpoint("/websocket")
public class WebSoketEndpoint {
// 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
private static int onlineCount = 0;
// concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通信的話,可以使用Map來存放,其中Key可以爲用戶標識
private static CopyOnWriteArraySet<WebSoketEndpoint> webSocketSet = new CopyOnWriteArraySet<WebSoketEndpoint>();
// 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
private Session session;
//用戶名
private final static String GUIST_NAME = "遊客";
//用戶編號
private static final AtomicInteger connectionIds = new AtomicInteger(0);
//用戶暱稱
private final String nickname;
/**
* 連接建立成功調用的方法
*
* @param session
* 可選的參數。session爲與某個客戶端的連接會話,需要通過它來給客戶端發送數據
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在線數加1
String message = String.format("* %s %s", nickname, "已連接");
try {
sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("有新連接加入!當前在線人數爲" + getOnlineCount());
}
public WebSoketEndpoint() {
nickname = GUIST_NAME + connectionIds.getAndIncrement();
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose() {
String message = String.format("* %s %s", nickname, "已關閉連接");
try {
sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
webSocketSet.remove(this); // 從set中刪除
subOnlineCount(); // 在線數減1
System.out.println("有一連接關閉!當前在線人數爲" + getOnlineCount());
}
}
/**
* 收到客戶端消息後調用的方法
*
* @param message
* 客戶端發送過來的消息
* @param session
* 可選的參數
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的消息:" + message);
// 羣發消息
for (WebSoketEndpoint item : webSocketSet) {
try {
item.sendMessage(nickname+":"+message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發生錯誤時調用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發生錯誤");
error.printStackTrace();
}
/**
* 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要添加的方法。
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
// this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSoketEndpoint.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSoketEndpoint.onlineCount--;
}
}