ZHIHU:WebSocket是什麼,基於什麼原理?爲何能夠實現持久鏈接?node
使用 ws 模塊來在服務器上支持 WebSocket 協議,下面使用 NPM 來安裝:nginx
cd /var/www/wxpro npm install ws --save
建立 websocket.js,實現 WebSocket 服務web
// 引入 ws 支持 WebSocket 的實現 const ws = require('ws'); // 導出處理方法 exports.listen = listen; /** * 在 HTTP Server 上處理 WebSocket 請求 * @param {http.Server} server * @param {wafer.SessionMiddleware} sessionMiddleware */ function listen(server, sessionMiddleware) { // 使用 HTTP Server 建立 WebSocket 服務,使用 path 參數指定須要升級爲 WebSocket 的路徑 const wss = new ws.Server({ server, path: '/ws' }); // 監聽 WebSocket 鏈接創建 wss.on('connection', (ws,request) => {// 要升級到 WebSocket 協議的 HTTP 鏈接 // 被升級到 WebSocket 的請求不會被 express 處理, // 須要使用會話中間節獲取會話 sessionMiddleware(request, null, () => { const session = request.session; if (!session) { // 沒有獲取到會話,強制斷開 WebSocket 鏈接 ws.send(JSON.stringify(request.sessionError) || "No session avaliable"); ws.close(); return; } // 保留這個日誌的輸出可以讓實驗室能檢查到當前步驟是否完成 console.log(`WebSocket client connected with openId=${session.userInfo.openId}`); serveMessage(ws, session.userInfo); }); }); // 監聽 WebSocket 服務的錯誤 wss.on('error', (err) => { console.log(err); }); } /** * 進行簡單的 WebSocket 服務,對於客戶端發來的全部消息都回復回去 */ function serveMessage(ws, userInfo) { // 監聽客戶端發來的消息 ws.on('message', (message) => { console.log(`WebSocket received: ${message}`); ws.send(`Server: Received(${message})`); }); // 監聽關閉事件 ws.on('close', (code, message) => { console.log(`WebSocket client closed (code: ${code}, message: ${message || 'none'})`); }); // 鏈接後立刻發送 hello 消息給會話對應的用戶 ws.send(`Server: 恭喜,${userInfo.nickName}`); }
編輯 app.js,調用 WebSocket 服務mongodb
// HTTP 模塊同時支持 Express 和 WebSocket const http = require('http'); // 引用 express 來支持 HTTP Server 的實現 const express = require('express'); // 引用 wafer-session 支持小程序會話 const waferSession = require('wafer-node-session'); // 使用 MongoDB 做爲會話的存儲 const MongoStore = require('connect-mongo')(waferSession); // 引入配置文件 const config = require('./config'); // 引入 WebSocket 服務實現 const websocket = require('./websocket'); // 建立一個 express 實例 const app = express(); // 獨立出會話中間件給 express 和 ws 使用 const sessionMiddleware = waferSession({ appId: config.appId, appSecret: config.appSecret, loginPath: '/login', store: new MongoStore({ url: `mongodb://${config.mongoUser}:${config.mongoPass}@${config.mongoHost}:${config.mongoPort}/${config.mongoDb}` }) }); app.use(sessionMiddleware); // 在路由 /me 下,輸出會話裏包含的用戶信息 app.use('/me', (request, response, next) => { response.json(request.session ? request.session.userInfo : { noBody: true }); if (request.session) { console.log(`Wafer session success with openId=${request.session.userInfo.openId}`); } }); // 實現一箇中間件,對於未處理的請求,都輸出 "Response from express" app.use((request, response, next) => { response.write('Response from express'); response.end(); }); // 建立 HTTP Server 而不是直接使用 express 監聽 const server = http.createServer(app); // 讓 WebSocket 服務在建立的 HTTP 服務器上監聽 websocket.listen(server, sessionMiddleware); // 啓動 HTTP 服務 server.listen(config.serverPort); // 輸出服務器啓動日誌 console.log(`Server listening at http://127.0.0.1:${config.serverPort}`);
#WebSocket 配置 map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443; server_name www.example.com; # 改成綁定證書的域名 #ssl 配置 ssl on; ssl_certificate 1_www.example.com.crt; # 改成本身申請獲得的 crt 文件的名稱 ssl_certificate_key 2_www.example.com.key; # 改成本身申請獲得的 key 文件的名稱 ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; #WebSocket 配置 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; location / { proxy_pass http://127.0.0.1:8765; } }
nginx -t nginx -s reload