在以前的文章中,我們介紹了HTTP通訊,這種通訊有一個缺點,如果我想從直接從服務器發消息給客戶端,需要客戶端先發起HTTP請求后服務器才能返回數據,且后續服務器想發送數據給客戶端都需要客戶端先發起請求,但這種方案在一些特殊場景應用的時候非常消耗資源,比如聊天室,如果使用HTTP請求,需要客戶端每隔一段時間就請求一次服務器,再由服務器返回數據。這種傳統的模式帶來很明顯的缺點,即客戶端需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
在這種情況下,html5定義了WebSocket協議,能更好的節省服務器資源和帶寬,并且能夠更實時地進行通訊。WebSocket只需要與服務器進行一次握手,即可實現實時的數據連接,并且傳輸協議是全雙工的,服務器可以隨時主動向客戶端發送數據,并且WebSocket協議在連接創建后,服務器和客戶端之間交換數據時,用于協議控制的數據包頭部相對較小,能明顯降低服務器及客戶端開銷。
我們的小程序也支持WebSocket通信,如果你想為你的小程序實現聊天室、服務器推送、小程序之間數據交互等功能,那就非常有必要搭建一個WebSocket服務器來進行WebSocket通訊。這篇文章中,我們將簡單介紹小程序WebSocket通信使用方法,并通過實例搭建一個WebSocket服務器。實現小程序與服務器之間的通訊。
在教程開始之前,需要搭建搭建好小程序的基礎開發環境,關于如何配置,大家可以參考如何入門小程序開發這篇文章的入門教程。
服務器搭建
既然要實現WebSocket通訊,那必須要擁有一臺WebSocket服務器,服務端的環境有很多選擇NodeJS、php、Python等大部分主流語言都可以部署WebSocket服務,今天我們將教大家使用PHP語言進行環境部署,其他語言請同學們自行部署。
運行環境搭建
我這里以Ubuntu Server 16.04 LTS為例,我們需要安裝php運行環境及NginxWeb服務,同時也需要申請免費的SSL證書和域名,關于證書和域名的申請注冊請參考如何快速搭建微信小程序這篇文章。注冊完域名及證書申請,我們就可以開始部署服務器了!首先,登錄服務器,執行下面的命令。
sudo apt update
sudo apt install php php-fpm php-curl nginx composer -y
安裝完成后,使用瀏覽器訪問你的服務器IP地址,如果看到下面的內容,則證明Web服務已經啟動。

