websocket从入门到实战(代码片段)

杨戬 杨戬     2023-01-28     149

关键词:

文章目录

WebSocket

WebSocket 介绍

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双向通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

WebSocket API

WebSocket 对象

创建 WebSocket 对象:

var socket = new WebSocket(url, [protocol] );

WebSocket属性

WebSocket对象属性如下:

属性描述
socket.readyState只读属性 readyState 表示连接状态,可以是以下值:
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开。
socket.bufferedAmount只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket事件:

事件事件处理程序描述
opensocket.onopen连接建立时触发
messagesocket.onmessage客户端接收服务端数据时触发
errorsocket.onerror通信发生错误时触发
closesocket.onclose连接关闭时触发

WebSocket方法:

方法描述
socket.send()使用连接发送数据
socket.close()关闭连接

WebSocket 实例

客户端

​ WebSocket 协议本质上是一个基于 TCP 的协议。

为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息“Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

我们按照上面的API实现客户端代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket</title>
<!--    <script src="jquery-3.2.1.min.js"></script>-->
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    <script>
        var socket;
        if(typeof(WebSocket) == "undefined") 
            console.log("您的浏览器不支持WebSocket");
        else
            console.log("您的浏览器支持WebSocket");

            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            socket = new WebSocket("ws://localhost:18085/socket/zhangsan");
            //打开事件
            socket.onopen = function() 
                console.log("Socket 已打开");
            ;
            //获得消息事件
            socket.onmessage = function(msg) 
                console.log(msg.data);
                //发现消息进入    调后台获取
                //getCallingList();
                $("#msg").append(msg.data+"</br>");
            ;
            //关闭事件
            socket.onclose = function() 
                console.log("Socket已关闭");
            ;
            //发生了错误事件
            socket.onerror = function() 
                alert("Socket发生了错误");
            

            //关闭连接
            function closeWebSocket() 
                socket.close();
            

            //发送消息
            function send() 
                var message = document.getElementById('text').value;
                socket.send(message);
            
        
    </script>
</head>
<body>
<div id="msg"></div>
</body>
</html>

服务端

工程结构如下:

1)引入依赖包

在工程中引入如下依赖:

<!--websocket-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2)websocket消息处理

在服务端,我们可以创建一个类 WebSocketServer,并暴露websocket地址出去,代码如下:

package com.yyl.wsdemo.websocket;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;


@Slf4j
@ServerEndpoint(value = "/socket/userid")
@Component
public class WebSocketServer 

    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private static Map<String, Session> sessions=new HashMap<String,Session>();

    //用户唯一标识符
    private String userid;

    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(@PathParam("userid") String userid, Session session) 
        sessions.put(userid,session);
        this.userid=userid;

        webSocketSet.add(this);     //加入set中
        try 
            sendMessage("连接成功");
         catch (IOException e) 
            log.error("websocket IO异常");
        
    

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() 
        webSocketSet.remove(this);  //从set中删除
        sessions.remove(userid);       //移除会话
    

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message) 
        log.info("来自客户端的消息:" + message);
        //群发消息
        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) 
        error.printStackTrace();
    

    /***
     * 群发
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException 
        for (Map.Entry<String, Session> sessionEntry : sessions.entrySet()) 
            sessionEntry.getValue().getBasicRemote().sendText(message);
        
    

    /***
     * 给指定用户发送消息
     * @param message
     * @param userid
     * @throws IOException
     */
    public void sendMessage(String message,String userid) throws IOException 
        System.out.println("=================");

        sessions.get(userid).getBasicRemote().sendText(message);
    

    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message) throws IOException 
        for (WebSocketServer item : webSocketSet) 
            try 
                item.sendMessage(message);
             catch (IOException e) 
                continue;
            
        
    

我们编写一个测试类 WebSocketController,用于实现给指定用户发消息,代码如下:

package com.yyl.wsdemo.controller;


