跳到主要內容區塊

計資中心電子報C&INC E-paper

技術論壇

透過 socket.io 來建立協同合作的電子白板
  • 卷期:v0030
  • 出版日期:0001-01-01

作者:楊德倫 / 臺灣大學計算機及資訊網路中心教學研究組幹事

在網頁使用者間以「即時」方式傳遞資訊的實際案例,例如:Facebook 的網頁版即時通訊、MSN 網頁線上客服…等透過 Node.js 等相關技術來開發專案的成果,已隨處可見,且相關案例不僅使用於文字傳輸,還可以透過 HTML 5 的 Canvas 元素,來建立協同合作的電子白板,以供大家線上討論之用。

前言
Socket.io 為一種 JavaScript 的 Library,透過 node.js來應用於伺服端的 library,以「事件」(Event)來進行前、後端訊息的接收與傳遞。建置相關機制前,務必先行安裝 Node.js 環境,讓 socket.io 得以順利運作。 socket.io 相關實例與建置方式,上一期「透過socket.io來建立人物移動聊天室」一文中已有提及,筆者在此並不贅述,讀者可先行參閱。新版 socket.io  已於數個月前進行更新,官方網站(以下稱為官網)也為入門提供相關實例分享,分為「Chat application」(聊天室)以及「collaborative whiteboard」(協同合作的電子白板),惟電子白板尚在建置,故筆者於官網釋出資訊與實例前,先行撰寫相關程式,以供各位讀者參考與運用。


圖(一)官方網站的 logo

基本用法
 socket.emit - 對一個特定的 socket 傳訊息
 socket.on – 對特定 event 的運行結果進行接收
 socket.broadcast.emit - 對目前 socket 之外所有線上的 socket 傳訊息
 io.sockets.emit - 對所有線上 socket 傳訊息

程式碼分享
eBoard.html

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>e-Board</title>

<!-- 自訂 css -->
<style>
<!--
*{
        margin: 0 auto;
    padding: 0;
        font-family: "微軟正黑體", "Oswald", sans-serif;
}

#wrapper{
        margin: 15px auto;
        padding: 0;
        width: 1000px;
        height: 800px;
        border: 2px solid;
}

#menu{
        float: left;
        width: 300px;
        height: 774px;
        margin: 10px;
        border: 2px solid;
}

#content{
        float: left;
        width: 652px;
        height: 774px;
        margin: 10px;
        border: 2px solid;
}

.clear{
        clear: left;
}
-->
</style>

<!-- 外部 javascript -->
<script src="https://cdn.socket.io/socket.io-1.0.6.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 自訂 javascript -->
<script>
$(document).ready(function() {
        /* socket.io 相關設定 */
       
        //連線位置與初始化
        var socket = io.connect('http://192.168.56.100:8080/');
       
        //連線作業
        socket.on('connect', function() {
                //宣告物件,放置訊息
                var obj = new Object;
                obj.name = prompt('尊姓大名?');
               
                //將輸入名稱傳到後端 node.js server 來通知其他人您已上線的訊息
                socket.emit('login', obj);
        });
       
        //上線通知
        socket.on('msg', function(data){
                $('#member_msg').append('<div>' + data.msg + '</div>');
        });
       
        //別人畫布上的動作,呈現在我們自己的頁面上
        socket.on('show', function(data){
                //設定筆尖大小
                $('#size').val( data.size );
                ctx.lineWidth = data.size;
               
                //繪圖
                ctx.beginPath(); 
        ctx.moveTo(data.x, data.y); 
        ctx.lineTo(data.new_x, data.new_y); 
        ctx.closePath(); 
        ctx.stroke();
        });
       
       
       
        /* 繪圖相關設定 */
        //宣告 canvas 元素
        var c = document.getElementsByTagName('canvas')[0];
       
        //設定 canvas 寬與高
        c.width  = 648;
        c.height = 770;
   
        //判斷畫布是否有動作的布林變數
        var drawing = false;
       
        //canvas 元素本身沒有畫作能力,僅為圖形容器,需要使用 javascript 來操作畫布
        //我們須要透過 getContext() 來取得一個提供在 canvas 上畫圖的方法與屬性之「物件」
    var ctx = c.getContext('2d');
       
        //繪圖物件初始設定
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.strokeStyle = '#000000';
    ctx.lineWidth = 1;
   
    //座標相關變數
    var offset, x, y, new_x, new_y;
   
    //滑鼠在畫布按下時的事件處理
    $(document).on('mousedown', '#whiteboard', function(e){
            e.preventDefault();
           
            //打開可供畫圖的機制
        drawing = true;
       
            //計算相對的畫布範圍(這很重要)
            offset = $(e.currentTarget).offset();
        x = e.pageX - offset.left; 
        y = e.pageY - offset.top;
        drawLine(x, y, x+1, y+1);
    });
   
    //滑鼠在畫布上按下左右時,移動的情況
    $(document).on('mousemove', '#whiteboard', function(e){
            e.preventDefault();
           
            //是否開啟畫圖機制
            if( drawing )
            {
                    //計算移動後的新座標,再進行畫圖作業
                new_x = e.pageX - offset.left;
                new_y = e.pageY - offset.top;
                drawLine(x, y, new_x, new_y);
                x = new_x;
                y = new_y;
            }
    });
   
    //放開滑鼠鍵
    $(document).on('mouseup', '#whiteboard', function(e){
            e.preventDefault();
           
            //關閉繪圖機制
            drawing = false;
    });
   
    //選擇筆尖大小
        $(document).on('change', '#size', function(e){
                ctx.lineWidth = $(this).val();
        });
   
        //畫圖,並將繪畫座標傳給網頁上的其他使用者
    function drawLine(x, y, new_x, new_y)
    { 
                //繪圖
        ctx.beginPath(); 
        ctx.moveTo(x, y); 
        ctx.lineTo(new_x, new_y); 
        ctx.closePath(); 
        ctx.stroke();
       
        //將繪畫座標透過 node.js 傳給使用者
        var obj = new Object;
        obj.x = x;
        obj.y = y;
        obj.new_x = new_x;
        obj.new_y = new_y;
        obj.size = $('#size').val();
        socket.emit('draw', obj);
    }

});
</script>
</head>
<body>
        <div id="wrapper">
                <div id="menu">
                        <div>
                                <select id="size">
                                        <option value="1">1</option>
                                        <option value="2">2</option>
                                        <option value="3">3</option>
                                        <option value="4">4</option>
                                        <option value="5">5</option>
                                        <option value="10">10</option>
                                        <option value="20">20</option>
                                </select>
                        </div>
                        <hr />
                        <div id="member_msg"></div>
                </div>
                <div id="content">
                        <canvas id="whiteboard"></canvas>
                </div>
                <div class="clear"></div>
        </div>
