websocket 聊天室 demo ( tornado + nginx + wss + 在線demo)

在線demo(已停止服務)

 

前端代碼(來源網友,自己稍作修改): 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> WebSocket ChatRoom </title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .box{
            width: 800px;
            margin-left: auto;
            margin-right: auto;
            margin-top: 25px;
        }
        #text{
            width: 685px;
            height: 130px;
            border: 1px solid skyblue;
            border-radius: 10px;
            font-size: 20px;
            text-indent: 1em;
            resize:none;
            outline: none;
        }
        #text::placeholder{
            color: skyblue;
        }
        .btn{
            width: 100px;
            margin: -27px 0 0px 8px;
        }
        #messages{
            padding-left: 10px;
            font-size: 25px;
        }
        #messages li{
            list-style: none;
            color: #000;
            line-height: 30px;
            font-size: 18px;

        }
    </style>
</head>
<body>
    <div class="box">
        <div>
            <textarea id="text" placeholder="請輸入您的內容"></textarea>
            <a href="javascript:WebSocketSend();" class="btn btn-primary">發送</a>
        </div>
        <ul id="messages">
        </ul>
    </div>
    
    <script type="text/javascript">
        var mes = document.getElementById('messages');

        if('WebSocket' in window){  /*判斷瀏覽器是否支持WebSocket接口*/
            /*創建創建 WebSocket 對象,協議本身使用新的ws://URL格式*/
            var Socket = new WebSocket("wss://10.0.0.189/wss/chat")
            /*連接建立時觸發*/
            Socket.onopen = function () {
                mes.innerHTML += "<br>連接已建立!╰( ̄▽ ̄)╮";
                //alert("連接已建立,可以進行通信")
            };
            /*客戶端接收服務端數據時觸發*/
            Socket.onmessage = function (ev) {
                var received_msg = ev.data; /*接受消息*/
                var aLi = "<li>" + received_msg + "</li>";
                mes.innerHTML += aLi;
                /*jq方式*/
                // $(mes).append($(aLi));
            };
            /*連接關閉時觸發*/
            Socket.onclose = function () {
                mes.innerHTML += "<br>連接已經關閉...(┬_┬)";
            };
        }else{
            /*瀏覽器不支持 WebSocket*/
            alert("您的瀏覽器不支持 WebSocket!");
        }
        function WebSocketSend() {
            /*form 裏的Dom元素(input select checkbox textarea radio)都是value*/
            var send_msg = document.getElementById('text').value;
            //或者JQ中獲取
            // var send_msg = $("#text").val();
            /*使用連接發送消息*/
            Socket.send(send_msg);
        }
    </script>
</body>
</html>

後端代碼(來源網友,自己稍作修改):

# coding:utf-8

import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
import os
import datetime

from tornado.web import RequestHandler
from tornado.options import define, options
from tornado.websocket import WebSocketHandler

define("port", default=8000, type=int)

class IndexHandler(RequestHandler):
    def get(self):
        self.render("index.html")

class ChatHandler(WebSocketHandler):

    users = set()  # 用來存放在線用戶的容器

    def open(self):
        self.users.add(self)  # 建立連接後添加用戶到容器中
        for u in self.users:  # 向已在線用戶發送消息
            print("user count {}".format(len(self.users)))
            u.write_message(u"[%s]-[%s]-進入聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
            print("{} enter room".format(self.request.remote_ip))

    def on_message(self, message):
        for u in self.users:  # 向在線用戶廣播消息
            u.write_message(u"[%s]-[%s]-說:%s" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), message))
            print("{} says {}".format(self.request.remote_ip, message))

    def on_close(self):
        self.users.remove(self) # 用戶關閉連接後從容器中移除用戶
        for u in self.users:
            u.write_message(u"[%s]-[%s]-離開聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
            print("{} leave room".format(self.request.remote_ip))

    def check_origin(self, origin):
        # 接受所有跨源流量
        return True
        
        # 若要允許來自站點任何子域的連接,您可以執行如下操作:
        # parsed_origin = urllib.parse.urlparse(origin)
        # return parsed_origin.netloc.endswith(".mydomain.com")

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app = tornado.web.Application([
            (r"/", IndexHandler),
            (r"/wss/chat", ChatHandler),
        ],
        static_path = os.path.join(os.path.dirname(__file__), "static"),
        template_path = os.path.join(os.path.dirname(__file__), "template"),
        debug = True
        )
    http_server = tornado.httpserver.HTTPServer(app, xheaders=True)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

配置NGINX,運行代碼:

1. 準備工作: 找一臺虛擬機或服務器, 安裝nginx, python3.6, tornado,把後端代碼拷貝上去。

2. nginx配置:

/etc/nginx/conf.d 下新增個文件 wss.conf

server_tokens off;

upstream wss_svr {
    server 127.0.0.1:8000;
}


server{
    listen 80;
    server_name 10.0.0.189;
    add_header Strict-Transport-Security max-age=15768000;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443;
    server_name 10.0.0.189;

    ssl on;
    index index.html index.htm;
    ssl_certificate   cert/10.0.0.189.crt;
    ssl_certificate_key  cert/10.0.0.189.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    location /ws {
        alias /var/www/wangzhu/web_chat/;
        index index.html;
    }
    
    location /wss {
        proxy_redirect off;
        proxy_pass http://wss_svr;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        proxy_read_timeout 180s;
    }
}

3. 本地生成證書,放在 /etc/nginx/cert 目錄下, 證書腳本如下,生成時輸入服務器ip地址

read -p "Enter your domain [www.example.com]: " DOMAIN

echo "Create server key..."
openssl genrsa -des3 -out $DOMAIN.key 1024

echo "Create server certificate signing request..."
SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$DOMAIN"
openssl req -new -subj $SUBJECT -key $DOMAIN.key -out $DOMAIN.csr

echo "Remove password..."
mv $DOMAIN.key $DOMAIN.origin.key
openssl rsa -in $DOMAIN.origin.key -out $DOMAIN.key

echo "Sign SSL certificate..."
openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt

echo "TODO:"
echo "Copy $DOMAIN.crt to /etc/nginx/ssl/$DOMAIN.crt"
echo "Copy $DOMAIN.key to /etc/nginx/ssl/$DOMAIN.key"

echo "Add configuration in nginx:"
echo "server {"
echo "    ..."
echo "    listen 443 ssl;"
echo "    ssl_certificate     /etc/nginx/ssl/$DOMAIN.crt;"
echo "    ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;"
echo "}"

4. 啓動nginx

service nginx start

5. 運行代碼

python wss.py

6. 打開前端代碼寫的html,即可看到界面

效果

 打開時頁面:

輸入文字時:

服務器打印:

 問題:

服務器打印的遠端IP成了127.0.0.1,不是真實的IP地址。

這個問題涉及2個地方,一個是nginx配置,一個是tornado代碼

nginx配置是沒得問題的:

那就是tornado了,因爲沒加  xheaders=True

http_server = tornado.httpserver.HTTPServer(app, xheaders=True)

OK,完美解決

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章