swoole 服務端120行代碼構建一個websocket 聊天室.

swoole :http://www.swoole.com/
PHP的異步、並行、高性能網絡通訊引擎,使用純C語言編寫,提供了PHP語言的異步多線程服務器,異步TCP/UDP網絡客戶端,異步MySQL,異步Redis,數據庫鏈接池,AsyncTask,消息隊列,毫秒定時器,異步文件讀寫,異步DNS查詢。 Swoole內置了Http/WebSocket服務器端/客戶端、Http2.0服務器端。php

7-22更新------ 昨天已經用面向對象和redis重作了一次,如今支持切換分組,私信了.而且對其餘地方也作了一些優化. 視頻演示: http://www.bilibili.com/video... 視頻中的列表沒有正常刷新漏洞已修復 github : https://github.com/buffge/buf...html

demo 地址 http://www.buffge.xin/buffcha...

end------

7-27更新

今天服務器被人攻擊了 他攻擊也許就是爲了測試或者玩我,並且他也沒有得逞
他利用個人redis的漏洞來攻擊我,由於我本地用一個可視化軟件去查看我服務器的數據庫,因此我
開放的權限而且沒有加密,以後有人想透過redis 給我服務器上傳腳本. 不過這個腳本就是重複運行而已,應該是個
練手的.他這個腳本沒有運行起來,由於我redis 的權限很低. 他首先給我redis裏面加入了這樣一句話前端

clipboard.png

而後想讓這個腳本在我服務器中循環運行.真的可惡.我好心開源給你們看了玩玩,我也沒空去搞那麼安全那麼好 可是就是有這種人渣來黑我.
等過幾天我忙完了手上的事從新開啓這個聊天室並添加新功能 和新樣式 不說了,我先舉報去了,我已經找到他的ip和他的羣了.就看tx管無論了.bye~
-------endmysql


昨晚一直在學習swoole. 終於搞出了一個多人聊天室.新手能夠看看,高玩請直接右上角.

思路:git

1:用戶登陸主頁面,此時後臺將用戶放入數據庫(我不會redis memcached 等),
 數據庫格式爲

clipboard.png

第一次登陸只須要將fd插入就好了.

2:此時若是不進行註冊,那麼就沒法發送信息給全局.
若是註冊了 那麼填寫用戶名,ajax請求 將用戶名進行加鹽信息摘要得到token.
註冊成功後發送token:117fdba5e4d4050ed18ffd85ac86ed5b5d72dec6a271f4e4f6a6e03f4b957cd4:user_name:張三123
這樣的一個字符串給服務器,服務器會進行token 和名稱驗證,若是是正確的token.那麼將用戶信息插入到數據庫.
並通知其餘websocket 有新用戶上線.

3:快樂的聊天吧.

路上遇到的坑:github

1:我本來準備在server.php(就是swoole 後臺服務)建立一個對象,
        存儲全部訪客的信息 姓名等等,可是他不共享這個對象,我以爲能夠用redis 緩存起來,
        我這裏就寫了玩的,就用mysql弄了,也沒有面向對象.
        2:不能用session  反正用了 以後就是各類問題,我本來是準備用session 存儲用戶名稱的.
        後臺沒辦法想出一個加密token 而後存入數據庫,
        具體的token是這樣的.

clipboard.png
註冊時候用鹽和sha256生成token,而後去swoole 服務器那邊驗證的時候也用一樣的用戶名和鹽進行計算,看是否同樣.若是同樣 說明是正確的登陸了. 我不知道這個方法好很差.之前只會session,這個是昨晚忽然想到的.web

服務器那邊的日誌以下:ajax

clipboard.png

4個用戶的聊天界面以下:redis

clipboard.png

clipboard.png

clipboard.png

clipboard.png

服務器端 代碼 看註釋.寫的渣別說,我是菜鳥.sql

<?php

/*
  <#日期 = "2017-7-19">
  <#時間 = "00:46:16">
  <#人物 = "buff" >
  <#備註 = " ">
 */