import com.yyl.wsdemo.utils.Result;
import com.yyl.wsdemo.utils.StatusCode;
import com.yyl.wsdemo.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping(value = "/ws")
public class WebSocketController 

    @Autowired
    private WebSocketServer webSocketServer;

    /***
     * 模拟给指定用户发消息
     */
    @GetMapping(value = "/send/userid")
    public Result sendMessage(@PathVariable(value = "userid")String userid, String msg) throws IOException, IOException 
        webSocketServer.sendMessage(msg,userid);
        return new Result(true, StatusCode.OK,"发送消息成功@");
    

测试效果如下:

代码链接

全部代码如下:https://download.csdn.net/download/weixin_45525272/87399321

WebSocket 应用场景

总结:适用于高即时性服务,比如聊天室的群聊,server顺序收到了张三,李四的消息,立即就推送给王五,不能让王五等半天。虽然Ajax也可以一秒一刷,让王五去问张三说话没,如果张三10分钟没说话,王五要去问600次。 用websocket 王五不用刷,等着就好了,服务器有了消息会自动给王五的。

基于websocket的事实通信的特点,其存在的应用场景大概有:

1.websocket社交订阅
对社交类的应用的一个裨益之处就是能够即时的知道你的朋友正在做什么。虽然听起来有点可怕,但是我们都喜欢这样做。你不会想要在数分钟之后才能知道一个家庭成员在馅饼制作大赛获胜或者一个朋友订婚的消息。你是在线的,所以你的订阅的更新应该是实时的。

2.websocket多玩家游戏

网络正在迅速转变为游戏平台。在不使用插件(我指的是Flash)的情况下,网络开发者现在可以在浏览器中实现和体验高性能的游戏。无论你是在处理DOM元素、css动画,html5的canvas或者尝试使用WebGL,玩家之间的互动效率是至关重要的。我不想在我扣动扳机之后,我的对手却已经移动位置。

3.websocket协同编辑/编程

我们生活在分布式开发团队的时代。平时使用一个文档的副本就满足工作需求了,但是你最终需要有一个方式来合并所有的编辑副本。版本控制系统,比如Git能够帮助处理某些文件,但是当Git发现一个它不能解决的冲突时,你仍然需要去跟踪人们的修改历史。通过一个协同解决方案,比如WebSocket,我们能够工作在同一个文档,从而省去所有的合并版本。这样会很容易看出谁在编辑什么或者你在和谁同时在修改文档的同一部分。

4.websocket收集点击流数据

分析用户与你网站的互动是提升你的网站的关键。HTTP的开销让我们只能优先考虑和收集最重要的数据部分。然后,经过六个月的线下分析,我们意识到我们应该收集一个不同的判断标准——一个看起来不是那么重要但是现在却影响了一个关键的决定。与HTTP请求的开销方式相比,使用Websocket,你可以由客户端发送不受限制的数据。想要在除页面加载之外跟踪鼠标的移动?只需要通过WebSocket连接发送这些数据到服务器,并存储在你喜欢的NoSQL数据库中就可以了(MongoDB是适合记录这样的事件的)。现在你可以通过回放用户在页面的动作来清楚的知道发生了什么。

5.股票基金报价

金融界瞬息万变——几乎是每毫秒都在变化。我们人类的大脑不能持续以那样的速度处理那么多的数据,所以我们写了一些算法来帮我们处理这些事情。虽然你不一定是在处理高频的交易,但是,过时的信息也只能导致损失。当你有一个显示盘来跟踪你感兴趣的公司时,你肯定想要随时知道他们的价值,而不是10秒前的数据。使用WebSocket可以流式更新这些数据变化而不需要等待。

6.体育实况更新

现在我们开始讨论一个让人们激情澎湃的愚蠢的东西——体育。我不是运动爱好者,但是我知道运动迷们想要什么。当爱国者在打比赛的时候,我的妹夫将会沉浸于这场比赛中而不能自拔。那是一种疯狂痴迷的状态,完全发自内心的。我虽然不理解这个,但是我敬佩他们与运动之间的这种强烈的联系,所以,最后我能做的就是给他的体验中降低延迟。如果你在你的网站应用中包含了体育新闻,WebSocket能够助力你的用户获得实时的更新。

