一,既然是TCP服務器,這個TCP服務器和咱平時作的有什麼不同呢.html
首先,平時的時候咱作的TCP服務器都是,一個或者多個客戶端鏈接咱作的TCP服務器,而後TCP服務器處理客戶端的數據.服務器
如今呢!需求變了!網絡
假設我有5個網絡設備,3個手機.我如今想讓網絡設備把數據遠程傳給手機.並且我還須要記錄網絡設備上傳的數據.函數
假設通訊是這樣的(並且後期還會不停的增長設備和手機)ui
二,咋辦???spa
1. 須要記錄全部設備的數據設計
2. 設備和手機之間存在多對一和一對多code
因此,必須須要個公共的服務器進行數據的中轉.htm
假設就把這個服務器作成TCP服務器,有人問,你咋不作成UDP呢?UDP他妹的發送數據很差判斷是否是發送成功.blog
難道我還每次都讓服務器給我回數據不成,我仍是少找些麻煩!
還有就是要實現遠程,有個公網IP就能夠,能夠本身買個服務器,上網絡公司拉一根專網
或者用本身電腦,用花生殼映射
仍是用雲服務器吧!就是運行在別人的服務器上的一臺電腦(就是一臺電腦),IP地址直接是公網.方便.
三,怎麼設計這個TCP服務器???
1.爲了應對這種通訊,首先設備發送的數據決不能是單單的數據,必須加點東西
2.若是把發送的數據帶上標識呢? 假設設備1發送的數據是 (aaaaa數據 ) aaaaa是數據標識,後面是真實數據
3.而後呢!假設手機1就接收數據標識是aaaaa的數據,怎麼讓服務器轉發給它呢???
4.若是手機1在鏈接上TCP服務器的時候 告訴TCP服務器我接收數據標識是 aaaaa的數據
5.經過上面的方式是否是有點眉頭了????
咱呢姑且把 "告訴TCP服務器我接收數據標識是 aaaaa的數據" 這個事情呢,起個名字 訂閱的主題是 aaaaa
把 "假設設備1發送的數據是 (aaaaa數據 )" 消息前面的 aaaaa 叫作 發佈的主題是aaaaa
四,總結上面的就是
手機1先鏈接TCP服務器,而後呢,規定個協議,告訴TCP服務器我訂閱的主題是aaaaa
這樣呢服務器就記住了,當出現消息前面的主題是aaaaa的消息的時候,他就把這個消息發給手機1
固然咱假設,設備1鏈接上TCP服務器,而後,告訴TCP服務器我訂閱的主題是wwww
這樣呢服務器就記住了,當出現消息前面的主題是wwww的消息的時候,他就把這個消息發給設備1
而後設備1鏈接上TCP服務器之後呢,這樣發送信息(假設發送的消息是123456): aaaaa1123456
服務器一接收到客戶端的消息,就取出來這個消息的標識是什麼,取出來的是 aaaaa
而後呢,輪訓下記錄的誰須要消息標識是aaaaa的消息,而後找到了手機1
最後把這個消息發送給手機1這個客戶端,而後手機1就接收到了1123456這個消息
同理:手機1發送 wwww998877 而後這個消息就會發給設備1 ,設備1就會收到 998877
不知道聽沒聽懂!!!沒有作過TCP服務器的你先作個TCP服務器和一個客戶端試一試通訊.....
五,總結
這個服務器道理上是這樣,服務器記錄各個設備的信息,各個設備訂閱的主題,而後呢,判斷這個消息而後進行轉發
可是...作個簡單的徹底能夠作出來,可是要想作的完善,並且要支持龐大消息數量的設備(來個百萬級).....不是一朝一夕就能夠的.
其實很長時間之前,人們就有這種需求了.多對一和一對多通訊
因此呢,一些組織和單位就開始解決這種問題,開始作這種軟件,因此MQTT就誕生了.
之因此叫MQTT是由於是外國人作的這種TCP服務器,外國人呢,爲實現這種功能的TCP服務器取了個名字叫
Message Queuing Telemetry Transport
而後取每一個首字母 就叫 MQTT了
其實有不少家作MQTT軟件,可是呢,我比較喜歡用emqtt
一,首先咱知道就是個TCP服務器,因此呢,須要先用TCP鏈接上他們的服務器.
二,而後須要發送第一條消息(注:並非上來就能夠訂閱主題的)
MQTT軟件規定呢,你發送的第一條信息是鏈接信息(至關於咱要先登陸)
他規定呢!
ClientID: 各個客戶端必須設定一個ID,各個客戶端必須都不同 假設是 123456
用戶名: 咱安裝MQTT軟件的時候能夠設置MQTT軟件的登陸的用戶名 假設是yang
密碼: 咱安裝MQTT軟件的時候能夠設置MQTT軟件的登陸的密碼 假設是 11223344
下面是我當初研究MQTT的協議,寫的,而後把上面三個參數填進去
注意這節我粘貼的代碼是當時爲了移植到51單片機而本身寫的(51內存太少了),咱用32哈,直接用的官方提供的庫.
/** * @brief 鏈接服務器的打包函數 * @param * @retval * @example **/ int ConnectMqtt(char *ClientID,char *Username,char *Password) { int ClientIDLen = strlen(ClientID); int UsernameLen = strlen(Username); int PasswordLen = strlen(Password); int DataLen = 0; int Index = 2; int i = 0; DataLen = 12 + 2+2+ClientIDLen+UsernameLen+PasswordLen; MqttSendData[0] = 0x10; //MQTT Message Type CONNECT MqttSendData[1] = DataLen; //剩餘長度(不包括固定頭部) MqttSendData[Index++] = 0; // Protocol Name Length MSB MqttSendData[Index++] = 4; // Protocol Name Length LSB MqttSendData[Index++] = 'M'; // ASCII Code for M MqttSendData[Index++] = 'Q'; // ASCII Code for Q MqttSendData[Index++] = 'T'; // ASCII Code for T MqttSendData[Index++] = 'T'; // ASCII Code for T MqttSendData[Index++] = 4; // MQTT Protocol version = 4 MqttSendData[Index++] = 0xc2; // conn flags MqttSendData[Index++] = 0; // Keep-alive Time Length MSB MqttSendData[Index++] = 60; // Keep-alive Time Length LSB 60S心跳包 MqttSendData[Index++] = (0xff00&ClientIDLen)>>8;// Client ID length MSB MqttSendData[Index++] = 0xff&ClientIDLen; // Client ID length LSB for(i = 0; i < ClientIDLen; i++) { MqttSendData[Index + i] = ClientID[i]; } Index = Index + ClientIDLen; if(UsernameLen > 0) { MqttSendData[Index++] = (0xff00&UsernameLen)>>8;//username length MSB MqttSendData[Index++] = 0xff&UsernameLen; //username length LSB for(i = 0; i < UsernameLen ; i++) { MqttSendData[Index + i] = Username[i]; } Index = Index + UsernameLen; } if(PasswordLen > 0) { MqttSendData[Index++] = (0xff00&PasswordLen)>>8;//password length MSB MqttSendData[Index++] = 0xff&PasswordLen; //password length LSB for(i = 0; i < PasswordLen ; i++) { MqttSendData[Index + i] = Password[i]; } Index = Index + PasswordLen; } return Index; }
獲得如下數據,而後把這個數據發給TCP 服務器,若是沒有錯誤,服務器就會回 90 02 00 00
10 22 00 04 4D 51 54 54 04 C2 00 03 00 06 31 32 33 34 35 36 00 04 79 61 6E 67 00 08 31 31 32 32 33 33 34 34
先說一件事情 全部的MQTT數據哈 第一個數據是說明整個數據是幹什麼的數據 第二個是說它後面的數據的總個數
10 : 固定,MQTT規定的鏈接用0x10
22: 是說0x22後面有0x22個數據 34個
00 04: 後面記錄MQTT版本號的字節個數
4D 51 54 54: M Q T T 版本號字符 這個是4版本,不一樣版本不同 3版本的是MQIsdp 額,瞭解就能夠
04: 版本號是 0x04
C2:這個呢想了解具體呢,須要看協議 http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
我說一下哈,不要去糾結這個協議,你再怎麼研究也沒大用,官方早就給咱準備好了各類平臺的包.咱要作的是熟練運用.
上面就是說有用戶名和密碼,每次鏈接的時候清除鏈接信息,沒有設置遺囑(後面會說)
00 03: 心跳包是3S一次(這個本身鏈接的時候本身設置),
MQTT規定必須客戶端必須發心跳包,客戶端發送的心跳包數據是 0xC0 0x00,這是MQTT規定的
若是心跳包間隔了你設定心跳包的1.5倍時間,你沒有發給服務器,服務器就認爲你掉線了,而後還有個遺囑問題,,後面會說
你發給服務器 0xC0 0x00 服務器會回你 0xD0 0x00 這個知道就好了
00 06:客戶端的ClientId有6位
後面的 31 32 33 34 35 36 就是ClientId ,這是MQTT服務器規定的,每隔客戶端必須有各自的ClientId
00 04: MQTT的用戶名
79 61 6E 67 我安裝MQTT的時候設置的MQTT的用戶名是yang
00 08: MQTT的密碼
31 31 32 32 33 33 34 34 我安裝MQTT的時候設置的MQTT密碼
好了,鏈接上TCP服務器 而後發送
10 22 00 04 4D 51 54 54 04 C2 00 03 00 06 31 32 33 34 35 36 00 04 79 61 6E 67 00 08 31 31 32 32 33 33 34 34
服務器呢就會回你 90 02 00 00
90: 固定
02: 後面有兩個數據
後面的兩個數據呢,有幾個返回值,0就說明成功,其它就是有各類問題
好比說回的是 90 02 00 04 就說明用戶名或者密碼有問題.
假設告訴服務器我訂閱的是2222
假設訂閱的時候訂閱的主題的消息標識是1,消息等級是0
那麼打包之後就是 82 09 00 01 00 04 32 32 32 32 00 而後把這個數據發給TCP服務器
/** * @brief MQTT訂閱/取消訂閱數據打包函數 * @param SendData * @param topic 主題 * @param qos 消息等級 * @param whether 訂閱/取消訂閱請求包 * @retval * @example **/ int MqttSubscribeTopic(char *topic,u8 qos,u8 whether) { int topiclen = strlen(topic); int i=0,index = 0; if(whether) MqttSendData[index++] = 0x82; //0x82 //消息類型和標誌 SUBSCRIBE 訂閱 else MqttSendData[index++] = 0xA2; //0xA2 取消訂閱 MqttSendData[index++] = topiclen + 5; //剩餘長度(不包括固定頭部) MqttSendData[index++] = 0; //消息標識符,高位 MqttSendData[index++] = 0x01; //消息標識符,低位 MqttSendData[index++] = (0xff00&topiclen)>>8; //主題長度(高位在前,低位在後) MqttSendData[index++] = 0xff&topiclen; //主題長度 for (i = 0;i < topiclen; i++) { MqttSendData[index + i] = topic[i]; } index = index + topiclen; if(whether) { MqttSendData[index] = qos;//QoS級別 index++; } return index; }
0x82: 告訴MQTT服務器,我要訂閱主題
0x09: 後面的數據個數
0x00 0x01 注意哈,訂閱主題的時候能夠設置了標識 標識呢 1-65535
之因此有這個傢伙:咱訂閱的時候怎麼判斷訂閱成功了呢???
訂閱成功之後呢!服務器會返回咱訂閱成功的回覆,回覆裏面就包含着咱寫的這個標識
咱呢能夠對比下這個標識,而後呢就知道究竟是不是訂閱成功了.
0x00 0x04 後面訂閱主題的長度
32 32 32 32 訂閱的主題是 2222
最後一個 00 是說消息等級,通常呢,訂閱設置爲0 就能夠
那就說一下這個消息等級有什麼用吧!
咱發送數據的時候也會攜帶一個消息等級
假設是0 那麼這條消息是否是真的發給MQTT服務器(Broker)了,就不知道了,
若是設備多個,還真不敢保證真的發給服務器了
假設是1 那麼一個客戶端發送消息之後呢,服務器一看消息等級是1,那麼就會回給那個發送消息的客戶端一個應答消息
客戶端發送完消息之後其實內部會啓動一個超時操做,若是多少時間內沒有回覆,那麼他會再發一次
假設是2 這個呢就是消息必定要到達MQTT服務器.這個很苛刻,也比較佔用內存
若是按照上面發呢,服務器會回
90 03 00 01 00
90:固定
03:後面的數據長度
00 01:這條主題的標識
00:消息等級
長話短說
發佈的時候呢,信息裏面都有這些內容
發佈的主題,消息,回傳標誌,消息等級,是否是須要服務器保留消息,消息的標識
/** * @brief MQTT發佈數據打包函數 * @param mqtt_message * @param topic 主題 * @param qos 消息等級 * @retval * @example **/ int MqttPublishData(char * topic, char * message, u8 qos) { int topic_length = strlen(topic); int message_length = strlen(message); int i,index=0; static u16 id=0; MqttSendData[index++] = 0x30; // MQTT Message Type PUBLISH if(qos) MqttSendData[index++] = 2 + topic_length + 2 + message_length;//數據長度 else MqttSendData[index++] = 2 + topic_length + message_length; // Remaining length MqttSendData[index++] = (0xff00&topic_length)>>8;//主題長度 MqttSendData[index++] = 0xff&topic_length; for(i = 0; i < topic_length; i++) { MqttSendData[index + i] = topic[i];//拷貝主題 } index += topic_length; if(qos) { MqttSendData[index++] = (0xff00&id)>>8; MqttSendData[index++] = 0xff&id; id++; } for(i = 0; i < message_length; i++) { MqttSendData[index + i] = message[i];//拷貝數據 } index += message_length; return index; }
發佈的主題: 誰訂閱了這個主題,消息就傳給誰
回傳標誌: 我沒用過,默認0
消息等級:上面說了
是否是須要服務器保留消息:一會和遺囑一塊說
消息的標識:上面有說起,通常用不到,默認1就能夠
還記得上面
我直接說遺囑是啥意思哈!
假設我手機和一個設備訂閱主題和發佈主題對應,我就能和這個設備通訊了
可是,我怎麼知道這個設備掉線了呢?
固然徹底能夠本身發信息給那個設備,若是不回覆,就說明掉線了
可是呢!MQTT服務器提供了一種方式
假設我設置好設備的遺囑消息是 offline 遺囑發佈的主題是 aaaaa
若是設備掉線,服務器就會給訂閱了aaaaa的客戶端發送 offline
還記得上面說的不 服務器若是在你設置的心跳包時間的1.5倍收不到心跳包就認爲你掉線了.
瞭解就能夠,關鍵是學會使用,就是個安裝軟件的事情,封包解包也不用你寫
官方早就把各類平臺的包給你準備好了,我之因此研究,是由於當時要用51單片機作,官方包太大.....沒辦法只能本身寫....