一馏段、定義
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議姨伟。它實現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動發(fā)送信息給客戶端舷暮。
WebSocket協(xié)議支持(在受控環(huán)境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠(yuǎn)程主機之間進(jìn)行全雙工通信屈呕。
二沟饥、產(chǎn)生的背景
簡單的說迅涮,WebSocket協(xié)議之前废赞,雙工通信是通過不停發(fā)送HTTP請求,從服務(wù)器拉取更新來實現(xiàn)逗柴,這導(dǎo)致了效率低下蛹头。WebSocket解決了這個問題
三、解決的問題
1.服務(wù)器被迫為每個客戶端使用許多不同的底層TCP連接:一個用于向客戶端發(fā)送信息戏溺,其它用于接收每個傳入消息渣蜗。
2.有些協(xié)議有很高的開銷,每一個客戶端和服務(wù)器之間都有HTTP頭旷祸。
3.客戶端腳本被迫維護(hù)從傳出連接到傳入連接的映射來追蹤回復(fù)耕拷。
四、實現(xiàn)原理和好處
1托享、原理:在實現(xiàn)websocket連線過程中骚烧,需要通過瀏覽器發(fā)出websocket連線請求,然后服務(wù)器發(fā)出回應(yīng)闰围,這個過程通常稱為“握手” 赃绊。
2、好處
① Header
互相溝通的Header是很小的-大概只有 2 Bytes
②Server Push
服務(wù)器的推送羡榴,服務(wù)器不再被動的接收到瀏覽器的請求之后才返回數(shù)據(jù)碧查,而是在有新數(shù)據(jù)時就主動推送給瀏覽器。
五校仑、請求響應(yīng)案例
瀏覽器請求
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://服務(wù)器地址
Sec-WebSocket-Version: 13
服務(wù)器回應(yīng)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
WebSocket借用http請求進(jìn)行握手忠售,相比正常的http請求,多了一些內(nèi)容迄沫。
其中稻扬,Upgrade: websocket
Connection: Upgrade
表示希望將http協(xié)議升級到Websocket協(xié)議。
Sec-WebSocket-Key是瀏覽器隨機生成的base64 encode的值羊瘩,用來詢問服務(wù)器是否是支持WebSocket泰佳。
服務(wù)器返回
Upgrade: websocket
Connection: Upgrade
告訴瀏覽器即將升級的是Websocket協(xié)議
Sec-WebSocket-Accept是將請求包“Sec-WebSocket-Key”的值盼砍,與”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個字符串進(jìn)行拼接,然后對拼接后的字符串進(jìn)行sha-1運算乐纸,再進(jìn)行base64編碼得到的衬廷。用來說明自己是WebSocket助理服務(wù)器。
六汽绢、個人demo
1.服務(wù)端
/*
websocket服務(wù)端
客戶端向服務(wù)器端建立websocket的url
-
*/
@ServerEndpoint("/websocket")
@Component
public class WebSocketServer {
//計算當(dāng)前在線人數(shù)
private static int onlineCount=0;
//concurrent包的線程安全set吗跋,用來存放每個客戶端對應(yīng)的mywebsocket對象,必須
private static CopyOnWriteArraySet<WebSocketServer>webSocketSet=new CopyOnWriteArraySet<>();
//與某個客戶端的連接會話宁昭,需要通過它來給客戶端發(fā)送數(shù)據(jù)跌宛,必須
private Session session;/*
- 連接建立成功使用的方法
-
/
@OnOpen
public void onOpen(Session session){
this.session=session;
webSocketSet.add(this);
addOnlineCount(); //在線數(shù)加1
System.out.println("有新窗口開始監(jiān)聽,當(dāng)前在線人數(shù)為" + getOnlineCount());
try {
sendMessage("連接成功");
} catch (IOException e) {
System.out.println("WebSocket IO異常");
}
}
/
連接關(guān)閉調(diào)用的方法
/
@OnClose
public void onClose() {
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //在線數(shù)減1
System.out.println("有連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
}
/ - 收到客戶端消息后調(diào)用的方法
- @param message 客戶端發(fā)送過來的消息
/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到客戶端的信息:" + message);
//群發(fā)消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* - @param session
- @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發(fā)生錯誤");
error.printStackTrace();
}
/**
- 實現(xiàn)服務(wù)器主動推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群發(fā)自定義消息
*/
public static void sendInfo(String message) throws IOException {
System.out.println("推送消息內(nèi)容:" + message);
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
2.配置webSocket
/*
WEBSOCKET配置類
開啟websocket支持
-
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3.提供接口
@RestController
@RequestMapping(value = "/socket")
public class WebSocketController {
//推送數(shù)據(jù)接口
@RequestMapping("/push")
public String pushMsg(HttpServletRequest request) {
String message=request.getParameter("info");
try {
WebSocketServer.sendInfo(message);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
}
4.主類
@SpringBootApplication
public class WebsocketApplication {public static void main(String[] args) {
SpringApplication.run(WebsocketApplication.class, args);
}
}
5.前端客戶端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>websocket頁面</title>
<style>
.ta1{
width: 400px;
height: 400px;
overflow-x:hidden;
background-color: tan
}
.ta2{
width:400px;
height:200px;
overflow-x:hidden;
background-color: aquamarine;
}
.one{
width: 300px;
height: 30px;
border-radius: 5px;
background-color: lightcoral
}
.two{
width: 50px;
height: 30px;
background-color: cadetblue;
border-radius: 5px;
}
body{
text-align: center;
}
</style>
</head>
<body>
<h2>WebSocket練習(xí)</h2>
<script>
function sendone() {
socket.send(document.getElementById("userMsg").value);
document.getElementById("userMsg").value=null;
}
var socket;
if (typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
} else {
console.log("您的瀏覽器支持WebSocket");
//實現(xiàn)化WebSocket對象积仗,指定要連接的服務(wù)器地址與端口建立連接
socket = new WebSocket("ws://localhost:8080/websocket");//打開事件 socket.onopen = function () { console.log("Socket已打開"); //socket.send("這是來自客戶端的消息:" + new Date()); }; //獲得消息事件 socket.onmessage = function (msg) { console.log(msg.data); alert(msg.data); document.getElementById("tao").value=document.getElementById("tao").value+'\n'+msg.data; }; //關(guān)閉事件 socket.onclose = function () { console.log("Socket已關(guān)閉"); }; //發(fā)生了錯誤事件 socket.onerror = function () { alert("Socket發(fā)生了錯誤"); }
}
</script>
<h3>消息顯示區(qū)域</h3>
<div>
<textarea class="ta1" id="tao"></textarea>
</div>
<div>
<input type="text" class="one" placeholder="輸入你要發(fā)送的信息" value="" id="userMsg">
<input type="button" class="two" value="發(fā)送" onclick="sendone()">
</div>
<h3>服務(wù)器端廣播消息區(qū)域</h3>
<div>
<textarea class="ta2"></textarea>
</div>
</body>
</html>
6.前端服務(wù)器端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>server頁面</title>
<style>
textarea{
width:200px;
height: 30px;
background-color: lightcoral;
overflow-x:hidden;
}
.two{
width: 50px;
height: 30px;
background-color: cadetblue;
border-radius: 5px;
}
body{
text-align: center;
}
</style>
</head>
<body>
<h3>服務(wù)器推送消息</h3>
<form action="/socket/push" method="post" accept-charset="utf-8" name="loginfrom">
<textarea id="info" name="info"></textarea>
<input type="submit" class="two" value="發(fā)送">
</form>
</body