if (php_sapi_name() !== 'cli') {
    exit("使用cli模式");
}
$serv = new Swoole\Websocket\Server("192.168.1.109", 9501);
//回調函數 新建一個websocket鏈接時 觸發的事件
$serv->on('Open', function($server, $req) {
    $mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
    $charsetsql = "set names utf8";
    $mysqli->query($charsetsql);
    //將新登錄的用戶 保存到mysql
    $sql = "INSERT INTO `users` (`fd`) VALUES ('{$req->fd}')";
    $mysqli->query($sql);
    if ($mysqli->affected_rows !== 1) {
        echo "插入數據失敗!sql== {$sql}";
    }
    echo "新客戶端鏈接: " . $req->fd . "時間:" . date("Y-n-j H:i:s") . "\n";
    $userlist = "";
    //檢查當前共有多少用戶在線
    $sql = "select `user_name` from users where `user_name`!=''";
    $res = $mysqli->query($sql);
    for ($i = 0; $i < $res->num_rows; $i++) {
        $result = $res->fetch_assoc();
        $userlist .= ('"' . $result['user_name'] . '",');
    }
    $userlist = substr($userlist, 0, strlen($userlist) - 1);
    //通知用戶 當前在線用戶列表
    $server->push($req->fd, "{\"code\":\"4\",\"users\":[{$userlist}]}");
    $res->free();
    $mysqli->close();
});
//當收到用戶的消息時 觸發事件
$serv->on('Message', function($server, $frame) {
    $mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
    $charsetsql = "set names utf8";
    $mysqli->query($charsetsql);
    $sql = "select * from users where fd={$frame->fd}";
    $res = $mysqli->query($sql);
    $result = $res->fetch_assoc();
    //獲取當前發消息的人的名稱
    $user_name = $result['user_name'];
    echo "收到來自客戶端{$frame->fd}的消息: " . $frame->data . "\n";
    //當用戶是第一個註冊時候(發送的語句前面5個字是token)
    if (strpos($frame->data, 'token') === 0) {
        //若是數據庫中沒有令牌
        if ($result['token'] === null) {
            $userData = explode(':', $frame->data);
            $hash = hash('sha256', 'daimin' . $userData[3]);
            if ($userData[1] == $hash) {
                $sql = "UPDATE `users` SET `token` = '{$hash}',`user_name` = '{$userData[3]}' WHERE `fd` = {$frame->fd}";
                $mysqli->query($sql);
                if ($mysqli->affected_rows !== 1) {
                    echo "更新用戶信息失敗, sql === {$sql}";
                    $server->push($frame->fd, "{\"code\":\"5\",\"mes\":\"更新用戶信息失敗\"}");
                }
                $server->push($frame->fd, "{\"code\":\"3\",\"user_name\":\"{$userData[3]}\"}");
                $res->free();
                $userlist = "";
                //檢查當前共有多少用戶在線
                $sql = "select `user_name` from users where `user_name`!=''";
                $res = $mysqli->query($sql);
                for ($i = 0; $i < $res->num_rows; $i++) {
                    $result = $res->fetch_assoc();
                    $userlist .= ('"' . $result['user_name'] . '",');
                }
                $userlist = substr($userlist, 0, strlen($userlist) - 1);
                //通知全部用戶 當前在線用戶列表
                foreach ($server->connections as $fd) {
                    $server->push($fd, "{\"code\":\"4\",\"users\":[{$userlist}]}");
                }
                echo "新註冊用戶 {$userData[3]}\n";
            }
            else {
                $server->push($frame->fd, "{\"code\":\"5\",\"mes\":\"token錯誤\"}");
            }
        }
        //若是隻是發送包含token這個字符串的語句 羣發
        else {
            foreach ($server->connections as $fd) {
                $server->push($fd, "{\"code\":\"2\",\"mes\":\"{$frame->data}\",\"user_name\":\"{$user_name}\"}");
            }
        }
    }
    //若是不是註冊用戶
    else {
        //若是沒有令牌
        if ($result['token'] === null) {
            $server->push($frame->fd, "{\"mes\":\"請先登陸!\"}");
            return;
        }
        //將換行轉換爲br
        $mes = nl2br($frame->data);
        //格式化json
        $mes = str_replace("\n", "", $mes);
        //羣發消息
        foreach ($server->connections as $fd) {
            $server->push($fd, "{\"code\":\"2\",\"mes\":\"{$mes}\",\"user_name\":\"{$user_name}\"}");
        }
    }
    $res->free();
    $mysqli->close();
});
//當websocket 斷開鏈接時 觸發事件
$serv->on('Close', function($server, $fd) {
    $mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
    $charsetsql = "set names utf8";
    $mysqli->query($charsetsql);
    $sql = "DELETE FROM `users` WHERE `users`.`fd` = {$fd}";
    //當用戶退出時 刪除信息
    $mysqli->query($sql);
    if ($mysqli->affected_rows !== 1) {
        echo "刪除用戶信息失敗, sql === {$sql}";
    }
    echo "客戶端{$fd}已斷開鏈接\n";
    $mysqli->close();
});

$serv->start();

前端js 核心

websocket.onmessage = function (evt) {
    console.log(evt.data);
    var data = JSON.parse(evt.data);
    //根據code 判斷業務
    switch (data.code) {
        //我的消息
        case '1':
            break;
            //全局消息
        case '2':
            var $time = bf_get_time();//這是我本身寫的一個獲取時間函數
            var $mes = data.mes;//消息內容
            var $user_name = data.user_name;//發消息的人
            var $who = $user_name === selfName ? "self" : "other";//根據名稱設置格式
            var $append = "<div class=\"mes_item\"><p><span class=\"user_name\">" + $user_name + " </span><time>" + $time + "</time></p><p class=\"message " + $who + "\">" + $mes + "</p></div>"
            $(".gui_content").append($append);
            var $cont_scrTop = $(".gui_content").scrollTop();
            var $list_height = $(".mes_item:last-of-type").height();
            $(".gui_content").animate({'scrollTop': $cont_scrTop + $list_height}, 100);
            break;
            //通知註冊用戶成功消息
        case '3':
            var $append = '<li>';
            $append += data.user_name;
            $append += '</li>';
            $(".user_list ul").append($append);
            $(".gui_user").html("<p>歡迎 " + data.user_name + "</p>");
            selfName = data.user_name;
            break;
            //更新當前在線列表 刪除的我沒寫~
        case '4':
            var $append = '';
            var $nowUser = $(".user_list ul li");
            outer:
                    for (var user in data.users) {
                for (var i = 0; i < $nowUser.length; i++) {
                    if ($nowUser[i].innerText === data.users[user]) {
                        continue outer;
                    }
                }
                console.log(data.users[user]);
                $append += '<li>';
                $append += data.users[user];
                $append += '</li>';
            }
            $(".user_list ul").append($append);
            break;
        case '5':
            alert(data.mes);
            break;
    }
    console.log('收到來自服務器的消息: ' + data.code);

};

等有空了用面向對象寫一次把mysql 換成redis,如今的代碼有點亂,一些功能也沒有寫,由於太困了,連續寫了8小時.

若是有大神看的話 能不能告訴我 哪裏該改進. 好比怎麼驗證用戶,用哪一種方法存儲之類的.謝謝了

相關文章
相關標籤/搜索