img
因為小程序獲取遠程數據,必須為HTTPS或WSS環境,所以目前搭建的環境,在小程序無法使用,接下來,我們將使用SSL證書加密小程序訪問你服務器之間的流量。這里就需要剛才注冊的域名及證書了。首先,將下載的證書,上傳到你的服務器,并記錄下這個位置。然后,我們將配置Nginx服務,以讓其支持WSS流量。
我們找到/etc/nginx/conf.d文件夾,新建配置文件,為了方便后續修改,我將這里的配置文件修改為weixin.techeek.cn.conf大家可以根據自己的需求修改。
cd /etc/nginx/conf.d
sudo nano weixin.techeek.cn.conf
在nano編輯器中,我們寫下下面的代碼
server {
listen 443 ssl;
server_name weixin.techeek.cn;
index index.php index.html index.htm;
root /usr/share/nginx/html;
ssl_certificate /home/ubuntu/1_weixin.techeek.cn_bundle.crt;
ssl_certificate_key /home/ubuntu/2_weixin.techeek.cn.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location ~ .php$ {
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location /
{
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80 default_server;
server_name weixin.techeek.cn;
root /usr/share/nginx/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
一定注意,將文中server_name中的weixin.techeek.cn更換成你的域名。將ssl_certificate和ssl_certificate_key中證書的路徑更換成你剛上傳證書的路徑。然后,執行下面的命令重啟nginx服務。
sudo service nginx restart
之后,打開你電腦的瀏覽器,然后通過域名訪問,注意,這里一定要在域名前加https://,比如我訪問的域名https://weixin.techeek.cn/。

1542188355313
如果域名前有小鎖標志,則證明你已經配置成功,可以開始下一步了,這里502報錯不用在意,因為我們還沒有搭建WebSocket服務,所以服務器會返回502錯誤。
配置通訊域名
基本環境配置好之后,可以登錄 微信公眾平臺 配置通信域名了。我們點擊微信公眾號右側的設置,然后找到服務器域名配置。

1542188610710
進入微信公眾平臺管理后臺設置服務器配置,如上圖所示,需要將你的服務器域名配置為你自己的域名。我這里的域名是weixin.techeek.cn。
WebSocket服務搭建
上述步驟準備完成后 ,就可以撰寫WebSocket服務端的代碼了,我這里使用的是PHP socket即時通訊框架Workerman來進行搭建。有了這個框架,我們就可以非常方便的搭建WebSocket服務。因為本文主要講解小程序端的WebSocket的使用,關于Workerman的詳細使用教程,可以參考Workerman官方手冊,本文僅做基礎環境安裝的介紹。
首先,我們創建一個運行WebSocket服務的目錄,我這里創建名為php-websocket-server,目錄位置可以自定義,我這里就將項目放在ubuntu用戶的根目錄下。
mkdir /home/ubuntu/php-websocket-server
cd /home/ubuntu/php-websocket-server
接下來,我們使用composer包管理器安裝WebSocket運行環境。因為某些原因,國內訪問composer可能會報錯,所以我們需要使用國內的composer鏡像。然后就可以安裝Workerman了。
sudo composer config -g repo.packagist composer https://packagist.phpcomposer.com
sudo composer update
sudo composer require workerman/workerman
安裝完成后,默認情況下會有三個文件,composer.json、composer.lock、vendor這三個文件,如果沒有,請重新執行上面的命令。
├── composer.json
├── composer.lock
└── vendor
安裝完workerman依賴文件,我們就可以撰寫系統所需的代碼了。使用nano編輯器,新建一個可執行的php文件,我這里創建的文件名為webSocket.php,大家可自行更改。
nano webSocket.php
代碼如下
<?php
require_once __DIR__ . '/vendor/autoload.php';
use WorkermanWorker;
$ws_worker = new Worker("websocket://0.0.0.0:8080");
$ws_worker->count = 4;
$ws_worker->onMessage = function($connection, $data)
{
$connection->send('hello ' . $data);
};
Worker::runAll();
這時,一個最基本的websocket服務就編輯完成了,這里的代碼意思是,通過/vendor/autoload.php引入Workerman的php文件,然后在8080端口創建websocket服務,并設置進程為4個進程。然后執行onMessage回調函數,該函數接收客戶端所發過來的數據$data,然后使用send方法將數據發回給客戶端。
接下來,我們就可以運行服務器了,執行下面的代碼即可運行。
sudo php webSocket.php start
如果看到類似下面的輸出,證明我們websocket服務器已經啟動,接下來就可以開始配置小程序端的代碼了。

1542247109151
小程序端
連接服務器
小程序連接Websocket服務器是通過wx.connectSocket()API進行連接的,為了方便連接API,我們先看看官方的文檔。

我們看到只有url是必填項,其他屬性可以不填,那么連接服務器就比較簡單了,我們打開index.js文件,寫下下面的代碼。
Page({
onReady: function () {
wx.connectSocket({
url: 'wss://weixin.techeek.cn'
})
},
})
有小程序開發經驗的小伙伴都知道,這里的onReady是小程序的生命周期函數,負責在小程序初次渲染完成后執行的函數,這樣我們編譯完小程序,小程序就自動連接服務器。現在編譯一下試試,咦,好像不行啊,怎么沒看到小程序有反應。我們打開控制臺,點擊Network按鈕,如果看到類似下面的內容,就證明你的小程序已經成功鏈接服務器了。

1542249696427
這里的HTTP狀態碼是101,101狀態碼是websocket特有的狀態碼,我們已經成功連接搭建的服務器。但是我們能不能直觀點看到已經連接服務器呢?當然可以,參考文檔使用success屬性,我們在其中加入回調函數。修改代碼如下。
Page({
onReady: function () {
wx.connectSocket({
url: 'wss://weixin.techeek.cn',
success: function (res) {
console.log("連接服務器成功")
},
fail: function (res) {
console.log("連接服務器失敗")
}
})
},
})
我們增加一個回調函數,如果服務器連接成功,向小程序控制臺打印出連接服務器成功。反正打印連接服務器失敗。

1542250247835
當然,我們也可以將成功的內容展示給小程序前端,代碼如下,首先修改index.wxml代碼。
<view><text>連接服務器狀態:{{status}}</text></view>
然后打開index.js文件,修改代碼
Page({
onReady: function () {
var myThis = this;
wx.connectSocket({
url: 'wss://weixin.techeek.cn',
success: function (res) {
myThis.setData({
status: "連接服務器成功"
})
},
fail: function (res) {
myThis.setData({
status: "連接服務器失敗"
})
}
})
},
})
現在重新編譯小程序,你會看到類似這樣的界面。

1542250497626
向服務器發送數據
服務器搭建我們說到,我們的服務器的代碼內容是將小程序發給服務器的任意字符前加hello之后返回給小程序,現在,我們已經成功連接服務器了。接下來,我們需要修改代碼,以便小程序將數據發給服務器。
官方文檔中,使用wx.sendSocketMessage()API將數據發給服務器,根據官方文檔,通過 WebSocket 連接發送數據。需要先wx.connectSocket連接服務器,并在 wx.onSocketOpen 回調之后才能發送。所以在調用wx.sendSocketMessage()前,需要先調用wx.onSocketOpen監聽WebSocket連接是否打開。代碼如下。
Page({
onReady: function () {
var myThis = this;
wx.connectSocket({
url: 'wss://weixin.techeek.cn'
})
wx.onSocketOpen(function (res) {
myThis.setData({
status: "websocket連接服務器成功"
})
})
},
})
現在,我們就可以使用wx.sendSocketMessage()發送數據到服務器了,先看看官方文檔,怎么使用。

我們只需要傳data內容給API,就能發內容給服務器了,那么修代碼內容如下。
Page({
onReady: function () {
var myThis = this;
wx.connectSocket({
url: 'wss://weixin.techeek.cn'
})
wx.onSocketOpen(function (res) {
wx.sendSocketMessage({
data: "你好",
success: function (res) {
console.log("數據已發給服務器")
}
})
myThis.setData({
status: "websocket連接服務器成功"
})
})
},
})
現在,我們的數據已經可以發給服務器了,可是我們還沒有看到服務器返回的數據,這時,我們就該使用另一個API了,監聽WebSocket 接受到服務器的消息事件wx.onSocketMessage(),該API返回服務器發出的消息。但是onReady函數是頁面加載就運行的,這時服務器還沒反應過來,數據返回了沒收到該怎么處理?我們可以引入另一個生命周期函數onLoad,這個函數是小程序負責監聽頁面加載的函數,我們可以將服務器消息事件監聽的API寫在這里,當接收到數據,由這個函數返回相關內容。所以代碼如下。
Page({
onReady: function () {
var myThis = this;
wx.connectSocket({
url: 'wss://weixin.techeek.cn'
})
wx.onSocketOpen(function (res) {
wx.sendSocketMessage({
data: "你好",
success: function (res) {
console.log("數據已發給服務器")
}
})
myThis.setData({
status: "websocket連接服務器成功"
})
})
},
onLoad: function (options) {
var myThis = this;
wx.onSocketMessage(function (res) {
myThis.setData({
message: res.data
})
})
},
})
為了方便觀察服務器返回的數據,我們修改下前端,增加服務器消息監聽的內容。
<view><text>連接服務器狀態:{{status}}</text></view>
<view><text>服務器消息:{{message}}</text></view>
現在,重新編譯,就能看到服務器返回Hello 你好的內容,我們發出的內容為你好,服務器在內容前加一個Hello然后返回給小程序。我們可以修改你好為任意內容,看看服務器能否正常返回相關內容。稍微優化下前端和后端代碼,如下。
index.wxml
<button type="primary" bindtap="connect">連接webSocket服務器</button>
<button type="warn" bindtap="close">斷開webSocket服務器</button>
<input placeholder="在這里輸入你要發送的彈幕內容" bindblur="input"/>
<button bindtap="send">向webSocket服務器發送消息</button>
<view><text>連接服務器狀態:{{status}}</text></view>
<view><text>服務器消息:{{message}}</text></view>
index.js
Page({
connect() {
var myThis = this;
wx.connectSocket({
url: 'wss://weixin.techeek.cn'
})
wx.onSocketOpen(function (res) {
myThis.setData({
status:"websocket連接服務器成功"
})
})
},
close(){
var myThis = this;
wx.closeSocket()
wx.onSocketClose(function (res) {
myThis.setData({
status: "websocket服務器已經斷開"
})
})
},
send(){
var myThis = this;
wx.sendSocketMessage({
data: this.inputValue,
success: function (res) {
console.log("發送信息")
wx.showToast({
title: '已發送',
icon: 'success',
duration: 1000
})
},
fail: function (res) {
myThis.setData({
status: "請連接服務器"
})
}
})
},
input: function (e) {
this.inputValue = e.detail.value
},
onLoad: function (options) {
var myThis = this;
wx.onSocketMessage(function (res) {
myThis.setData({
message:res.data
})
wx.showToast({
title: '你收到來自服務器的消息',
icon: 'none',
duration: 2000
})
})
},
})

1542253679047
這樣,我們就實現了向服務器發送數據,同時服務器返回數據的全部流程。
服務器主動發送數據到小程序
有人可能會問,這個HTTP通信方式沒有區別啊,還是小程序先請求數據到服務器,然后服務器返回數據啊,我沒看到什么不同。雖然表現是這樣,但是現在小程序和服務器是長連接狀態,服務器可以直接推送內容到小程序,不信?我們試試。打開你的服務器Websocket.php文件,將代碼修改為下面的內容。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use WorkermanWorker;
use WorkermanLibTimer;
$worker = new Worker('websocket://0.0.0.0:8080');
$worker->onWorkerStart = function($worker){
Timer::add(10, function()use($worker){
foreach($worker->connections as $connection) {
$connection->send('你好!');
}
});
};
$worker->onMessage = function($connection, $data)
{
echo $data . "n";
$connection->send('服務器已經收到了你的消息');
};
Worker::runAll();
然后運行服務器。
sudo php webSocket.php start
這行代碼中,我們實現了小程序連接服務器后,服務器每隔10秒主動推送數據你好給小程序,無需小程序主動請求內容,同時,小程序發出的內容,可以在服務端顯示。現在點擊你小程序連接webSocket服務器按鈕,看看效果。

然后我們向服務器發點消息試試。服務器也已經收到了小程序發出的數據。

總結
websocket通信在小程序端還是比較簡單的,趕快去自己試試吧~后續我還會介紹一篇利用websocket通訊進行聊天室搭建的教程