7.多媒体聊天

视频会议并不能代替和真人相见,但当你不能在同一个屋子里见到你谈话的对象时,视频会议是个不错的选择。尽管视频会议私有化做的“不错”,但其使用还是很繁琐。我可是开放式网络的粉丝,所以用WebSockets getUserMedia API和html5音视频元素明显是个不错的选择。WebRTC的出现顺理成章的成为我刚才概括的组合体,它看起来很有希望,但其缺乏目前浏览器的支持,所以就取消了它成为候选人的资格。

8.基于位置的应用

越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹),你可以收集到更加细致化的数据。如果你想实时的更新网络数据仪表盘(可以说是一个监视运动员的教练),HTTP协议显得有些笨拙。借用WebSocket TCP链接可以让数据飞起来。

9.在线教育

上学花费越来越贵了,但互联网变得更快和更便宜。在线教育是学习的不错方式,尤其是你可以和老师以及其他同学一起交流。很自然,WebSockets是个不错的选择,可以多媒体聊天、文字聊天以及其它优势如与别人合作一起在公共数字黑板上画画…

10.论坛的消息广播
早期的论坛消息通知,靠的都是js轮询,现在有了websocket 可以改改了。

11.秒杀系统用户状态长链接
​ 在秒杀系统中,无论是非热点商品还是热点商品抢单,抢单完成后,我们应该要通知用户抢单状态,非热点商品可以直接响应抢单结果,但热点商品目前还没有实现通知响应,通知用户抢单状态用户可以通过定时向后台发出请求查询实现,但这种短连接方式效率低,会和服务器进行多次通信,这块我们可以使用长连接websocket实现。

01如何学习pythonweb开发从入门到实战(代码片段)

PythonWeb开发从入门到实战前言:PythonWeb是学校所学的课程,我希望在学习的同时通过写笔记的形式来记录我学习以及由学校学习转而自身对此方向感兴趣的一个过程,更多还是让自己在课程结束之后进行一个小的总结... 查看详情

01如何学习pythonweb开发从入门到实战(代码片段)

PythonWeb开发从入门到实战前言:PythonWeb是学校所学的课程,我希望在学习的同时通过写笔记的形式来记录我学习以及由学校学习转而自身对此方向感兴趣的一个过程,更多还是让自己在课程结束之后进行一个小的总结... 查看详情

sqoop从入门到实战(代码片段)

第1章Sqoop简介  Sqoop是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql,postgresql,...)间进行数据的高校传递,可以将一个关系型数据库(例如:MySQL,Oracle,Postgres等)中的数据导入到Hadoop的HDFS中,也可以将HDFS的数... 查看详情

protobuf从入门到实战(代码片段)

简介     从第一次接触Protobuf到实际使用已经有半年多,刚开始可能被它的名字所唬住,其实就它是一种轻便高效的数据格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。 优点平台... 查看详情

hive从入门到实战四(代码片段)

