基于springboot的websocket聊天室[通俗易懂]

基于springboot的websocket聊天室[通俗易懂]一、概述1.Http2.WebSocket3.Socket4.WebSocket和Http5.WebSocket和Socket6.长连接,短连接7.http和websocket的

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、概述

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 它本身就规定了是正真的、双工的长连接,两边都必须要维持住连接的状态

基于springboot的websocket聊天室[通俗易懂]

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

基于springboot的websocket聊天室[通俗易懂]

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+'&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.添加依赖

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

}

五、运行效果

基于springboot的websocket聊天室[通俗易懂]

搞定!

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170842.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 什么是ARM?_arm开发板

    什么是ARM?_arm开发板原文一、ARM是什么?ARM既可以认为是一个公司的名字,也可以认为是对一类处理器的统称,还可以认为是一种技术的名字。ARM公司是专门从事基于RISC技术芯片设计开发的公司,作为知识产权供应商,本身不直接从事芯片生产,而是转让设计许可,由合作公司生产各具特色的芯片。ARM处理器的内核是统一的,由ARM公司提供,而片内部件则是多样的,由各大半导体公司设计,这使得ARM设计嵌入式系统的时候,可以基于同样的核心,使用不同的片内外设,从而具有很大的优势。二、ARM内核与架构

    2022年10月14日
    0
  • redis之淘汰策略和删除策略_局部淘汰策略

    redis之淘汰策略和删除策略_局部淘汰策略redis内存不足时的淘汰策略一般情况下,当内存超出物理内存限制时,内存数据将与磁盘产生频繁交换(swap),swap会导致redis性能急剧下降,对于访问量较大的情况下,swap的存取效率会让服务基本处于不可用的状态。在生产环境中,一般不允许redis出现swap行为,redis提供了maxmemory设置其最多可占用的内存空间。当redis使用的内存超出maxmemory时,此时已经没有多余可用的内存空间,新的数据将无法写入,redis提供了几种数据淘汰策略,用于清理数据,腾出空间以继续

    2022年10月20日
    0
  • Java大数据学习01–大数据的本质及学习顺序介绍

    Java大数据学习01–大数据的本质及学习顺序介绍随着互联网的使用人数越来越多,产生的数据也越来越多。根据数据我们可以分析出很多有用的信息。这也就是当前为什么大数据这么火的行为。学习大数据有很多种方式,但我们学习的载体是以目前最普遍,最流行的Java语言来进行学习。下面我们进入正题:1、首先是大数据的本质:大数据的本质无非两条:大数据的存储(Hadoop)和大数据的计算(Spark)存储:由于数据量巨大,把所有数据存在一个机器…

    2022年5月28日
    35
  • 电平转换的作用_电平转换电路原理

    电平转换的作用_电平转换电路原理作为一名电子设计的硬件工程师,电平转换是每个人都必须面对的的话题,主芯片引脚使用的1.2V、1.8V、3.3V等,连接外部接口芯片使用的1.8V、3.3V、5V等,由于电平不匹配就必须进行电平转换。每个工程师都有自己的一套转换方案,今天我们将5种电平转换的方法进行汇总,并且总结各种的优劣势,避免设计过程踩坑。一、电平转换方法5种电平转换方法分别是:晶体管电平转换方法;专用电平转换芯片;限流电阻电平转换方法;电阻分压电平转换方法;二极管电平转换方法;下面我们会从速率、驱动能力、漏电流、成本

    2022年8月10日
    6
  • 万能乘法速算法大全_小学生两位数乘法容易出错?只因没掌握这个“万能”速算法…

    万能乘法速算法大全_小学生两位数乘法容易出错?只因没掌握这个“万能”速算法…儿童节快乐两位数乘法,在小学阶段的数学学习当中,是经常遇到的。尤其是小学三、四年级,每当遇到这类乘法,小学生都非常容易出错,甚至一算就错。原因是,一些比较大的两位数,在用列竖式法相乘时,会出现连续进位,一旦有一个环节,进位失误了,就会导致整个乘法出现错误,结果为零分。因此,这类运算,很让老师头疼。那么,有没有实用的速算法,可以解决这类乘法问题,而且不出错呢?有!今天,就分享一种“万能”两…

    2022年6月7日
    32
  • zookeeper分布式锁实现原理(分布式锁怎么实现)

       摘要:本文要使用Zookeeper来实现一个分布式锁,是一个悲观锁。  本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning一、锁设计  获取锁实现思路:1.首先创建一个作为锁目录(znode),通常用它来描述锁定的实体,称为:/lock_node2.希望获得锁的客户端在锁目录下创建zno…

    2022年4月15日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号