WebSocket的认识以及实现直播弹幕的代码

运用的场景

​ WebSocket通常运用在此类情况。服务端数据变化的时候,需要立即通知前端。

为什么采用WebSocket而不是其他协议呢,例如HTTP?

​ 首先,HTTP有比较大的局限性。

  1. HTTP请求只能由客户端发起请求,服务器才能产生响应,一个服务器响应只能对应一个客户端请求。
  2. HTTP1.0中每一次请求都需要建立一次连接,HTTP1.1中,请求头中Connection: keep-alive,这个属性使HTTP请求可以在一次连接的基础上,发生多个请求。
  3. 为了获取服务端是否有数据更新,客户端只能采用轮询或者长连接的方式,这两种方式不能保证好的时效性以及合理的服务器开销。

WebSocket则可以在建立WebSocket连接之后保持一个长连接,服务器与客户端都可以主动发送数据。

WebSocket协议与HTTP协议的关系

​ 我们知道HTTP协议是建立在TCP连接之上的。HTTP是应用层的协议,TCP是传输层的协议。而WebSocket连接在建立连接之前都需要客户端首先发送一个HTTP请求给服务器。但是这一次的请求头属性是这样子的Connection: Upgrade。Upgrade表明此次请求会被升级。请求头属性Upgrade: websocket,这表明此次请求被升级成了WebSocket协议。其中说明一下几个相关的属性,Sec-WebSocket-Key 属性是浏览器生成用来验证服务器是不是WebSocket代理的。Sec-WebSocket-Version属性表示的是协议的版本号。

​ 下面是一个上知乎时候的请求头

GET wss://messaging.zhihu.com/ws?udid=ADCCTzJorgyPTs8Wf4XDRIU9AxdiumMdLUI%3D HTTP/1.1
Host: messaging.zhihu.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: https://www.zhihu.com
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3346.9 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _zap=d9799872-2f14-453a-8046-3fcb52fbc31e; d_c0="ADCCTzJorgyPTs8Wf4XDRIU9AxdiumMdLUI=|1510642687"; z_c0="2|1:0|10:1519470533|4:z_c0|92:Mi4xcmxlMEFRQUFBQUFBTUlKUE1taXVEQ1lBQUFCZ0FsVk54WlYtV3dDSUlHMWY3b2thWHprOHFMUnVGMnExQ1hneExR|aa3f4aa167abfcf79a130d9dbc79b9db9939fca637a912b97dead6e83a1248cd"; __utma=51854390.2029141864.1525242282.1525242282.1525242282.1; __utmz=51854390.1525242282.1.1.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; __utmv=51854390.100-1|2=registration_date=20150520=1^3=entry_date=20150520=1; q_c1=f1f572658785464ba36fae7942eaaf67|1527147861000|1510377437000; _xsrf=aa18cc1d-c3f3-4307-8dd3-02c9c156e249
Sec-WebSocket-Key: Ef+CnxJwdZAIEqovO+ia3A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

​ 最后说明,在HTTP协议中,普通的URL为http://x.x.x,加密的HTTP则为https://x.x.x;在WebSocket协议中,普通的URL为ws:x.x.x,加密的WebSocket则为wss:x.x.x。再补充一点,不支持WebSocket的浏览器是没有办法使其支持的。只能采用其他方式。

实现直播弹幕

​ 先说明一下实现WebSocket的两种方式

  1. 使用tomcat的WebSocket实现,需要tomcat7以上版本,jdk7以上版本。

  2. 使用spring的WebSocket实现,需要spring4.X以上版本。

    本文采用的是tomcat实现的WebSocket。只需要服务端建立一个类。



/**
 * Created by ding on 2018/5/31.
 */


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint("/websocket/{liveId}")
public class WebSocket {
    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;

    /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识*/
//    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();

    /**
     * 用来存放每个直播相对应的WebSocket的set集合
     */
    private static Map<String, CopyOnWriteArraySet> liveMap = new ConcurrentHashMap<String, CopyOnWriteArraySet>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据。
     */
    private Session session;

    private String liveId;

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(@PathParam("liveId") String liveId, Session session) {
        this.liveId = liveId;
        this.session = session;
        CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();
        //若map中存在该live,取出WebSocket的set,并将新的WebSocket加入set,否则直接将WebSocket对象加入Set
        if (liveMap.containsKey(liveId)) {
            webSocketSet = liveMap.get(liveId);
        } else {

        }
        webSocketSet.add(this);     //加入set中
        liveMap.put(liveId, webSocketSet);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        CopyOnWriteArraySet<WebSocket> webSocketSet = liveMap.get(liveId);
        webSocketSet.remove(this);  //从set中删除
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
    }

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

    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * @Description: 群发消息
     * @Author: jiang-weirong
     * @Date: 2018/6/4 14:20
     */
    public void messageGroup(String message, String liveId){
        if(liveMap.containsKey(liveId)){
            CopyOnWriteArraySet<WebSocket> webSocketSet = liveMap.get(liveId);
            for (WebSocket item : webSocketSet) {
                try {
                    item.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }else {

        }
    }

    public static synchronized int getOnlineCount(String liveId) {
        if (liveMap.containsKey(liveId)) {
            CopyOnWriteArraySet<WebSocket> webSocketSet = liveMap.get(liveId);
            onlineCount = webSocketSet.size();
        } else {
            return 0;
        }
        return onlineCount;
    }
}

​ 类中的liveId表示的是直播房间编号,实现的主要功能:

  1. 可以通过该类获取各个直播房间的观众人数。
  2. 可以发送信息给在某个直播房间的所有观众。

前端的js建立WebSocket的代码:

    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/websocket/" + liveId);
    }
    else {
        alert('当前浏览器 Not support websocket')
    }

猜你喜欢

转载自blog.csdn.net/wantaceveryday/article/details/84938817