1. 程式人生 > >基於websocket技術的網頁彈幕實現

基於websocket技術的網頁彈幕實現

前言:本彈幕是基於websocket技術實現的網頁彈幕,需要HTML5技術支援。具備如下功能:
1、開啟彈幕,從資料庫裡讀取歷史彈幕;
2、一個客戶端傳送彈幕,所有的客戶端均可以看到。

具體實現:
網頁端:

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request
.getServerName()+":"+request.getServerPort()+path+"/"; %>
<!DOCTYPE html> <html> <head> <title>index.html</title> <meta name="keywords" content="keyword1,keyword2,keyword3"> <meta name="description" content="this is my page"> <meta name
="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <style type="text/css"> body { background: url(images/01.jpg); no-repeat top center; font-size: 12px; font-family: "微軟雅黑"; } * { margin: 0; padding: 0; } /* screen start*/
.screen { width: 300px; height: 100px; background: #669900; } .dm { width: 100%; height: 100%; position: absolute; top: 0; left: 0; display: none; } .dm .d_screen .d_del { width: 38px; height: 38px; background: #600; display: block; text-align: center; line-height: 38px; text-decoration: none; font-size: 20px; color: #fff; border-radius: 19px; border: 1px solid #fff; z-index: 2; position: absolute; right: 20px; top: 20px; outline: none; } .dm .d_screen .d_del:hover { background: #F00; } .dm .d_screen .d_mask { width: 100%; height: 100%; background: #000; position: absolute; top: 0; left: 0; opacity: 0.6; filter: alpha(opacity = 60); z-index: 1; } .dm .d_screen .d_show { position: relative; z-index: 2; } .dm .d_screen .d_show div { font-size: 26px; line-height: 36px; font-weight: 500; position: absolute; top: 76px; left: 10; color: #fff; } /*end screen*/ /*send start*/ .send { width: 100%; height: 76px; position: absolute; bottom: 0; left: 0; border: 1px solid red; } .send .s_filter { width: 100%; height: 76px; background: #000; position: absolute; bottom: 0; left: 0; opacity: 0.6; filter: alpha(opacity = 60); } .send .s_con { width: 100%; height: 76px; position: absolute; top: 0; left: 0; z-index: 2; text-align: center; line-height: 76px; } .send .s_con .s_text { width: 800px; height: 36px; border: 0; border-radius: 6px 0 0 6px; outline: none; } .send .s_con .s_submit { width: 100px; height: 36px; border-radius: 0 6px 6px 0; outline: none; font-size: 14px; color: #fff; background: #65c33d; font-family: "微軟雅黑"; cursor: pointer; border: 1px solid #5bba32; } .send .s_con .s_submit:hover { background: #3eaf0e; } /*end send*/
</style> </head> <body> <form> <a href="#" id="startDm">開啟彈幕</a> <!-- dm start --> <div class="dm"> <!-- d_screen start --> <div class="d_screen"> <a href="#" class="d_del">X</a> <div class="d_mask"></div> <div class="d_show"> </div> </div> <!-- end d_screen --> <!-- send start --> <div class="send"> <div class="s_filter"></div> <div class="s_con"> <input type="text" class="s_text" maxlength="30"/><input type="button" value="發表評論" class="s_submit" id="btn"/> </div> </div> <!-- end send --> </div> <!-- end dm--> </form> <!-- <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"></script> --> <script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script> <!-- <script type="text/javascript" src="websocket.js" id="websocket"> </script> --> <script type="text/javascript"> $(function() { $("#startDm").click(function() { $("#startDm,.dm").toggle(1000); window.setTimeout(function(){ // reference to <head> var head = document.getElementsByTagName('head')[0]; // a new CSS /* var css = document.createElement('link'); css.type = "text/css"; css.rel = "stylesheet"; css.href = "new.css"; */ // a new JS var js = document.createElement("script"); js.type = "text/javascript"; js.src = "websocket.js"; // preload JS and CSS /* head.appendChild(css); */ head.appendChild(js); // preload image /* new Image().src = "new.png"; */ }, 1000) ; /* window.setTimeout("document.getElementById('websocket').src='websocket + js.js'" , 1000) ; */ /* var websocket=null; var _top=80; var index=0; var host=window.location.host; //判斷當前瀏覽器是否支援WebSocket if('WebSocket' in window){ websocket=new WebSocket("ws://192.168.50.155:8080/Danmu01/websocket"); } else{ alert("Not Support WebSocket!"); } //連線發生錯誤的回撥方法 websocket.onerror = function(){ setMessageInnerHTML("error"); }; //連線成功建立的回撥方法 websocket.onopen = function(){ setMessageInnerHTML("open"); } //接收到訊息的回撥方法 websocket.onmessage = function(event){ setMessageInnerHTML(event); } //連線關閉的回撥方法 websocket.onclose = function(){ setMessageInnerHTML("close"); } //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。 window.onbeforeunload = function(){ websocket.close(); } //將訊息顯示在網頁上 function setMessageInnerHTML(innerHTML){ $(".d_show").append("<div id='"+index+"'>"+ innerHTML.data + "</div>"); launch(); } //傳送訊息 function send(){ //var message = document.getElementById('text').value; var message = $(".s_text").val(); alert(message); websocket.send(message); } */ //init_screen(); }); $(".d_del").click(function() { $("#startDm,.dm").toggle(1000); //init_screen(); }); $("#btn").click(function(){ send(); }); $(".s_text").keydown(function() { var code = window.event.keyCode; if (code == 13)//回車鍵按下時,輸出到彈幕 { send(); } }); }); /* $("#startDm").click(function() { alert("forEach^^^") ; <c:forEach var="danmu" items="${allDanmu}"> alert("進入forEach迴圈" + ${danmu.danmuInfo}) ; $(".d_show").append("<div id='"+index+"'>"+ ${danmu.danmuInfo} + "</div>"); </c:forEach> }) ; */ function launch() { var _height = $(window).height(); var _left = $(window).width() - $("#"+index).width(); var time=10000; if(index%2==0) time=20000; _top+=80; if(_top>_height-100) _top=80; $("#"+index).css({ left:_left, top:_top, color:getRandomColor() }); $("#"+index).animate({ left:"-"+_left+"px"}, time, function(){}); index++; } /* //初始化彈幕 function init_screen() { var _top = 0; var _height = $(window).height(); $(".d_show").find("div").show().each(function() { var _left = $(window).width() - $(this).width(); var time=10000; if($(this).index()%2==0) time=20000; _top+=80; if(_top>_height-100) _top=80; $(this).css({ left:_left, top:_top, color:getRandomColor() }); $(this).animate({ left:"-"+_left+"px"}, time, function(){}); }); } */ //隨機獲取顏色值 function getRandomColor() { return '#' + (function(h) { return new Array(7 - h.length).join("0") + h })((Math.random() * 0x1000000 << 0).toString(16)) } </script> </body> </html>

這一段jsp程式碼部分借鑑了潭州學院Array老師的彈幕頁面,在此表示感謝。
通過研究上面一段程式碼可以發現,裡面設定了websocket.js的定時載入並且載入了jquery-1.11.1.min.js。後一個.js檔案可以在網上下到,只要把它放在對應的路徑下就可以了。下面提供websocket.js的程式碼。

websocket.js

var websocket=null;
var _top=80;
var index=0;

var host=window.location.host;
//判斷當前瀏覽器是否支援WebSocket
if('WebSocket' in window){
websocket=new WebSocket("ws://192.168.50.155:8080/Danmu01/websocket");/*("ws://"+host+"/Danmu/websocket");*/
}
else{
alert("Not Support WebSocket!");
}


//連線發生錯誤的回撥方法
websocket.onerror = function(){
    setMessageInnerHTML("error");
};

//連線成功建立的回撥方法
websocket.onopen = function(){
    setMessageInnerHTML("open");
}

//接收到訊息的回撥方法
websocket.onmessage = function(event){
    setMessageInnerHTML(event);
}

//連線關閉的回撥方法
websocket.onclose = function(){
    setMessageInnerHTML("close");
}


//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function(){
    websocket.close();
}


