websocket 订阅
WebSocket 展开全文
WebSocket
信息
操作系统
跨平台
开发语言
JavaHTML/CSS查看源码 »
开源协议
未知
WebSocket
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准 RFC 6455 ,WebSocketAPI 被 W3C 定为标准。 在 WebSocket API 中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 现在,很多网站为了实现即时通讯(real-time),所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(time interval)(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request d的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求(request),然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽。 而最比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求(reuqest)。 在 WebSocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即使服务带来了两大好处: 1. Header 互相沟通的Header是很小的-大概只有 2 Bytes 2. Server Push 服务器可以主动传送数据给客户端
收起全文
精华内容
参与话题
问答
  • 看完让你彻底搞懂Websocket原理

    万次阅读 多人点赞 2016-03-11 15:46:38
    偶然在知乎上看到一篇回帖,瞬间觉得之前看的那么多资料都不及这一篇回帖让我对websocket的认识深刻有木有。所以转到我博客里,分享一下。比较喜欢看这种博客,读起来很轻松,不枯燥,没有布道师的阵仗,纯粹为分享...

    偶然在知乎上看到一篇回帖,瞬间觉得之前看的那么多资料都不及这一篇回帖让我对 websocket 的认识深刻有木有。所以转到我博客里,分享一下。比较喜欢看这种博客,读起来很轻松,不枯燥,没有布道师的阵仗,纯粹为分享。废话这么多了,最后再赞一个~

    一、websocket与http

    WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)

    首先HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个HTTP请求合并为一个,但是 Websocket 其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解

    有交集,但是并不是全部。

    另外Html5是指的一系列新的API,或者说新规范,新技术。Http协议本身只有1.0和1.1,而且跟Html本身没有直接关系。。通俗来说,你可以用HTTP协议传输非Html数据,就是这样=。=

    再简单来说,层级不一样。

    二、Websocket是什么样的协议,具体有什么优点

    首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。简单的举个例子吧,用目前应用比较广泛的PHP生命周期来解释。

    HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次HTTP请求就结束了。

    在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。

    教练,你BB了这么多,跟Websocket有什么关系呢?_(:з」∠)_好吧,我正准备说Websocket呢。。

    首先Websocket是基于HTTP协议的,或者说借用了HTTP的协议来完成一部分握手。

    首先我们来看个典型的 Websocket 握手(借用Wikipedia的。。)

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

    熟悉HTTP的童鞋可能发现了,这段类似HTTP协议的握手请求中,多了几个东西。我会顺便讲解下作用。

    Upgrade: websocket
    Connection: Upgrade

    这个就是Websocket的核心了,告诉 Apache 、 Nginx 等服务器:注意啦,我发起的是Websocket协议,快点帮我找到对应的助理处理~不是那个老土的HTTP。

    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13

    首先, Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,告诉服务器:泥煤,不要忽悠窝,我要验证尼是不是真的是Websocket助理。

    然后, Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。简单理解:今晚我要服务A,别搞错啦~

    最后, Sec-WebSocket-Version 是告诉服务器所使用的 Websocket Draft(协议版本),在最初的时候,Websocket协议还在 Draft 阶段,各种奇奇怪怪的协议都有,而且还有很多期奇奇怪怪不同的东西,什么Firefox和Chrome用的不是一个版本之类的,当初Websocket协议太多可是一个大难题。。不过现在还好,已经定下来啦~大家都使用的一个东西~ 脱水: 服务员,我要的是13岁的噢→_→

    然后服务器会返回下列东西,表示已经接受到请求, 成功建立Websocket啦!

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

    这里开始就是HTTP最后负责的区域了,告诉客户,我已经成功切换协议啦~

    Upgrade: websocket
    Connection: Upgrade

    依然是固定的,告诉客户端即将升级的是 Websocket 协议,而不是mozillasocket,lurnarsocket或者shitsocket。

    然后, Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key 。 服务器:好啦好啦,知道啦,给你看我的ID CARD来证明行了吧。。

    后面的, Sec-WebSocket-Protocol 则是表示最终使用的协议。

    至此,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行了。具体的协议就不在这阐述了。

    ——————技术解析部分完毕——————

    你TMD又BBB了这么久,那到底Websocket有什么鬼用, http long poll ,或者ajax轮询 不都可以实现实时信息传递么。

    好好好,年轻人,那我们来讲一讲Websocket有什么用。来给你吃点胡(苏)萝(丹)卜(红)

    三、Websocket的作用

    在讲Websocket之前,我就顺带着讲下 long poll 和 ajax轮询 的原理。

    ajax轮询

    ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

    场景再现:

    客户端:啦啦啦,有没有新信息(Request)

    服务端:没有(Response)

    客户端:啦啦啦,有没有新信息(Request)

    服务端:没有。。(Response)

    客户端:啦啦啦,有没有新信息(Request)

    服务端:你好烦啊,没有啊。。(Response)

    客户端:啦啦啦,有没有新消息(Request)

    服务端:好啦好啦,有啦给你。(Response)

    客户端:啦啦啦,有没有新消息(Request)

    服务端:。。。。。没。。。。没。。。没有(Response) —- loop

    long poll

    long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

    场景再现:

    客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)

    服务端:额。。 等待到有消息的时候。。来 给你(Response)

    客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request) -loop

    从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。

    何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。

    简单地说就是,服务器是一个很懒的冰箱(这是个梗)(不会、不能主动发起连接),但是上司有命令,如果有客户来,不管多么累都要好好接待。

    说完这个,我们再来说一说上面的缺陷(原谅我废话这么多吧OAQ)

    从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。

    ajax轮询 需要服务器有很快的处理速度和资源。(速度)long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

    所以 ajax轮询 和 long poll 都有可能发生这种情况。

    客户端:啦啦啦啦,有新信息么?

    服务端:月线正忙,请稍后再试(503 Server Unavailable)

    客户端:。。。。好吧,啦啦啦,有新信息么?

    服务端:月线正忙,请稍后再试(503 Server Unavailable)

    客户端:然后服务端在一旁忙的要死:冰箱,我要更多的冰箱!更多。。更多。。(我错了。。这又是梗。。)

    言归正传,我们来说Websocket吧

    通过上面这个例子,我们可以看出,这两种方式都不是最好的方式,需要很多资源。

    一种需要更快的速度,一种需要更多的’电话’。这两种都会导致’电话’的需求越来越高。

    哦对了,忘记说了HTTP还是一个状态协议。

    通俗的说就是,服务器因为每天要接待太多客户了,是个健忘鬼,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

    所以在这种情况下出现了,Websocket出现了。他解决了HTTP的这几个难题。首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。所以上面的情景可以做如下修改。

    客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)

    服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)

    客户端:麻烦你有信息的时候推送给我噢。。

    服务端:ok,有的时候会告诉你的。

    服务端:balabalabalabala

    服务端:balabalabalabala

    服务端:哈哈哈哈哈啊哈哈哈哈

    服务端:笑死我了哈哈哈哈哈哈哈

    就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你 )

    这样的协议解决了上面同步有延迟,而且还非常消耗资源的这种情况。那么为什么他会解决服务器上消耗资源的问题呢?

    其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。简单地说,我们有一个非常快速的 接线员(Nginx) ,他负责把问题转交给相应的 客服(Handler) 。

    本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。,导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。

    这样就可以解决客服处理速度过慢的问题了。

    同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输 identity info (鉴别信息),来告诉服务端你是谁。

    虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。

    但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。

    同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的。。),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)了

    ——————–

    至于怎么在不支持Websocket的客户端上使用Websocket。。答案是: 不能

    但是可以通过上面说的 long poll 和 ajax 轮询 来 模拟出类似的效果

    看评论 知道的原帖地址, 很抱歉转载的时候图片没有显示,

    转载:https://www.zhihu.com/question/20215561

    展开全文
  • SpringBoot2.0集成WebSocket,实现后台向前端推送信息

    万次阅读 多人点赞 2018-05-10 22:54:29
    什么是WebSocket? - 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? - 答案很简单,因为 HTTP 协议有一个缺陷:***通信只能由客户端发起***,...

    什么是WebSocket?

    这里写图片描述
    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

    为什么需要 WebSocket?

    初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

    • 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。
      这里写图片描述
      举例来说,我们想要查询当前的排队情况,只能是页面轮询向服务器发出请求,服务器返回查询结果。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此WebSocket 就是这样发明的。

    前言

    2020-10-20 教程补充:

    • 补充关于@Component@ServerEndpoint关于是否单例模式等的解答,感谢大家热心提问和研究。
    • Vue版本的websocket连接方法

    2020-01-05 教程补充:

    感谢大家的支持和留言,14W访问量是满满的动力!接下来还会有websocket+redis集群优化篇针对多ws服务器做简单优化处理,敬请期待!

    话不多说,马上进入干货时刻。

    maven依赖

    SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入

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

    WebSocketConfig

    启用WebSocket的支持也是很简单,几句代码搞定

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     * 开启WebSocket支持
     * @author zhengkai.blog.csdn.net
     */
    @Configuration  
    public class WebSocketConfig {  
    	
        @Bean  
        public ServerEndpointExporter serverEndpointExporter() {  
            return new ServerEndpointExporter();  
        }  
      
    } 
    

    WebSocketServer

    这就是重点了,核心都在这里。

    1. 因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

    2. 直接@ServerEndpoint("/imserver/{userId}")@Component启用即可,然后在里面实现@OnOpen开启连接,@onClose关闭连接,@onMessage接收消息等方法。

    3. 新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便IM之间对userId进行推送消息。单机版实现到这里就可以。

    4. 集群版(多个ws节点)还需要借助mysql或者redis等进行处理,改造对应的sendMessage方法即可。

    package com.softdev.system.demo.config;
    
    import java.io.IOException;
    import java.util.concurrent.ConcurrentHashMap;
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.stereotype.Component;
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    
    
    /**
     * @author zhengkai.blog.csdn.net
     */
    @ServerEndpoint("/imserver/{userId}")
    @Component
    public class WebSocketServer {
    
        static Log log=LogFactory.get(WebSocketServer.class);
        /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
        private static int onlineCount = 0;
        /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
        private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
        /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
        private Session session;
        /**接收userId*/
        private String userId="";
    
        /**
         * 连接建立成功调用的方法*/
        @OnOpen
        public void onOpen(Session session,@PathParam("userId") String userId) {
            this.session = session;
            this.userId=userId;
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                webSocketMap.put(userId,this);
                //加入set中
            }else{
                webSocketMap.put(userId,this);
                //加入set中
                addOnlineCount();
                //在线数加1
            }
    
            log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
    
            try {
                sendMessage("连接成功");
            } catch (IOException e) {
                log.error("用户:"+userId+",网络异常!!!!!!");
            }
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                //从set中删除
                subOnlineCount();
            }
            log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息*/
        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("用户消息:"+userId+",报文:"+message);
            //可以群发消息
            //消息保存到数据库、redis
            if(StringUtils.isNotBlank(message)){
                try {
                    //解析发送的报文
                    JSONObject jsonObject = JSON.parseObject(message);
                    //追加发送人(防止串改)
                    jsonObject.put("fromUserId",this.userId);
                    String toUserId=jsonObject.getString("toUserId");
                    //传送给对应toUserId用户的websocket
                    if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
                        webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                    }else{
                        log.error("请求的userId:"+toUserId+"不在该服务器上");
                        //否则不在这个服务器上,发送到mysql或者redis
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    
        /**
         *
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
            error.printStackTrace();
        }
        /**
         * 实现服务器主动推送
         */
        public void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }
    
    
        /**
         * 发送自定义消息
         * */
        public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
            log.info("发送消息到:"+userId+",报文:"+message);
            if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
                webSocketMap.get(userId).sendMessage(message);
            }else{
                log.error("用户"+userId+",不在线!");
            }
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            WebSocketServer.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketServer.onlineCount--;
        }
    }
    

    消息推送

    至于推送新信息,可以再自己的Controller写个方法调用WebSocketServer.sendInfo();即可

    
    import com.softdev.system.demo.config.WebSocketServer;
    import org.springframework.http.ResponseEntity;
    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 org.springframework.web.servlet.ModelAndView;
    import java.io.IOException;
    
    /**
     * WebSocketController
     * @author zhengkai.blog.csdn.net
     */
    @RestController
    public class DemoController {
    
        @GetMapping("index")
        public ResponseEntity<String> index(){
            return ResponseEntity.ok("请求成功");
        }
    
        @GetMapping("page")
        public ModelAndView page(){
            return new ModelAndView("websocket");
        }
    
        @RequestMapping("/push/{toUserId}")
        public ResponseEntity<String> pushToWeb(String message, @PathVariable String toUserId) throws IOException {
            WebSocketServer.sendInfo(message,toUserId);
            return ResponseEntity.ok("MSG SEND SUCCESS");
        }
    }
    
    

    页面发起

    页面用js代码调用websocket,当然,太古老的浏览器是不行的,一般新的浏览器或者谷歌浏览器是没问题的。还有一点,记得协议是ws的,如果使用了一些路径类,可以replace(“http”,“ws”)来替换协议。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>websocket通讯</title>
    </head>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        var socket;
        function openSocket() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else{
                console.log("您的浏览器支持WebSocket");
                //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
                //等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
                //var socketUrl="${request.contextPath}/im/"+$("#userId").val();
                var socketUrl="http://localhost:9999/demo/imserver/"+$("#userId").val();
                socketUrl=socketUrl.replace("https","ws").replace("http","ws");
                console.log(socketUrl);
                if(socket!=null){
                    socket.close();
                    socket=null;
                }
                socket = new WebSocket(socketUrl);
                //打开事件
                socket.onopen = function() {
                    console.log("websocket已打开");
                    //socket.send("这是来自客户端的消息" + location.href + new Date());
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    console.log(msg.data);
                    //发现消息进入    开始处理前端触发逻辑
                };
                //关闭事件
                socket.onclose = function() {
                    console.log("websocket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    console.log("websocket发生了错误");
                }
            }
        }
        function sendMessage() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else {
                console.log("您的浏览器支持WebSocket");
                console.log('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
                socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
            }
        }
    </script>
    <body>
    <p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
    <p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
    <p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
    <p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
    <p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
    </body>
    
    </html>
    

    运行效果

    • v20200105,加入开源项目spring-cloud-study-websocket,更新运行效果,更方便理解。
    • v1.1的效果,刚刚修复了日志,并且支持指定监听某个端口,代码已经全部更新,现在是这样的效果
    1. 打开两个页面,按F12调出控控制台查看测试效果:
    页面 参数
    http://localhost:9999/demo/page fromUserId=10,toUserId=20
    http://localhost:9999/demo/page fromUserId=20,toUserId=10

    分别开启socket,再发送消息
    在这里插入图片描述
    在这里插入图片描述
    2. 向前端推送数据:

    • http://localhost:9999/demo/push/10?message=123123

    在这里插入图片描述
    通过调用push api,可以向指定的userId推送信息,当然报文这里乱写,建议规定好格式。

    后续

    针对简单IM的业务场景,进行了一些优化,可以看后续的文章SpringBoot2+WebSocket之聊天应用实战(优化版本)(v20201005已整合)

    主要变动是CopyOnWriteArraySet改为ConcurrentHashMap,保证多线程安全同时方便利用map.get(userId)进行推送到指定端口。

    相比之前的Set,Set遍历是费事且麻烦的事情,而Map的get是简单便捷的,当WebSocket数量大的时候,这个小小的消耗就会聚少成多,影响体验,所以需要优化。在IM的场景下,指定userId进行推送消息更加方便。

    Websocker注入Bean问题

    关于这个问题,可以看最新发表的这篇文章,在参考和研究了网上一些攻略后,项目已经通过该方法注入成功,大家可以参考。
    关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法

    netty-websocket-spring-boot-starter

    Springboot2构建基于Netty的高性能Websocket服务器(netty-websocket-spring-boot-starter)
    只需要换个starter即可实现高性能websocket,赶紧使用吧

    Springboot2+Netty+Websocket

    Springboot2+Netty实现Websocket,使用官方的netty-all的包,比原生的websocket更加稳定更加高性能,同等配置情况下可以handle更多的连接。

    代码样式全部已经更正,也支持websocket连接url带参数功能,另外也感谢大家的阅读和评论,一起进步,谢谢!~~

    ServerEndpointExporter错误

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘serverEndpointExporter’ defined in class path resource [com/xxx/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

    感谢@来了老弟儿 的反馈:

    如果tomcat部署一直报这个错,请移除 WebSocketConfig@Bean ServerEndpointExporter 的注入 。

    ServerEndpointExporter 是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig配置类和@ServerEndpoint注解实例。使用规则也很简单:

    1. 如果使用默认的嵌入式容器 比如Tomcat 则必须手工在上下文提供ServerEndpointExporter
    2. 如果使用外部容器部署war包,则不需要提供提供ServerEndpointExporter,因为此时SpringBoot默认将扫描服务端的行为交给外部容器处理,所以线上部署的时候要把WebSocketConfig中这段注入bean的代码注掉。

    正式项目的前端WebSocket框架 GoEasy

    感谢kkatrina的补充,正式的项目中,一般是用第三方websocket框架来做,稳定性、实时性有保证的多,也会包括一些心跳、重连机制。

    GoEasy专注于服务器与浏览器,浏览器与浏览器之间消息推送,完美兼容世界上的绝大多数浏览器,包括IE6, IE7之类的非常古老的浏览器。支持Uniapp,各种小程序,react,vue等所有主流Web前端技术。
    GoEasy采用 发布/订阅 的消息模式,帮助您非常轻松的实现一对一,一对多的通信。
    https://www.goeasy.io/cn/doc/

    @Component@ServerEndpoint关于是否单例模式,能否使用static Map等一些问题的解答

    看到大家都在热心的讨论关于是否单例模式这个问题,请大家相信自己的直接,如果websocket是单例模式,还怎么服务这么多session呢。

    1. websocket是原型模式@ServerEndpoint每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式。
    2. Spring的@Component默认是单例模式,请注意,默认 而已,是可以被改变的。
    3. 这里的@Component仅仅为了支持@Autowired依赖注入使用,如果不加则不能注入任何东西,为了方便。
    4. 什么是prototype 原型模式? 基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例B的话,就需要使用原型模式。
    5. 关于在原型模式下使用static 的webSocketMap,请注意这是ConcurrentHashMap ,也就是线程安全/线程同步的,而且已经是静态变量作为全局调用,这种情况下是ok的,或者大家如果有顾虑或者更好的想法的化,可以进行改进。 例如使用一个中间类来接收和存放session。
    6. 为什么每次都@OnOpen都要检查webSocketMap.containsKey(userId) ,首先了为了代码强壮性考虑,假设代码以及机制没有问题,那么肯定这个逻辑是废的对吧。但是实际使用的时候发现偶尔会出现重连失败或者其他原因导致之前的session还存在,这里就做了一个清除旧session,迎接新session的功能。

    Vue版本的websocket连接

    感谢**@GzrStudy**的贡献,供大家参考。

    <script>
    export default {
        data() {
            return {
                socket:null,
                userId:localStorage.getItem("ms_uuid"),
                toUserId:'2',
                content:'3'
            }
        },
      methods: {
        openSocket() {
          if (typeof WebSocket == "undefined") {
            console.log("您的浏览器不支持WebSocket");
          } else {
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            //等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
            //var socketUrl="${request.contextPath}/im/"+$("#userId").val();
            var socketUrl =
              "http://localhost:8081/imserver/" + this.userId;
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            console.log(socketUrl);
            if (this.socket != null) {
              this.socket.close();
              this.socket = null;
            }
            this.socket = new WebSocket(socketUrl);
            //打开事件
            this.socket = new WebSocket(socketUrl);
            //打开事件
            this.socket.onopen = function() {
              console.log("websocket已打开");
              //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            this.socket.onmessage = function(msg) {
              console.log(msg.data);
              //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            this.socket.onclose = function() {
              console.log("websocket已关闭");
            };
            //发生了错误事件
            this.socket.onerror = function() {
              console.log("websocket发生了错误");
            };
          }
        },
        sendMessage() {
          if (typeof WebSocket == "undefined") {
            console.log("您的浏览器不支持WebSocket");
          } else {
            console.log("您的浏览器支持WebSocket");
            console.log(
              '{"toUserId":"' +
                 this.toUserId +
                '","contentText":"' +
                 this.content +
                '"}'
            );
            this.socket.send(
              '{"toUserId":"' +
                 this.toUserId +
                '","contentText":"' +
                 this.content +
                '"}'
             );
        
        }
    }
    
    展开全文
  • websocket深入浅出

    万次阅读 多人点赞 2018-12-17 16:33:17
    websocket简介 websocket是什么 答: 它是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 为什么需要websocket? 疑问? 我们已经有了 HTTP 协议,为什么还需要另一个协议?它能...

    websocket简介

    websocket是什么

    答: 它是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

    为什么需要websocket? 疑问? 我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

    答:

    • 因为 HTTP 协议有一个缺陷:通信只能由客户端发起
    • 我们都知道轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开), 因此websocket应运而生。

    简介

    WebSocket用于在Web浏览器和服务器之间进行任意的双向数据传输的一种技术。WebSocket协议基于TCP协议实现,包含初始的握手过程,以及后续的多次数据帧双向传输过程。其目的是在WebSocket应用和WebSocket服务器进行频繁双向通信时,可以使服务器避免打开多个HTTP连接进行工作来节约资源,提高了工作效率和资源利用率。

    WebSocket目前支持两种统一资源标志符wswss,类似于HTTP和HTTPS。

    实现原理

    浏览器发出webSocket的连线请求,服务器发出响应,这个过程称为握手,握手的过程只需要一次,就可以实现持久连接。

    握手与连接

    浏览器发出连线请求,此时的request如下:

    request

    通过get可以表明此次连接的建立是以HTTP协议为基础的,返回101状态码。

    如果不是101状态码,表示握手升级的过程失败了

    101是Switching Protocols,表示服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送这个响应后的空档,将http升级到webSocket

    其中UpgradeConnection字段告诉服务端,发起的是webSocket协议

    Sec-WebSocket-Key是浏览器经过Base64加密后的密钥,用来和response里面的Sec-WebSocket-Accept进行比对验证

    Sec-WebSocket-Version是当前的协议版本

    Sec-WebSocket-Extensions是对WebSocket的协议扩展

    服务器接到浏览器的连线请求返回结果如下:

    response

    UpgradeConnection来告诉浏览器,服务已经是基于webSocket协议的了,让浏览器也遵循这个协议

    Sec-WebSocket-Accept是服务端确认后并加密后的Sec-WebSocket-Accept

    至此,webSocket连接成功,接下来就是webSocket的协议了。

    客户端的简单示例

    var ws = new WebSocket("wss://echo.websocket.org");
    
    ws.onopen = function(evt) { 
      console.log("Connection open ..."); 
      ws.send("Hello WebSockets!");
    };
    
    ws.onmessage = function(evt) {
      console.log( "Received Message: " + evt.data);
      ws.close();
    };
    
    ws.onclose = function(evt) {
      console.log("Connection closed.");
    }; 
    
    ws.onerror = function(evt) {
      console.log("error!!!"); 
    }; 
    

    客户端的 API

    • 以下 API 用于创建 WebSocket 对象
    var ws = new WebSocket('ws://echo.websocket.org');
    
    • websocket 属性
    ws.readyState
    CONNECTING:值为0,表示正在连接。
    OPEN:值为1,表示连接成功,可以通信了。
    CLOSING:值为2,表示连接正在关闭。
    CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
    
    ws.bufferedAmount
    只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
    
    • WebSocket 事件
    事件       事件处理程      描述
    open     ws.onopen      连接建立时触发
    message  ws.onmessage   客户端接收服务端数据时触发
    error    ws.onerror     通信发生错误时触发
    close    ws.onclose     连接关闭时触发
    

    如果要指定多个回调函数,可以使用addEventListener方法

    ws.addEventListener('open', function (event) {
      ws.send('Hello Server!');
    });
    ws.addEventListener("close", function(event) {
      ...
      // handle close event
    });
    ws.addEventListener("message", function(event) {
      var data = event.data;
      ...
      // 处理数据
    });
    
    • websocket 方法
    方法                  描述
    ws.send()            使用连接发送数据
    ws.close()           关闭链接
    

    node 搭建服务器

    ws模块

    ws是一个websocket库,可以用了创建服务器。

    新建server.js 输入以下代码

    const WebSocket = require('ws');
     
    const wss = new WebSocket.Server({ port: 8181 });
     
    wss.on('connection', function connection(ws) {
      ws.on('message', function(message){
        wss.clients.forEach(function(ws){ // 看这里看这里  wss.clients 拿到所有的连接
              ws.send(message) // 发送消息
          })
      })
    });
    
    

    新建index.html 这里只列举下js代码

    <script>
    var ws = new WebSocket('ws://localhost:8181')
    ws.onopen = function(e){
        console.log('开始连接服务器')
        $('.btn-primary').click(function(){
            ws.send($('.form-control').val()) // 发送消息
          })
          ws.onmessage = function(msg){ // 监听消息
            $('body').append('<p>'+ msg.data +'</p>')
          }
          $('.btn-danger').click(function(){
            ws.close()  //  关闭连接
          })
    }
    </script>
    

    完整代码请戳github

    这里使用ws模块实现了两个demo,运行server.js后请自行查看。

    Socket.io

    简介

    Socket.io是一个webSocket库,目标是构建不同浏览器和移动设备上使用的实时应用。它会自动根据浏览器从webSocket ajax长轮询 ifrane流等各种方式选择最佳的方式。

    特点

    Socket.io主要有以下几点:

    1、实时分析:将数据推送到客户端,这些客户端会被表示为实时计数器,图表或日志客户
    2、实时通讯和聊天:几行代码就可以实现一个简单的聊天室
    3、二进制流传输:支持任何形式的二进制文件传输,例如:图片,视频,音频等
    4、文档合并:允许多个用户同时编辑一个文档,并且能够看到每个用户做出的修改

    聊天室的实现

    Socket.io上面有个入门的聊天室demo,基于node-http-server或者express,玩了koa以后,觉得koa很清爽,所以打算用koa来实现聊天室。

    首先创建simple-koa-chat文件夹,用来存放我们的代码。

    执行npm init -y命令生成package.json文件

    执行npm i koa socket.io -D 安装koa和socket.io,并添加到devDependencies依赖

    执行mkdir src && cd src && touch index.html创建src文件夹,并在里面创建index.html

    执行cd../ && touch app.js,回到根目录下,创建app.js。

    执行完后,在编辑器里面打开,此时目录结构如下:

    目录结构

    编辑app.js文件

    	
    	const Koa = require('koa')
    	const app = new Koa()
    	const fs = require('fs')
    	const server = require('http').Server(app.callback())
    	const io = require('socket.io')(server)
    	
    	
    	app.use(async (ctx, next)=>{
    		ctx.response.type = 'html'
    		ctx.body = await fs.createReadStream('./src/index.html')
    	})
    	
    	server.listen(8080, ()=>{
    		console.log('open http://localhost:8080')
    	});
    	io.on('connection', function(socket){
    		socket.on('chat message', function(msg){
    			io.emit('chat message', mas)
    		})
    	})
    

    编辑index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Socket.IO chat</title>
        <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
      </head>
      <style>
        li{
          padding: 4px 10px;
          border: 1px solid #eee;
          width:fit-content;
          border-radius: 5px;
          margin-bottom: 10px;
        }
      </style>
      <body>
        <div class="container" style="margin-top:100px">
            <div class="panel panel-default">
                <div class="panel-heading">Socket.IO 简易聊天室</div>
                <div class="panel-body">
                    <div class="col-md-12">
                        <ul class="messages pull-letf list-unstyled"></ul>
                        <form action="">
                          <div class="form-group">
                            <textarea class="form-control"  placeholder="输入发言内容"></textarea>
                          </div>
                          <button type="button" class="btn btn-primary">发言</button>
                        </form>
                    </div>
                </div>
              </div>
        </div>
        
        
        <script src="/socket.io/socket.io.js"></script>
        <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
        <script>
            $(function () {
                var socket = io();
                $('.btn').click(function(){
                  socket.emit('chat message', $('.form-control').val());
                  $('.form-control').val('');
                });
    
                // 监听 chat message事件 
                socket.on('chat message', function(msg){
                  $('.messages').append($('<li>').text(msg));
                });  
            });
        </script>
      </body>
    </html>
    
    

    执行npm run start 启动我们的服务。

    打开浏览器你可以看到如下的页面

    简易聊天室

    Socket.io API

    Socket.io由两部分组成:

    1、服务端 挂载或集成到nodeJS http服务器 socket.io

    2、客户端 加载到浏览器的客户端 socket.io-client

    先来说下服务端集成,分为简单的两步:

    1、引入模块并实例化

    // 这里使用koa框架,其他框架原理都一样
    
    const Koa = require('koa')
    const app = new Koa()
    
    const http = require('http').Server(app.callback())
    const io = require('socket.io')(http)
    
    // 引入`koa`并且初始化,引入`http`模块,将`koa`的回调当作`http.Server`的回调函数,最后将http传入实例化一个`socket.io`。
    

    2、设置连接后的操作

    io.on('connection', function(socket){
      socket.on('chat', function(msg){
        socket.emit('client', msg)
      })
    })
    
    // io.on函数接收'connection'字符串做为客户端发起连接的事件,连接成功后,调用带有		socket参数的回调函数。接收一个chat自定义的事件,使用socket.emit方法发送消息
    
    

    服务端集成好后,接下来是客户端

    </body>标签中添加以下代码

    <script src="/socket.io/socket.io.js"></script>
    <script>
      var socket = io()
      
      socket.emit('chat', 'hello')
      
      socket.on('client', function(data){
        console.log(data)
      })
    </script>
      
      <!-- 加载客户端的js文件,调用io() 函数, 初始化socket对象 发送chat事件到服务端,这时候服务端接收到了chat事件,并发出了client事件,浏览器接收到了client事件,将数据打印到了控制台上。 -->
    

    流程大概如下:
    流程

    我给你,你给我,来而不往非礼也,哲学啊。

    emit和on函数

    通过上图可以看到,每端总会接收事件和发送事件,socket最关键的就是emit和on两个函数,所有的交互其实就是这两个函数的情感纠葛,你来我往。

    emit用来发送一个事件(事件名称用字符串表示),名称可以自定义也可使用默认的事件名称,接着是一个对象,表示发送的内容,如:socket.emit('chat', {'name':'zhangsan'})

    on用来接收一个事件(事件名称用字符串表示),然后是响应事件的回调函数,其中函数里面的参数就是收到的数据。如socket.on('chat',function(data){console.log(data)})

    服务端默认事件一览

    io.on('connection', function(socket){}) socket连接成功时触发,用于初始化

    socket.on('message', function(data, callback){}) 接收客户端通过socket.send传送的消息,data是传输的消息,callback是收到消息后要执行的函数

    socket.on('anything', function(data){}) 收到任何事件都会触发

    socket.on('disconnect', function(){}) socket失去链接时触发,包括关闭浏览器,主动断开,掉线等情况

    进阶

    1、怎么实现私聊?

    每个socket都会有一个唯一的id,私聊的实现方式就是找到这个socket对象,发送事件,浏览器接收事件就实现了私聊。

    现在有A、B两个链接,B想发送给A,我们拿到A的id告诉服务器,我要发送给A,浏览器从socket数组里面找到这个对应的socket,然后发送事件。

    // client
    var obj = {
      toKey:'A的Id',
      message:'我想告诉A'
    }
    socket.emit('sendOne', obj)
    
    //server 
    // 这里我们借助underscore库
    
    socket.on('sendOne', function(obj){
      var ToSocket = _.findWhere(io.sockets.sockets, {id:obj.id}) // 看这里看这里
      ToSocket.emit('toOne', obj)
    })	
    
    // 
    socket.on('toOne', function(obj){
      // 这里写自己的逻辑 obj就是B私聊给A的信息
    })
    

    参考上面API,我们可以将聊天室一步步的丰富起来,添加更多的功能,最后它大概长这样

    代码请戳

    简易聊天室在master分支,丰富后的聊天室在zjx分支,请自行查看

    服务器端的实现

    常用的 Node 实现有以下三种

    展开全文
  • 基于Java的WebSocket的聊天室

    千人学习 2017-05-11 10:27:07
    HTML5中定义了WebSocket规范,该规范使得能够实现在浏览器端和服务器端通过WebSocket协议进行双向通信,运用Java语言和Tomcat的WebSocket实现作为服务端,实现一个聊天室的搭建。
  • WebSocket介绍和Socket的区别

    万次阅读 2019-06-03 13:55:26
    WebSocket介绍与原理 WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。 ——百度百科 目的:即时通讯,替代轮询 应用场景:网站...

    WebSocket介绍与原理 

    WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。

    ——百度百科

    目的:即时通讯,替代轮询

    应用场景:网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等。按照以往的技术能力通常是采用轮询、Comet技术解决。

    HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。

    缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上

    然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

    原理:WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。

    连接过程 —— 握手过程

    1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
    2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
    3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
    4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。

    WebSocket与HTTP的关系

    相同点

    • 都是一样基于TCP的,都是可靠性传输协议。
    • 都是应用层协议。

    不同点

    • WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
    • WebSocket是需要握手进行建立连接的。

    联系

    • WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

    WebSocket与Socket的关系

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

    HTML5与WebSocket的关系

    WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如:

    * 基于 C 的 libwebsocket.org
    * 基于 Node.js 的 Socket.io
    * 基于 Python 的 ws4py
    * 基于 C++ 的 WebSocket++
    * Apache 对 WebSocket 的支持: Apache Module mod_proxy_wstunnel
    * Nginx 对 WebSockets 的支持: NGINX as a WebSockets Proxy 、 NGINX Announces Support for WebSocket Protocol 、WebSocket proxying
    * lighttpd 对 WebSocket 的支持:mod_websocket

    WebSocket 机制

    WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

    WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

    非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示:

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

    使用 WebSocket 模式客户端与服务器的交互如下图:

    图 2.WebSocket 请求响应客户端服务器交互图

    上图对比可以看出,相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

    我们再通过客户端和服务端交互的报文看一下 WebSocket 通讯与传统 HTTP 的不同:

    在客户端,new WebSocket 实例化一个新的 WebSocket 客户端对象,连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL,WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而连接服务端端口,执行双方握手过程,客户端发送数据格式类似:

    清单 1.WebSocket 客户端连接报文

    GET /webfin/websocket/ HTTP/1.1
    Host: localhost
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
    Origin: 
    http://localhost
    :8080
    Sec-WebSocket-Version: 13
    http://localhost
    :8080
    Sec-WebSocket-Version: 13

    可以看到,客户端发起的 WebSocket 连接报文类似传统 HTTP 报文,”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。

    服务端收到报文后返回的数据格式类似:

    清单 2.WebSocket 服务端响应报文

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

    “Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。

    在开发方面,WebSocket API 也十分简单,我们只需要实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息,在下文 WebSocket 实现及案例分析部分,可以看到详细的 WebSocket API 及代码实现。

    WebSocket 实现
    如上文所述,WebSocket 的实现分为客户端和服务端两部分,客户端(通常为浏览器)发出 WebSocket 连接请求,服务端响应,实现类似 TCP 握手的动作,从而在浏览器客户端和 WebSocket 服务端之间形成一条 HTTP 长连接快速通道。两者之间后续进行直接的数据互相传送,不再需要发起连接和相应。

    以下简要描述 WebSocket 服务端 API 及客户端 API。

    WebSocket 服务端 API
    WebSocket 服务端在各个主流应用服务器厂商中已基本获得符合 JEE JSR356 标准规范 API 的支持,以下列举了部分常见的商用及开源应用服务器对 WebSocket Server 端的支持情况:


    表 1.WebSocket 服务端支持

    厂商 应用服务器 备注
    IBM WebSphere WebSphere 8.0 以上版本支持,7.X 之前版本结合 MQTT 支持类似的 HTTP 长连接
    甲骨文 WebLogic WebLogic 12c 支持,11g 及 10g 版本通过 HTTP Publish 支持类似的 HTTP 长连接
    微软 IIS IIS 7.0+支持
    Apache Tomcat Tomcat 7.0.5+支持,7.0.2X 及 7.0.3X 通过自定义 API 支持
      Jetty Jetty 7.0+支持


    以下我们使用 Tomcat7.0.5 版本的服务端示例代码说明 WebSocket 服务端的实现:

    JSR356 的 WebSocket 规范使用 javax.websocket.*的 API,可以将一个普通 Java 对象(POJO)使用 @ServerEndpoint 注释作为 WebSocket 服务器的端点,代码示例如下:

    清单 3.WebSocket 服务端 API 示例 

    @ServerEndpoint("/echo")
     public class EchoEndpoint {
    
     @OnOpen
     public void onOpen(Session session) throws IOException {
     //以下代码省略...
     }
     
     @OnMessage
     public String onMessage(String message) {
     //以下代码省略...
     }
    
     @Message(maxMessageSize=6)
     public void receiveMessage(String s) {
     //以下代码省略...
     } 
    
     @OnError
     public void onError(Throwable t) {
     //以下代码省略...
     }
     
     @OnClose
     public void onClose(Session session, CloseReason reason) {
     //以下代码省略...
     } 
     
     }

    代码解释:

    上文的简洁代码即建立了一个 WebSocket 的服务端,@ServerEndpoint("/echo") 的 annotation 注释端点表示将 WebSocket 服务端运行在 ws://[Server 端 IP 或域名]:[Server 端口]/websockets/echo 的访问端点,客户端浏览器已经可以对 WebSocket 客户端 API 发起 HTTP 长连接了。

    使用 ServerEndpoint 注释的类必须有一个公共的无参数构造函数,@onMessage 注解的 Java 方法用于接收传入的 WebSocket 信息,这个信息可以是文本格式,也可以是二进制格式。

    OnOpen 在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session 表明两个 WebSocket 端点对话连接的另一端,可以理解为类似 HTTPSession 的概念。

    OnClose 在连接被终止时调用。参数 closeReason 可封装更多细节,如为什么一个 WebSocket 连接关闭。

    更高级的定制如 @Message 注释,MaxMessageSize 属性可以被用来定义消息字节最大限制,在示例程序中,如果超过 6 个字节的信息被接收,就报告错误和连接关闭。

    注意:早期不同应用服务器支持的 WebSocket 方式不尽相同,即使同一厂商,不同版本也有细微差别,如 Tomcat 服务器 7.0.5 以上的版本都是标准 JSR356 规范实现,而 7.0.2x/7.0.3X 的版本使用自定义 API (WebSocketServlet 和 StreamInbound, 前者是一个容器,用来初始化 WebSocket 环境;后者是用来具体处理 WebSocket 请求和响应,详见案例分析部分),且 Tomcat7.0.3x 与 7.0.2x 的 createWebSocketInbound 方法的定义不同,增加了一个 HttpServletRequest 参数,使得可以从 request 参数中获取更多 WebSocket 客户端的信息,如下代码所示:

    清单 4.Tomcat7.0.3X 版本 WebSocket API

    public class EchoServlet extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol,
    HttpServletRequest request) {
     //以下代码省略....
    return new MessageInbound() {
     //以下代码省略....
    }
    protected void onBinaryMessage(ByteBuffer buffer)
    throws IOException {
     //以下代码省略...
    }
    protected void onTextMessage(CharBuffer buffer) throws IOException {
     getWsOutbound().writeTextMessage(buffer);
     //以下代码省略...
    }
    };
    }
    }

    因此选择 WebSocket 的 Server 端重点需要选择其版本,通常情况下,更新的版本对 WebSocket 的支持是标准 JSR 规范 API,但也要考虑开发易用性及老版本程序移植性等方面的问题,如下文所述的客户案例,就是因为客户要求统一应用服务器版本所以使用的 Tomcat 7.0.3X 版本的 WebSocketServlet 实现,而不是 JSR356 的 @ServerEndpoint 注释端点。

    WebSocket 客户端 API
    对于 WebSocket 客户端,主流的浏览器(包括 PC 和移动终端)现已都支持标准的 HTML5 的 WebSocket API,这意味着客户端的 WebSocket JavaScirpt 脚本具备良好的一致性和跨平台特性,以下列举了常见的浏览器厂商对 WebSocket 的支持情况:

    表 2.WebSocket 客户端支持

    浏览器 支持情况
    Chrome Chrome version 4+支持
    Firefox Firefox version 5+支持
    IE IE version 10+支持
    Safari IOS 5+支持
    Android Brower  Android 4.5+支持

    客户端 WebSocket API 基本上已经在各个主流浏览器厂商中实现了统一,因此使用标准 HTML5 定义的 WebSocket 客户端的 JavaScript API 即可,当然也可以使用业界满足 WebSocket 标准规范的开源框架,如 Socket.io。

    以下以一段代码示例说明 WebSocket 的客户端实现:

    清单 5.WebSocket 客户端 API 示例

    var ws = new WebSocket(“ws://echo.websocket.org”); 
    ws.onopen = function(){ws.send(“Test!”); }; 
    ws.onmessage = function(evt){console.log(evt.data);ws.close();}; 
    ws.onclose = function(evt){console.log(“WebSocketClosed!”);}; 
    ws.onerror = function(evt){console.log(“WebSocketError!”);};

    第一行代码是在申请一个 WebSocket 对象,参数是需要连接的服务器端的地址,同 HTTP 协议开头一样,WebSocket 协议的 URL 使用 ws://开头,另外安全的 WebSocket 协议使用 wss://开头。

    第二行到第五行为 WebSocket 对象注册消息的处理函数,WebSocket 对象一共支持四个消息 onopen, onmessage, onclose 和 onerror,有了这 4 个事件,我们就可以很容易很轻松的驾驭 WebSocket。

    当 Browser 和 WebSocketServer 连接成功后,会触发 onopen 消息;如果连接失败,发送、接收数据失败或者处理数据出现错误,browser 会触发 onerror 消息;当 Browser 接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数 evt 中包含 Server 传输过来的数据;当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。我们可以看出所有的操作都是采用异步回调的方式触发,这样不会阻塞 UI,可以获得更快的响应时间,更好的用户体验。

    展开全文
  • 前言  html5支持使用websocket协议...下面介绍使用C++实现的websocket++的简单使用。websocket++更详细介绍点此。  websocket++需要boost的支持,所以工程中需要包含boost的头文件和库。boost在VS中的如何使用参...
  • C++ websocket库 websocketpp/websocket++例子编译试用

    万次阅读 热门讨论 2016-09-07 17:15:00
    想试试C++的websocket,搜了下,发现websocket++用的人挺多,对主流浏览器都支持; 使用也方便,包含头文件即可(再加上boost头文件和库),没有其他依赖
  • websocket官方网址:http://websocket.org/ websocket++官方网址:https://www.zaphoyd.com/websocketpp websocket++使用手册:https://www.zaphoyd.com/websocketpp/manual/ 今天接收到一个任务,说是使用...
  • websocket++ vs2013编译环境,改一下vs属性->附件包含目录路径,可直接编译
  • WebSocket介绍和Socket的区别

    万次阅读 多人点赞 2017-01-17 14:43:55
    WebSocket介绍与原理 WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。 ——百度百科 目的:即时通讯,替代轮询 网站上的即时...
  • WebSocket 入门及开源库

    千次阅读 2019-03-07 16:45:00
    WebSocket 协议和知识 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和...
  • 网络协议、socket、webSocket

    万次阅读 多人点赞 2019-07-31 19:20:28
    一、网络协议 网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。 1、OSI七层协议 OSI是一个开放性的通信系统互连参考模型,他是一个定义得非常好的协议规范。OSI模型有7层结构,从上到下分别...
  • websocket

    千次阅读 2013-12-04 11:02:51
    http://www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-websocket-protocol-support ...WebSocket API 在本文中 实现 WebSocket WebSocket 编程 API 参考 IEBlog 文章 规范 相关主题 Internet Explo
  • 纠结了两天的
  • springboot+websocket实现服务端、客户端

    万次阅读 多人点赞 2019-01-12 12:54:43
    一、引言 小编最近一直在使用springboot框架...websocket主要功能就是实现网络通讯,比如说最经典的客服聊天窗口、您有新的消息通知,或者是项目与项目之间的通讯,都可以采用websocket来实现。 二、websocket介...
  • 在Spring Boot框架下使用WebSocket实现消息推送

    万次阅读 多人点赞 2016-12-23 16:48:37
    Spring Boot的学习持续进行中。前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的支持(使用Spring Boot开发Web项目(二)之...什么是WebSocket
  • SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)

    万次阅读 多人点赞 2019-06-12 16:20:11
    关于Netty Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。 MAVEN依赖 <dependencies> <!-- ...g...
  • websocket之参数传递和依赖注入

    万次阅读 2017-03-14 10:39:00
    我在通过websocket实现这个功能的过程中遇到了2个问题:websocket建立连接时需要传递参数(例如服务器ip,项目名称,日志文件位置等)进去;需要注入service 层的类,以便在onOpen()方法中进行数据查询和业务处理...
  • webSocket

    2016-10-17 15:21:49
    WebSocket概述 WebSocket是基于H5规范的,WebSocket 类用于发送和接收数据。 在 H5 规范中,定义了客户端和服务器通讯的 WebSocket 方式,在得到浏览器支持以后,WebSocket 将会取代 Comet成为服务器推送的方法。...
  • JAVA+WebSocket聊天室(WebSocket示例)

    千次下载 热门讨论 2012-11-18 16:27:39
    3 lib下的catalina.jar 和 tomcat-coyote.jar取自TOMCAT7.0.32,所以如果部署在TOMCAT7.0.32上就重复了,那么删除WebSocketDemo1/WebRoot/WEB-INF/lib/*.jar 如果部署在其它版本的TOMCAT可以保留(未试过)。

空空如也

1 2 3 4 5 ... 20
收藏数 115,379
精华内容 46,151
关键字:

websocket