WebSocket的介绍

WebSocket

websocket的背景

如今,不少网站为了实现推送技术,所用的技术都是 Ajax 轮询或者long poll 。这种传统的模式带来很明显的缺点,即浏览器须要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费不少的带宽等资源。javascript

websocket的特色

  • WebSocket 是 HTML5 开始提供的一种在单个 TCP 链接上进行全双工通信的协议。能更好的节省服务器资源和带宽,而且可以更实时地进行通信。
  • WebSocket 使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据。
  • 在 WebSocket API 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。
  • 浏览器经过 JavaScript 向服务器发出创建 WebSocket 链接的请求,链接创建之后,客户端和服务器端就能够经过 TCP 链接直接交换数据。

Ajax轮询

ajax轮询 的原理很是简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
场景再现:
客户端:啦啦啦,有没有新信息(Request)
服务端:没有(Response)
客户端:啦啦啦,有没有新消息(Request)
服务端:好啦好啦,有啦给你。(Response)
客户端:啦啦啦,有没有新消息(Request)
服务端:。。。。。没。。。。没。。。没有(Response) ---- loop前端

long poll

long poll 其实原理跟 ajax轮询 差很少,都是采用轮询的方式,不过采起的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起链接后,若是没消息,就一直不返回Response给客户端。直到有消息才返回,返回完以后,客户端再次创建链接,周而复始。
场景再现:
客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
服务端:额。。 等待到有消息的时候。。来 给你(Response)
客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request) -loopjava

从上面能够看出其实这两种方式,都是在不断地创建HTTP链接,而后等待服务端处理,能够体现HTTP协议的另一个特色,被动性。何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。web

从上面很容易看出来,无论怎么样,上面这两种都是很是消耗资源的。
ajax轮询 须要服务器有很快的处理速度和资源。(速度)
long poll 须要有很高的并发,也就是说同时接待客户的能力。(场地大小)
因此ajax轮询 和long poll 缺点很是明显。ajax

websocket 与Http的关系

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

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

首先,相对于HTTP这种非持久的协议来讲,Websocket是一个持久化的协议。安全

  • HTTP仍是一个无状态协议。通俗的说就是,服务器由于天天要接待太多客户了,是个健忘鬼,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。
  • HTTP的生命周期经过Request来界定,也就是一个Request 一个Response,那么HTTP1.0,此次HTTP请求就结束了。
  • 在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP链接中,能够发送多个Request,接收多个Response。
  • 可是 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。并且这个response也是被动的,不能主动发起。

因此在这种状况下出现了,Websocket出现了。他解决了HTTP的难题。服务器

websocket协议创建

WebSocket并非全新的协议,而是利用了HTTP协议来创建链接。咱们来看看WebSocket链接是如何建立的。websocket

首先,WebSocket链接必须由浏览器发起,由于请求协议是一个标准的HTTP请求,格式以下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不一样:

  1. GET请求的地址不是相似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个链接将要被转换为WebSocket链接;
  3. Sec-WebSocket-Key是用于标识这个链接,并不是用于加密数据;
  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

服务器若是接受该请求,就会返回以下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次链接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

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

websocket的客户端简单实例

var ws = new WebSocket("wss://localhost:8080/ws/asset");

//链接创建成功调用的方法
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.");
};

webSocket的服务端简单实例

@ServerEndpoint(value = "/ws/asset")
@Component
@Slf4j
public class WebSocketServer {
    

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