</body>
</html>

eBoard.js

 

var server = require('http').createServer(handler),
        ip = "192.168.56.100",
        port = 8080,
        fs = require('fs'),
        si = require('socket.io');

 

//透過網
function handler(request, response) {
        fs.readFile('./eboard.html', function(err, data) {
                if (err)
                {
                        response.writeHead(500, {'Content-Type':'text/plain'});
                        return response.end('Error loading msg.html');
                }
                response.writeHead(200);
                response.end(data);
        });
}

 

server.listen(port, ip, function(){
        //node.js server 啟動並監聽時,所呈現的伺服器訊息
        console.log("Server has started.");
});

/*
 * 用法:
 * socket.on 接收另一端訊息
 * socket.emit 將訊息送給到另一端
 * socket.broadcast.emit 將訊息傳送給其他人(不含自己)
 * io.socket.emit 將訊息傳送至包括自己在內的所有人
 * */

var io = si.listen(server);
io.sockets.on('connection', function(socket) {

        //登入初始化
        socket.on('login', function(data)
        {
                //伺服端訊息
                console.log("connected");
               
                //宣告物件,放置訊息
                var obj = new Object;
                obj.name = data.name;
                obj.msg = data.name + ' 已上線';
               
                //將在前端輸入的名稱記錄下來
                socket.name = data.name;
               
                //將自己上線訊息傳給自己的網頁
                socket.emit('msg', obj);
               
                //告訴其他人你上線了
                socket.broadcast.emit('msg', obj);
        });
       
        //接受畫布作業訊息
        socket.on('draw', function(data){
                //將畫布作業訊息傳給其他線上的人
                socket.broadcast.emit('show', data);
        });

        //離線
        socket.on('disconnect', function() {
                //宣告物件,放置訊息
                var obj = new Object;
                obj.msg = socket.name + ' 已離開';
               
                //通知所有人自己已經離開
                io.sockets.emit('msg', obj);
        });
       
});

 

成果展示


圖(二)測試使用者登入


圖(三)測試使用者登入


圖(四)測試使用者登入


圖(五)測試使用者登入完成


圖(六)任意從其中一個瀏覽器畫圖,其他瀏覽器也看得到


圖(七)測試筆尖大小調整


圖(八)更換筆尖後,其他使用者或瀏覽器也能觀看同樣結果

後記
線上即時通訊的盛行,豐富了網路使用者在交流的過程,同時也讓使用者表達的方式更具多樣性。透過 socket.io 在各種案例的具體應用,強化了溝通所運用的技巧,未來若加入線上視訊、語音等功能,便能與世界各地的人群,進行更多的交流。

參考資料
[1] Socket.io 官網, http://socket.io/
[2] 透過 socket.io來建立人物移動聊天室
http://www.cc.ntu.edu.tw/chinese/epaper/0029/20140620_2910.html
[3] 聊天廣播 - Socket.io 功能介紹與解說
http://iosdevelopersnote.blogspot.tw/2012/09/socketio.html