WebSocket实战笔记

1.为什么选择webSokect?

由于项目中有个实时新增消息的提醒,以及定时提醒需求。

百度百科:

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocketAPI也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket优点:

在以前的消息推送机制中,用的都是 Ajax 轮询(polling),在特定的时间间隔由浏览器自动发出请求,将服务器的消息主动的拉回来,
这种方式是非常消耗资源的,因为它本质还是http请求,而且显得非常笨拙。而WebSocket 在浏览器和服务器完成一个握手的动作,
在建立连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。

传统方法对比:

一、轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。 
优点:后端程序编写比较容易。 
缺点:请求中有大半是无用,浪费带宽和服务器资源。 
实例:适于小型应用。
二、长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。 
优点:在无消息的情况下不会频繁的请求,耗费资源小。 
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。 
实例:WebQQ、Hi网页版、Facebook IM。
三、长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。 
优点:消息即时到达,不发无用请求;管理起来也相对方便。 
缺点:服务器维护一个长连接会增加开销。 
实例:Gmail聊天

四、Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。 
优点:实现真正的即时通信,而不是伪即时。 
缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。 
实例:网络互动游戏。

2.webSocket是什么?

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

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

在这里插入图片描述

第一步:客户端发起HTTP请求连接
第二步:服务端从请求头中取出Sec-WebSocket-Key的值
第三步:给Sec-WebSocket-Key值拼接一个magic_string的到一个新的value
第四步:给新的value先做sha1加密再做base64加密
第五步:拼接一个响应头
第六步:服务器将拼好的响应头发送给客户端
第七步:客户端解密Sec-WebSocket-Accept得到Sec-WebSocket-Key判断是否握手成功

3.webSocket能干什么?

1.社交订阅
对社交类的应用的一个裨益之处就是能够即时的知道你的朋友正在做什么。虽然听起来有点可怕,但是我们都喜欢这样做。你不会想要在数分钟之后才能知道一个家庭成员在馅饼制作大赛获胜或者一个朋友订婚的消息。你是在线的,所以你的订阅的更新应该是实时的。 2.多玩家游戏 网络正在迅速转变为游戏平台。在不使用插件(我指的是Flash)的情况下,网络开发者现在可以在浏览器中实现和体验高性能的游戏。无论你是在处理DOM元素、CSS动画,HTML5的canvas或者尝试使用WebGL,玩家之间的互动效率是至关重要的。我不想在我扣动扳机之后,我的对手却已经移动位置。

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

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

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

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

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

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

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

4.webSocket怎么用?

①.在gradle引入jar: spring-boot-starter-websocket

compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.0.4.RELEASE'

②.注入ServerEndpointExporter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author wyz
 * @create 2019-08-0111:17
 * 注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。
 * 要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,
 * 因为它将由容器自己提供和管理。
 */
@Configuration
@EnableWebSocket
@ComponentScan("com.augurit.xmjg.webSocket")
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

③.编写webSocket 服务端


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * WebScoket 服务工具类:用于后端向前端推送数据
 *
 * @author wyz
 * @create 2019-07-3116:40
 */
@Component
@ServerEndpoint(value = "/websocket") //开启websocket 服务
public class XmjgJytsWebSocket {

    public static Logger logger = LoggerFactory.getLogger(XmjgJytsWebSocket.class);

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

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

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //创建线程池 里面有5个线程
    private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        try {
            this.session = session;
            webSocketSet.add(this);     //加入set中
            addOnlineCount();           //统计在线数加1
            System.out.println("有新连接加入!当前在线人数为[ " + getOnlineCount() + " ],时间:" + LocalDateTime.now());
            sendMessage("您好!您是第" + getOnlineCount() + "个双工通信的用户!");//返回客户端的数据
        } catch (IOException e) {
            e.getStackTrace();
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //统计在线数减1
        System.out.println("有一连接关闭!当前在线人数为[ " + getOnlineCount() + " ], 时间:" + LocalDateTime.now());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message) {
        Runnable t = new Runnable() {
            @Override
            public void run() {
                try {
                    // 业务代码
                    System.out.println("客户端的消息:" + message);
                    sendMessage("收到您的消息为:" + message);//发送消息返回客户端
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
        fixedThreadPool.submit(t);//使用线程池
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 发送消息
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        //给session加上同步锁,避免出现多个线程同时往同一个session写数据,导致报错的情况。
        synchronized (session) {
            this.session.getBasicRemote().sendText(message);
        }

    }


    /**
     * 发送自定义消息--发送给所有在线用户
     */
    public static void sendInfo(String message) {

        for (XmjgJytsWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        XmjgJytsWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        XmjgJytsWebSocket.onlineCount--;
    }


}

④.客户端编写

//1.检查浏览器是否支持WebSocket
    if ("WebSocket" in window){
        alert("您的浏览器支持 WebSocket!");
        // 打开一个 web socket
        var ws = new WebSocket("ws://127.0.0.1:8000/xmjg/websocket");
        ws.onopen = function(){
            // Web Socket 已连接上,使用 send() 方法发送数据
            ws.send("我是一只小小鸟!!!!!");
            //alert("数据发送中...");
        };
        //接受服务器推送回来的信息后的回调函数
        ws.onmessage = function (evt){
            var received_msg = evt.data;
            alert(received_msg);
        };
        //WebSocket关闭后的回调函数
        ws.onclose = function(){
            // 关闭 websocket
            alert("连接已关闭...");
        };
        /*监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,
            防止连接还没断开就关闭窗口,server端会抛异常。同时关闭所有定时任务
         */
        window.onbeforeunload = function(){
            //关闭webSocket连接
            ws.close();
            //关闭定时任务
            endTask();
        }
    }else{
        // 浏览器不支持 WebSocket
        alert("您的浏览器不支持 WebSocket!");
    }

参考文章:
https://yq.aliyun.com/articles/656918
https://www.cnblogs.com/ttjsndx/p/9268800.html
https://blog.csdn.net/Jack_EUSong/article/details/79064081
https://baike.baidu.com/item/WebSocket/1953845?fr=aladdin
https://blog.csdn.net/qq_27409289/article/details/81814272
https://segmentfault.com/a/1190000010140660

发布了18 篇原创文章 · 获赞 2 · 访问量 663

猜你喜欢

转载自blog.csdn.net/qq_34699995/article/details/98598859
今日推荐