    /**
     * 链接创建成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        SessionSet.add(session);
        int cnt = OnlineCount.incrementAndGet(); // 在线数加1
        log.info("有链接加入,当前链接数为:{}", cnt);
        SendMessage(session, "链接成功");
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        SessionSet.remove(session);
        int cnt = OnlineCount.decrementAndGet();
        log.info("有链接关闭,当前链接数为:{}", cnt);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("来自客户端的消息:{}",message);
        System.out.println(session.toString());
        SendMessage(session, "收到消息,消息内容:"+message+session.getId());

    }

    /**
     * 出现错误
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId());
        error.printStackTrace();
    }

    /**
     * 发送消息,实践代表,每次浏览器刷新,session会发生变化。
     * @param session
     * @param message
     */
    public static void SendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("发送消息出错:{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 群发消息
     * @param message
     * @throws IOException
     */
    public static void BroadCastInfo(String message) throws IOException {
        for (Session session : SessionSet) {
            if(session.isOpen()){
                SendMessage(session, message);
            }
        }
    }

    /**
     * 指定Session发送消息
     * @param sessionId
     * @param message
     * @throws IOException
     */
    public static void SendMessage(String message,String sessionId) throws IOException {
        Session session = null;
        for (Session s : SessionSet) {
            if(s.getId().equals(sessionId)){
                session = s;
                break;
            }
        }
        if(session!=null){
            SendMessage(session, message);
        }
        else{
            log.warn("没有找到你指定ID的会话:{}",sessionId);
        }
    }
}

websocket的心跳机制

websockt心跳机制,不得不说很形象;那何为心跳机制,就是代表client与server的链接是否还在的检测机制;

若是不存在检测,那么网络忽然断开,形成的后果就是client、server可能还在傻乎乎的发送无用的消息,浪费了资源;怎样检测呢?原理就是定时向server发送消息,若是接收到server的响应就代表链接依旧存在;
这个心跳机制在分布式中也很常见,

demo

聊天室demo

(1)Client:客户端说明

​ 客户端的代码主要是使用H5的WebSocket进行实现,在前端网页中使用WebSocket进行链接服务端,而后创建Socket链接进行通信。

(2)Server:服务端说明

​ 服务端主要是创建多个客户端的关系,进行消息的中转等。客户端成功链接到服务端以后,就能够经过创建的通道进行发送消息到服务端,服务端接收到消息以后在群发给全部的客户端。

(3)客户端和服务端链接

var websocket = new WebSocket("ws://localhost:8080/myWs");

(4)客户端和服务端怎么发送消息?

客户端可使用webSocket提供的send()方法,以下代码:

var message = document.getElementById('text').value;  
websocket.send(message);

服务端怎么发送消息呢?主要是使用在成功创建链接的时候,建立的Session对象进行发送,以下代码:

session.getAsyncRemote().sendText("恭喜您成功链接上WebSocket");

(5)客户端和服务端怎么接受消息?

客户端接收消息消息使用的是websocket的onmessage回调方法,以下代码:

websocket.onmessage = function(event) {  
           //文本信息直接显示,若是是json信息,须要转换下在显示.  
       var data = event.data;  
       document.getElementById('message').innerHTML += data;  
}

服务端:

@OnMessage  
public void onMessage(String message, Session session) {  
        System.out.println("来自客户端的消息:" + message);  
}

(6)群聊原理(群发消息)

服务端在和客户端创建链接的时候,会建立一个webSocket对象,咱们会将每一个链接建立的对象进行报错到一个列表中,好比:CopyOnWriteArraySet(这是线程安全的);在要进行群发的时候,编写咱们的列表对象进行群发消息。

(7)单聊原理(一对一消息)

聊的时候,就无需遍历列表,而是须要知道发送者和接受者各自的Session对象,这个Session对象怎么获取呢?Session能够获取到sessionId,发送者在发送消息的时候,携带接收消息的sessionId,那么问题就演变成了:发送者怎么知道接受者的sessionId,那就是加入一个在线用户列表便可,在线用户列表中有用户的基本信息,包括sessionId。

websocket的实时推送

对比聊天室的demo,不一样之处在于,客户端连入服务器时候,会开启一个线程,在线程中对客户端进行推送数据。

关键代码:

/**
     * 接收到消息
     *
     * @param text
     */
    @OnMessage
    public void onMsg(Session session,@PathParam("param") String param) throws IOException {
        //记录客户端
        webSocketMaps.put(session, param);
        //实例化工做任务
        Operater operater =new Operater(session,param);
        //开启线程
        Thread thread = new Thread(operater);
        thread.start();
        logger.info("发送线程启动成功");
    }

目前业务还不是很复杂,后期功能添加时候,再进行扩展,关于这个实时推送,大概开了50个窗口就链接失败了。关于websocket的高并发,能够考虑。

相关文章
相关标签/搜索