一、概述
1.Http
#http簡介
HTTP是一個應用層協議,無狀態的,端口號為80。主要的版本有1.0/1.1/2.0.
#http1.0/1.1/2.0
1.HTTP/1.* 一次請求-響應,建立一個連接,用完關閉;
2.HTTP/1.1 串行化單線程處理,可以同時在同一個tcp鏈接上發送多個請求,但是只有響應是有順序的,只有上一個請求完成后,下一個才能響應。一旦有任務處理超時等,后續任務只能被阻塞(線頭阻塞);
3.HTTP/2 並行執行。某任務耗時嚴重,不會影響到任務正常執行
2.WebSocket
#WebSocket簡介
Websocket是html5提出的一個協議規范,是為解決客戶端與服務端實時通信。本質上是一個基於tcp,先通過HTTP/HTTPS協議發起一條特殊的http請求進行握手后創建一個用於交換數據的TCP連接.WebSocket同HTTP一樣也是應用層的協議,但是它是一種雙向通信協議。
#WebSocket連接過程
1. 瀏覽器、服務器建立TCP連接,三次握手。這是通信的基礎,傳輸控制層,若失敗后續都不執行。
2. TCP連接成功后,瀏覽器通過HTTP協議向服務器傳送WebSocket支持的版本號等信息。(開始前的HTTP握手)
3. 服務器收到客戶端的握手請求后,同樣采用HTTP協議回饋數據。
4. 當收到了連接成功的消息后,通過TCP通道進行傳輸通信。
#WebSocket優勢
瀏覽器和服務器只需要要做一個握手的動作,然后單獨建立一條TCP的通信通道進行數據的傳送, 在
建立連接之后,雙方可以在任意時刻,相互推送信息。同時,服務器與客戶端之間交換的頭信息很小。
3.Socket
#socket簡介
Socket並不是一個協議,而是為了方便使用TCP或UDP而抽象出來的一層,是位於應用層和傳輸控制層之間的一組接口。
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
#socket作用
當兩台主機通信時,必須通過Socket連接,Socket則利用TCP/IP協議建立TCP連接。TCP連接則更依靠於底層的IP協議,IP協議的連接則依賴於鏈路層等更低層次。
4.WebSocket 和 Http
#相同點
1. 都是一樣基於TCP的,都是可靠性傳輸協議。
2. 都是應用層協議。
#不同點
1. WebSocket是雙向通信協議,模擬Socket協議,可以雙向發送或接受信息。HTTP是單向的。
2. WebSocket是需要握手進行建立連接的
#聯系
WebSocket在建立握手時,數據是通過HTTP傳輸的。但是建立之后,在真正傳輸時候是不需要HTTP協議的。
5.WebSocket 和 Socket
#區別
Socket是傳輸控制層協議,WebSocket是應用層協議。
6.長連接,短連接
#短連接
連接->傳輸數據->關閉連接
HTTP是無狀態的,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。
也可以這樣說:短連接是指SOCKET連接后發送后接收完數據后馬上斷開連接。
#長連接
連接->傳輸數據->保持連接 -> 傳輸數據-> ... ->關閉連接。
長連接指建立SOCKET連接后不管是否使用都保持連接,但安全性較差。
7.http和websocket的長連接區別
HTTP1.1通過使用Connection:keep-alive進行長連接,HTTP 1.1默認進行持久連接。在一次 TCP 連接中可以完成多個 HTTP 請求,但是對每個請求仍然要單獨發 header,Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。這種長連接是一種“偽鏈接”
websocket的長連接,是一個真的全雙工。長連接第一次tcp鏈路建立之后,后續數據可以雙方都進行發送,不需要發送請求頭。
keep-alive雙方並沒有建立正真的連接會話,服務端可以在任何一次請求完成后關閉。WebSocket 它本身就規定了是正真的、雙工的長連接,兩邊都必須要維持住連接的狀態

傳統 HTTP 請求響應客戶端服務器交互圖

