1. 什麼是WebSocket
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
現在,很多網站爲了實現推送技術,所用的技術都是 Ajax 輪詢。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然後由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
HTML5 定義的 WebSocket 協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。
瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以後,客戶端和服務器端就可以通過 TCP 連接直接交換數據。
當你獲取 Web Socket 連接後,你可以通過 send() 方法來向服務器發送數據,並通過 onmessage 事件來接收服務器返回的數據。
2 使用websocket實現網絡通信
2.1 客戶端開發
var Socket = new WebSocket(url, [protocol] );
第一個參數 url, 指定連接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。
WebSocket對象提供了一些事件函數,用於監聽WebSocket的不同狀態。
事件函數 | 描述 |
---|---|
onopen | 建立連接時候觸發 |
onmessage | 客戶端接收到服務端數據時觸發 |
onerror | 通信發生錯誤時觸發 |
onclose | 連接關閉時觸發 |
除此以外,WebSocket還提供了下面兩個常用函數:
函數名 | 描述 |
---|---|
send | 向服務端發送數據 |
close | 關閉連接 |
WebSocket本質上是一個TCP協議,爲了建立一個 WebSocket 連接,客戶端瀏覽器首先要向服務器發起一個 HTTP 請求,這個請求和通常的 HTTP 請求不同,包含了一些附加頭信息,其中附加頭信息"Upgrade: WebSocket"表明這是一個申請協議升級的 HTTP 請求,服務器端解析這些附加的頭信息然後產生應答信息返回給客戶端,客戶端和服務器端的 WebSocket 連接就建立起來了,雙方就可以通過這個連接通道自由的傳遞信息,並且這個連接會持續存在直到客戶端或者服務器端的某一方主動的關閉連接。
下面是WebSocket客戶端的示例代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<input type="text" id="content">
<input type="button" value="send" onclick="send()"/>
<script>
// 創建WebSocket對象
var ws = new WebSocket("ws://localhost:8080/websocket2");
// 建立連接自動調用該方法
ws.onopen = function() {
console.log("建立連接成功!");
};
// 接收到消息時候自動調用該方法
ws.onmessage = function(evt) {
console.log("接收來自服務器的通知...");
alert(evt.data);
}
// 關閉連接時自動調用該方法
ws.onclose = function() {
console.log("連接已經關閉!");
}
//連接發生錯誤的回調方法
ws.onerror = function () {
setMessageInnerHTML("error");
};
// 發送按鈕事件
function send() {
var content = document.getElementById("content").value;
ws.send(content);
}
</script>
</body>
</html>
2.2 服務端開發
目前支持WebSocket的服務器包括:tomcat、weblogic、node.js、nginx等等。下面以tomcat爲例開發websocket服務端程序。
從.Tomcat從7.0.47開始支持JSR356規範,該規範對不同容器(如tomcat、jetty等)使用WebSocket作出統一的規範要求。
JSR356實現WebSocket有兩種方式,一種是使用註解的,另一種是繼承javax.websocket.Endpoint類。下面以註解爲例介紹如何開發WebSocket的服務端。
第一步:創建一個Maven項目,並加入相關websocket相關依賴。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chinasofti</groupId>
<artifactId>websockettest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:新建一個Endpoint類,並使用過@ServerEndpoint註解進行標註。
@ServerEndpoint("/websocket")
public class EchoEndpoint2 {}
上面括號中指定了WebSocket服務的名字。如果客戶端需要連接該服務,那麼就需要通過該名字來訪問。
第三步:根據WebSocket不同狀態提供相應的處理方法;
@ServerEndpoint("/websocket")
public class EchoEndpoint {
private Session session;
// 建立連接時自動調用
@OnOpen
public void open(final Session session) throws IOException {
System.out.println("建立連接!");
this.session = session;
}
// 接收消息時自動調用
@OnMessage
public void inMessage(String message) throws IOException {
System.out.println("websocket讀取到的數據:" + message);
// 發送消息給客戶端
session.getBasicRemote().sendText("hello " + message);
}
// 關閉連接時自動調用
@OnClose
public void end() {
System.out.println("websocket關閉了!");
}
}
3. WebSocket和Socket的區別
就像Java和JavaScript,並沒有什麼太大的關係。而WebSocket的名字雖然與Socket很相似,但是它們是兩個完全不同的東西。
從網絡結構上看,Socket 是屬於傳輸控制層的協議,而WebSocket 則是一個典型的應用層協議。
從使用上看,Socket是半雙工通訊技術,即數據能雙向傳送但不能同時雙向傳送;而WebSocket是全雙向通訊技術,即數據能夠同時雙向傳送。
4. 案例:Web聊天室
實現思路:
1)客戶端連接服務端後,服務端需要把每一個客戶端的session保存到一個集合中;
2)當客戶端向服務端發送消息後,服務端會先把消息保存到集合裏面,然後服務端就會把該集合的所有消息讀取出來並轉換成字符串後,一次性地發送給每一個客戶端;
3)客戶端接收到服務端推送的消息後,把消息顯示出來;
4.1 客戶端開發
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<textarea id="chatarea" rows="30" cols="200"></textarea><br/>
暱稱:<input type="text" id="name" size="10" value=""><br/>
消息內容:<input type="text" id="content" size="100">
<input type="button" value="send" onclick="send()"/>
<script>
// 創建WebSocket對象
var ws = new WebSocket("ws://localhost:8080/websocket2");
// 建立連接自動調用該方法
ws.onopen = function() {
console.log("建立連接成功!");
};
// 接收到消息時候自動調用該方法
ws.onmessage = function(evt) {
console.log("接收來自服務器的通知...");
document.getElementById("chatarea").value = evt.data;
}
// 關閉連接時自動調用該方法
ws.onclose = function() {
console.log("連接已經關閉!");
}
//連接發生錯誤的回調方法
ws.onerror = function () {
setMessageInnerHTML("error");
};
// 發送按鈕事件
function send() {
var name = document.getElementById("name").value;
var content = document.getElementById("content").value;
ws.send(name + ": " + content);
document.getElementById("content").value = "";
document.getElementById("content").focus();
}
</script>
</body>
</html>
4.2 服務端開發
@ServerEndpoint("/websocket")
public class EchoEndpoint {
// 該集合用於保存所有客戶端的session
static HashSet<Session> sessions = new HashSet<Session>();
// 該集合保存所有的歷史消息
static LinkedList<String> messages = new LinkedList<String>();
private Session session;
@OnOpen
public void open(final Session session) throws IOException {
System.out.println("建立連接!");
this.session = session;
sessions.add(session);
}
@OnMessage
public void inMessage(String message) throws IOException {
System.out.println("websocket讀取到的數據:" + message);
messages.add(message);
StringBuilder sb = new StringBuilder();
for (String msg : messages) {
sb.append(msg + "\r\n");
}
for (Session session : sessions) {
session.getBasicRemote().sendText(sb.toString());
}
}
@OnClose
public void end() {
System.out.println("websocket關閉了!");
sessions.remove(this.session);
}
}