//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML){
$(".d_show").append("<div id='"+index+"'>"+ innerHTML.data + "</div>");
launch();
}

/*websocket.onmessage = function(msg) {
    alert(msg.data);
    setMessageInnerHTML(msg) ;
};*/


//傳送訊息
function send(){
    //var message = document.getElementById('text').value;
    var message = $(".s_text").val();
    /*alert($(".s_text").val(""));
    alert(message);*/
    websocket.send(message);
}

觀察websocket.js,可知在開始的時候例項化了一個Websocket類,構造方法中傳入的引數是”ws://192.168.50.155:8080/Danmu01/websocket”,代表專案所在伺服器的ip地址+埠號+專案名+websocket,其中websocket對應服務端程式碼中@ServerEndpoint(“/websocket”)的websocket。

服務端程式碼:

package com.danmu.websocket;


import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import com.deyao.danmu.pojo.*;






import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.deyao.danmu.factory.ServiceFactory;





@ServerEndpoint("/websocket")
public class MyWebsocket {



    private static CopyOnWriteArraySet<MyWebsocket> websocketPools=new CopyOnWriteArraySet<MyWebsocket>();


    private Session session;
    boolean index = false ;

/**
* 連線建立成功呼叫的方法
* @param session 可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
*/
    @OnOpen
    public void onOpen(Session session) throws Exception{
            this.session=session;
            websocketPools.add(this);
            index = true ; 
            onMessage("Welcome to DEYAO danmu ^_^" , this.session) ;
    }
/**
* 連線關閉呼叫的方法
*/
    @OnClose
    public void onClose(){
            websocketPools.remove(this);
    }
/**
* 收到客戶端訊息後呼叫的方法
* @param message 客戶端傳送過來的訊息
* @param session 可選的引數
*/
    @OnMessage
    public void onMessage(String message,Session session) throws Exception{
        if (index) {
                Map<String , Object> map = ServiceFactory.getIDanmuServiceInstance()
                    .list(1, 1000, "", "userNickname") ;
                List<Danmu> list = (List<Danmu>) map.get("allDanmu01") ;
                for(int i = 0 ; i < list.size() ; i++) {
                    String msg = list.get(i).getDanmuInfo() ;
                    this.session.getBasicRemote().sendText(msg);
                    System.out.println(msg);
            }
            index = false ;


        } else {
            Danmu danmu = new Danmu() ;
            danmu.setDanmuInfo(message);
            danmu.setUserId(null);
            danmu.setUserNickname(null);
            ServiceFactory.getIDanmuServiceInstance().insert(danmu);

        }
        for(MyWebsocket item:websocketPools) {
            try {
                item.send(message);

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }   
        }
    }


    private void send(String message) throws IOException{
        this.session.getAsyncRemote().sendText(message);
        //this.session.getBasicRemote().sendText(message);

    }

/**
* 發生錯誤時呼叫
* @param session
* @param error
*/
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("發生錯誤");
        error.printStackTrace();
    }

}

研究服務端程式碼,我們可以發現在初始的時候new了一個CopyOnWriteArraySet,通過的他的泛型可知他是儲存websocket.js中新建的Websocket物件的。因為有一個客戶端開始了彈幕就觸發websocket.js中的onOpen方法,也就有了一個新的session,也就執行了服務端的onOpen方法,所以,在服務端的onOpen方法中,就要將新的session加入到CopyWriteArraySet中,並且在此時要讀取資料庫中的歷史彈幕到前臺。所以在onOpen中呼叫了onMessage方法,將資料庫中的歷史彈幕資料呈現到前臺。這裡要注意一點是讀取歷史彈幕資料的時候要使用this.session.getBasicRemote().sendText(msg);
onMessage方法是有新彈幕資訊的時候自動呼叫的,這裡在onOpen方法中呼叫他是為了讓他讀出歷史彈幕資訊。onMessage中的for迴圈就是用來將一個客戶端傳送的新訊息推送到每一個session(即客戶端)。