基于springboot的websocket聊天室

倖福魔咒の 提交于 2019-11-26 23:49:00

WebSocket入门

1.概述

1.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 并行执行。某任务耗时严重,不会影响到任务正常执行

1.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的通信通道进行数据的传送, 在 建立连接之后,双方可以在任意时刻,相互推送信息。同时,服务器与客户端之间交换的头信息很小。

1.3 Socket

#socket简介 Socket并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。  #socket作用 当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。

1.4 WebSocket 和 Http

#相同点 1. 都是一样基于TCP的,都是可靠性传输协议。 2. 都是应用层协议。  #不同点 1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。 2. WebSocket是需要握手进行建立连接的  #联系 WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

1.5 WebSocket 和 Socket

#区别 Socket是传输控制层协议,WebSocket是应用层协议。

1.6 长连接,短连接

#短连接 连接->传输数据->关闭连接 HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。 也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。
#长连接 连接->传输数据->保持连接 -> 传输数据-> ... ->关闭连接。 长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

1.4 http和websocket的长连接区别

HTTP1.1通过使用Connection:keep-alive进行长连接,HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。这种长连接是一种“伪链接”  websocket的长连接,是一个真的全双工。长连接第一次tcp链路建立之后,后续数据可以双方都进行发送,不需要发送请求头。   keep-alive双方并没有建立正真的连接会话,服务端可以在任何一次请求完成后关闭。WebSocket 它本身就规定了是正真的、双工的长连接,两边都必须要维持住连接的状态

传统 HTTP 请求响应客户端服务器交互图

WebSocket 模式客户端与服务器的交互图

2.WebSocket请求报文和Http的差别

2.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.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

3.基于springboot的简单聊天室

3.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>

3.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+'&nbsp;&nbsp;'+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.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>

3.4 后台

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();             }         }     }  } 

3.5 运行

搞定!

=====================================

参考:https://www.cnblogs.com/ricklz/p/11108320.html

参考:https://www.cnblogs.com/Javi/p/9303020.html

=====================================

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!