WebSocket 模式客戶端與服務器的交互圖
二、WebSocket請求報文和Http的差別
1.響應頭
HTTP/1.1 101
Upgrade: websocket
Connection: upgrade #協議升級成功
Sec-WebSocket-Accept: IiuAbVCofO973oDeSggnpHFjLeU= #服務端處理之后的key
Sec-WebSocket-Extensions: permessage-deflate #擴展協議
Date: Fri, 02 Aug 2019 03:29:14 GMT
2.請求頭
Host: xiajibagao.top:8080 #升級協議的服務主機地址端口
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13 #當前WebSocket協議版本號
Origin: http://xiajibagao.top:8080
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: wJfIzjPgehJrCXmKSvL8cQ== #連接憑證,客戶端將這個key發送給服務器,服務器將這個key進行處理,將處理后的key返回給客戶端,客戶端根據這個key是否正確來判斷是否建立連接。
Connection: keep-alive, Upgrade
Cookie: UM_distinctid=16c31bfc8219-0079038beea7fb-4c312c7c-144000-16c31bfc8226a3; CNZZDATA1277674304=827412020-1564198032-%7C1564319195
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket #通知服務器協議升級為WebSocket
三、基於springboot的簡單聊天室
1.新建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>這是一個聊天室</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<style>
#showarea{
border:1px solid darkgrey;
border-radius:10px;
min-height: 350px;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<br />
<div class="col-sm-4">
<div class="form-group">
<!--用戶名-->
<input type="text" class="form-control" value="匿名" id="username">
<br />
<!--聊天內容-->
<textarea value="輸入聊天內容" rows="10" class="form-control" id="sendarea"></textarea>
<br />
<!--提交按鈕-->
<button class="btn btn-block btn-success" onclick="submitmsg()">發送</button>
</div>
</div>
<div class="col-sm-8">
<!--消息列表展示區-->
<div id="showarea">
</div>
</div>
</div>
</div>
</body>
</html>
2.頁面的js
<script>
//當打開頁面時新建WebSocket連接
var host = window.location.host;
var url = "ws://"+host+"/webchat/chat";//訪問到后台項目注解所在的類
var ws = new WebSocket(url); //new一個新ws對象,new完即新建立一條“管道”
/**
*ws.onopen方法,當連接建立成功后觸發
*/
ws.onopen=function(){
onsole.log("連接成功!");
};
/**
*發送消息給服務器
*/
function submitmsg() {
//獲取用戶名和要發送的消息
var username = document.getElementById("username").value;
var sendarea = document.getElementById("sendarea").value;
//轉換為json字符串
var jsonmsg ={
username:username,
sendarea:sendarea,
time:new Date()
}
//發送消息
ws.send(JSON.stringify(jsonmsg));
}
/**
*從服務器接收消息
*ws.onmessage方法,當后台接受發送信息時觸發
*/
ws.onmessage = function(evn){
//轉換為json字符串
var jsonobj = eval(JSON.parse(evn.data));
//往頁面插入消息
var msg = document.createElement("h4");
context = jsonobj.username+' '+getDate(jsonobj.time)+'<br />'+jsonobj.sendarea
msg.innerHTML=context;
var showareadiv = document.getElementById("showarea");
showareadiv.appendChild(msg);
};
/**
*ws.onclose方法,當窗口關閉,會話結束時觸發
*/
ws.onclose = function(){
onsole.log("關閉連接");
};
/*日期轉換*/
function getDate(time){
var date = new Date(time);
Year = date.getFullYear();
Month = date.getMonth();
Day = date.getDay();
time = Year+"-"+getZero(Month)+"-"+getZero(Month);
return time;
}
/*日期補零*/
function getZero(num){
if(parseInt(num) < 10 ){
num = "0" + num;
}
return num;
}
</script>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- 點擊按鈕,提交文本框內內容 -->
<button onclick="send()">open</button>
<input type="text" value="輸入點什么" id="textarea">
<script type="text/javascript">
var host = window.location.host; //獲取地址
var url = "ws://"+host+"/websocket/echo";//訪問到后台項目注解所在的類
var ws = new WebSocket(url);//new一個新ws對象,new完即新建立一條“管道”
//當連接建立成功后觸發
ws.onopen=function(){
onsole.log("連接成功!");
};
//當窗口關閉,會話結束時觸發
ws.onclose = function(){
onsole.log("關閉連接");
};
//當后台接受發送信息時觸發
ws.onmessage = function(evn){
alert(evn.data);
};
//當點擊按鈕時提交信息
function send() {
var msg = document.getElementById("textarea").value;
ws.send(msg);
}
</script>
</body>
</html>
3.添加依賴
<dependencies>
<!--使用自帶的tomcat-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
</dependencies>
4.Controller
package com.huang.socketserver;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* Author:huang
* Date:2019-08-02 22:35
* Description:<描述>
*/
//
@ServerEndpoint("/chat")
@RestController
public class ChatSocket {
//定義一個全局變量集合sockets,用戶存放每個登錄用戶的通信管道
private static Set<ChatSocket> sockets = new HashSet<ChatSocket>();
//定義一個全局變量Session,用於存放登錄用戶
private Session session;
/**
*@OnOpen注解
*注解下的方法會在連接建立時運行
*/
@OnOpen
public void open(Session session){
System.out.println("建立了一個socket通道" + session.getId());
this.session = session;
//將當前連接上的用戶session信息全部存到scokets中
sockets.add(this);
}
/**
*@OnMessage注解
*注解下的方法會在前台傳來消息時觸發
*/
@OnMessage
public void getmes(Session session,String jsonmsg){
broadcast(sockets,jsonmsg);
}
/**
*@OnClose注解
*注解下的方法會在連接關閉時運行
*/
@OnClose
public void close(Session session){
//移除退出登錄用戶的通信管道
sockets.remove(this);
System.out.println(session.getId()+"退出了會話!");
}
/**
*廣播消息
*/
public void broadcast(Set<ChatSocket> sockets , String msg){
//遍歷當前所有的連接管道,將通知信息發送給每一個管道
for(ChatSocket socket : sockets){
try {
//通過session發送信息
System.out.println("發送給管道"+socket.session.getId());
socket.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.huang.websocket;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint(value = "/echo")
public class echoSocket {
public echoSocket() {
System.out.println("新對象!");
}
/**
*@OnOpen注解
*注解下的方法會在連接建立時運行
*/
@OnOpen
public void open(Session session){
//一個session代表一個通信會話
//通過sessionid區分每一個會話
System.out.println("連接成功");
System.out.println(session.getId());
}
/**
*@OnClose注解
*注解下的方法會在連接關閉時運行
*/
@OnClose
public void close(Session session){
System.out.println("關閉"+session.getId());
}
/**
*@OnMessage注解
*注解下的方法會在前台傳來消息是觸發
*/
@OnMessage
public void getmes(Session session,String msg){
System.out.println("客戶端:"+msg);
try {
//向請求服務器的會話發送消息
session.getBasicRemote().sendText("服務器:你發來的消息是-"+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、運行效果

搞定!
=====================================
參考:https://www.cnblogs.com/ricklz/p/11108320.html
參考:https://www.cnblogs.com/Javi/p/9303020.html
=====================================