第8章压缩和存储(Hive高级)8.1Hadoop源码编译支持Snappy压缩8.1.1资源准备1、CentOS联网  配置CentOS能连接外网。Linux虚拟机pingwww.baidu.com是畅通的。  注意:采用root角色编译,减少文件夹权限出现问题。2、jar包准备(had... 查看详情

flink流式计算从入门到实战二(代码片段)

文章目录三、Flink运行架构1、JobManager和TaskManager2、并发度与Slots3、开发环境搭建4、提交到集群执行5、并行度分析6、Flink整体运行流程Flink流式计算实战专题二==楼兰三、Flink运行架构这一章重点是分析清楚运行架构以及并... 查看详情

hive从入门到实战一(代码片段)

第1章Hive入门1.1什么是Hive  Hive:由Facebook开源用于解决海量结构化日志的数据统计(分析数据的框架)。  Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类SQL查询功能。 &e... 查看详情

flume从入门到实战(代码片段)

第1章Flume概述1.1Flume定义  Flume(水槽)是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统。Flume基于流式架构,灵活简单。  在2009年Flume被捐赠了apache软件基金会,为hadoop相关组件... 查看详情

hive从入门到实战二(代码片段)

第4章DDL数据定义4.1创建数据库1)创建一个数据库,数据库在HDFS上的默认存储路径是/user/hive/warehouse/*.db。hive (default)> create database db_hive;2)避免要创建的数据库已经存在错误,增加ifnotexists判断。(标准写法)hi... 查看详情

azkaban从入门到实战(代码片段)

一概述1.1为什么需要工作流调度系统1)一个完整的数据分析系统通常都是由大量任务单元组成:  shell脚本程序,java程序,mapreduce程序、hive脚本等。2)各任务单元之间存在时间先后及前后依赖关系。3)为了很好地组织... 查看详情

flink流式计算从入门到实战三(代码片段)

文章目录四、FlinkDataStreamAPI1、Flink程序的基础运行模型2、Environment运行环境3、Source3.1基于File的数据源3.2基于Socket的数据源3.3基于集合的数据源3.4从Kafka读取数据3.5自定义Source4、Sink4.1输出到到控制台4.2输出到文件4.3输出到Socket4.4... 查看详情

kafka从入门到实战(代码片段)

第1章Kafka概述1.1消息队列1)点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)  点对点模型通常是一个基于拉取或者轮询的消息传送模型,这种模型从队列中请求信息,而不是将消息推送到客户端。这... 查看详情

shell脚本从入门到实战(代码片段)

Shell文章目录Shellshell概述Shell脚本入门1.脚本格式2.第一个Shell脚本:helloworld3.第二个Shell脚本:多命令处理Shell中的变量系统变量1.常用系统变量2.案例实操自定义变量1.基本语法2.变量定义规则特殊变量:$n特殊变量:$#... 查看详情

pandas从入门到实战(day1)(代码片段)

PandasPandas读取数据&pandas数据结构数据的读取读取csv文件读取txt文件读取excel文件读取sqlPandas数据结构(DataFrame&Seires)Seires创建SeiresDataFrame创建DataFrame从DataFrame查询出Seires取出一列/多列取出一行/多行数据的读取数据... 查看详情

hive从入门到实战五(代码片段)

 第10章Hive实战之谷粒影音10.1需求描述统计硅谷影音视频网站的常规指标,各种TopN指标:统计视频观看数Top10统计视频类别热度Top10统计出视频观看数最高的20个视频的所属视频类别以及对应视频类别的个数统计视频观看数Top5... 查看详情

flink流式计算从入门到实战五(代码片段)

文章目录八、Flink项目实战1、需求背景2、数据流程设计3、应用实现4、实现效果分析Flink流式计算实战专题五==楼兰八、Flink项目实战​这一个章节,我们来找一个常见的流式计算场景,将Flink真正用起来。1、需求背... 查看详情

flink流式计算从入门到实战一(代码片段)

文章目录一、理解Flink与流计算1、初识Flink2、Flink的适用场景3、流式计算梳理二、Flink安装部署1、Flink的部署方式2、获取Flink3、实验环境与前置软件4、集群搭建5、Standalone模式启动6、Yarn模式提交任务6.1、首先在yarn上启动yarn-sess... 查看详情

flink流式计算从入门到实战四(代码片段)

文章目录六、FlinkTableAPI和FlinkSQL1、TableAPI和SQL是什么?2、如何使用TableAPI3、基础编程框架3.1创建TableEnvironment3.2将流数据转换成动态表Table3.3将Table重新转换为DataStream4、扩展编程框架4.1临时表与永久表4.2AppendStream和RetractStream4... 查看详情