寫在準備動手的時候:javascript
Socket通信在iOS中也是很常見,本身最近也一直在學習Telegram這個開源項目,Telegram就是在Socket的基礎上作的即時通信,這個相信瞭解這個開源項目的也都知道,但願本身能慢慢的瞭解一下它的這個MtProtoKit開源協議,即時通信這一塊的東西我之前寫過一篇《iOS 即時通信 + 仿微信聊天框架 + 源碼》,從點擊量看的出來真的這一塊的東西咱們的需求量仍是很大,《iOS 即時通信 + 仿微信聊天框架 + 源碼》這篇文章因爲本身去年也是能力有限,如今我本身去看也會以爲不少地方不怎麼盡如人意,接下來Socket能夠說這一個系列的本身準備認認真真的寫下去,不能急於求成,但願能認真紮實的把這系列的東西總結一下,至少讓本身以爲總結的全面一點。html
Socket是什麼?java
咱們從聊天的一些經常使用的協議開始慢慢的理解這個Socket,不想在這了直接拋出Socket的概念,在腦海中有這個問題就行!後面咱們會慢慢的一步一步找到答案!在這裏先給出兩個連接: node
《iOS即時通信,從入門到「放棄」?》這篇文章細緻的說了iOS即時通信這一塊做者本身理解和總結,能很好的幫助咱們理解即時通信,只是篇幅有點長,要看徹底得花點時間。面試
《關於iOS socket都在這裏了》這篇文章我看過以後也以爲很不錯,上面說的文章要是能幫咱們理解即時通信,那這篇文章就能幫咱們消化一下Socket。編程
上面這兩篇文章我相信會對咱們幫助會很大,因此是強烈推薦!結合這兩篇內容,咱們開始慢慢的總結Socket,在文章中全部的練習Demo以及詳細的一些集成過程Demo都會給你們。過了一年,再問一下本身到底該怎樣實現即時通信? 安全
(1)第三方IM服務服務器
第三方能作即時通信的是在是太多了,環信,融雲,網易雲信等等....說說我本身的理解:微信
一、他們的技術的確沒問題,是OK的。要是你公司整個開發團隊技術實力通常(普通公司的整個團隊技術其實實力真的通常,包括我本身待過的一些公司),還有之前你公司也沒有本身作過即時通信而且還有一點就是想速成,那我就建議使用第三方!這一點說說我本身的親身經歷感受會更真實一點,我待的第一家公司,項目有即時通信的需求,咱們是利用Socket來作,作到最後仍是沒有作出一個讓人滿意的即時通信,消息的丟失、連接狀態的不穩定,剛開始的時候也根本還沒怎麼考慮過信息安全這些問題,第二家有了第一次的前車可鑑再結合整個團隊的實力水平,選擇利用環信作,總體感受比第一次好了許多,等到如今接觸到Telegram,才以爲通常的公司仍是推薦使用第三方,那樣至少能保證你這個功能是沒問題的。網絡
二、第三方的弊端,收費!定製化程度不是徹底掌握在你的手裏,收費這一點,像網易這些在開始就明確了價格怎樣供你選擇,像環信這種,會在某一個適當的時機,當你用戶積累到必定量的時候開始收費。這個成本在開發的時候是必須須要考慮的。
三、沒辦法真正的提升你對即時通信的理解以及水平的提高。
(2) 有能力本身作
有能力本身作真的確定是最好,這個時候就須要你去好好認證的學習即時通信所須要的方方面面,這個過程你的收穫確定會很大,但困難也確定會有,須要本身權衡,本身作,咱們該怎樣開始?下面就重點總結一下咱們本身開始作即時通信的時候咱們應該掌握的東西。
即時通信本身動手 -- 選擇
這個時候你須要選擇咱們使用的傳輸協議: TCP 仍是 UDP
固然,你在選擇以前,確定要知道什麼是TCP?什麼是UDP?(這裏就說一個題外話,在剛開始接觸iOS的時候,常常會看到有些人吐槽,我一個作iOS的,面試的時候常遇到有人問什麼是TCP什麼是UDP,我須要瞭解這些幹嗎?有用嗎?有必要嗎?不知道這樣吐槽的你工做幾年以後會不會以爲那時的你很青澀?這些咱們須要掌握的必要性我就不在多提了,我也相信看這篇文章朋友也不會有這樣的想法!)
這個選擇問題,我給到一篇即時通信網上比較好的文章的結論:文章連接《移動端IM/推送系統的協議選型:UDP仍是TCP?》但願須要的朋友認真看看。下面是文章最後給的結論:
認真看看文章,傳輸協議我相信你也可能作一個正確的選擇了,那接下來你還得考慮聊天協議用哪一個,因此這一塊的主題就是 -- 選擇
聊天協議這裏總結,比較一些它們之間的優缺點:
上面的總結能幫助咱們區分這些協議,清楚了這些,咱們本身在作即時通信的時候。最好的選擇仍是利用Socket來實現,下面就總結一些本身學習以後對Socket的理解,要是有什麼不對的地方,你們能夠指出來。
Socket理解
Socket(中文名:套接字)是通訊的基石,是支持TCP/IP協議的網絡通訊的基本操做單元,Socket自己並非協議,Socket本質是編程接口(API),是對TCP/IP的封裝。經過Socket,咱們能更好的使用TCP/IP 協議,而不是說只有經過Socket咱們才能使用TCP/IP協議。要是沒有Socket咱們就得直面傳輸層的TCP/IP協議,這個工做量就會更大,難度也會更大!
創建Socket鏈接至少須要一對套接字,其中一個運行於客戶端稱爲ClientSocket ,另外一個運行於服務器端稱爲ServerSocket 。
下面是百度找的Socket使用TCP協議創建鏈接的一個流程圖:
其實這整個步驟就包括了它的鏈接,客戶端發送消息以及讀取消息,服務端接收消息和給客戶端發送消息以及到最後一個斷開鏈接的請求等等的過程,咱們先看看整個鏈接的過程,也有人總結過大概是下面這樣的幾個步驟:(前面那些初始化過程就不提,這個本身注意就行)
一、服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待鏈接的狀態(也就是上面的阻塞直到客戶端鏈接),實時監控網絡狀態,等待客戶端的鏈接請求。注意上面的bind() Lister() accept() 這些都是服務端須要作的操做,這個方法咱們在下一篇講CocoaAsyncSocket源碼的時候會看到,在這裏留個印象,有助於後面的理解。
二、客戶端請求:客戶端初始化Socket提出鏈接請求,要鏈接的目標是服務器端的Socket。爲此,客戶端的套接字必須首先描述它要鏈接的服務器的套接字,指出服務器端套接字的地址和端口號,而後就向服務器端套接字提出鏈接請求。(這個過程在你客戶端初始化Socket在鏈接時候能在加深理解這一步)
三、鏈接確認:當服務器端Socket監聽到或者說接收到客戶端Socket的鏈接請求時,就響應客戶端Socket的請求,創建一個新的線程,把服務器端Socket的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式創建鏈接。而服務器端套接字繼續處於監聽狀態,繼續接收其餘客戶端套接字的鏈接請求。
經過上面的過程,你的Socket就和服務端Socket創建了鏈接!
再補充一下:咱們在傳輸數據時,可使用(傳輸層)TCP/IP協議,可是那樣的話,若是沒有應用層,便沒法識別數據內容,若是想要使傳輸的數據有意義,則必須使用到應用層協議,應用層協議有不少,好比咱們常說的HTTP(Http是應用層的協議,它實際上也創建在TCP協議之上)。Web使用HTTP協議做應用層協議,以封裝HTTP文本信息,而後使用TCP/IP作傳輸層協議將它發到網絡上。
在Http和Socket之間,百度上面這個例子也是被不少的博客所用:Http是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通訊的能力。
上面的這些你要理解前面說過的TCP,UDP,網絡層級這些東西,理解這個Socket就會容易不少!因此仍是建議理解TCP,UDP這些協議,像TCP鏈接時候的三次握手,斷開時候的四次揮手。它和UDP之間的區別,還有Http的特色這些仍是瞭解一下的好,這些我本身之前也是沒有總結過,等寫完這些再好好總結一些這些網絡層次以及這些協議。
上面說了Socket的鏈接以後,隨之咱們就再理解一下和它永遠都在一塊兒的幾個問題: 心跳機制 PingPong機制 斷線重連
一: 心跳機制
心跳通常是指某端(絕大多數狀況下是客戶端)每隔必定時間向服務端發送自定義指令,以判斷雙方是否存活,因其按照必定間隔發送,相似於心跳同樣,因此你們也就都叫它心跳機制。關於心跳的咱們在這裏介紹完以後在後面的Demo中我會把源碼給你們,具體的能夠結合源碼一塊兒看看。
Socket選擇TCP傳輸協議創建了鏈接,這個TCP協議有一個KeepAlive機制,下面文章也是很明確的指出了爲何不能用TCP協議的KeepAlive機制來作心跳的緣由, 總結的觀點是: TCP KeepAlive 是用於檢測鏈接的死活,而不是用來檢測鏈接是否可用!而咱們的心跳就是爲了檢測鏈接死活的同時還要檢測鏈接是否可用!
再說說你用心跳保持長鏈接,那你的心跳方案怎樣設計呢?在參考文章的最下面文章給出了這樣兩個觀點,先看圖:
參考文章 《爲何說基於TCP的移動端IM仍然須要心跳保活? 》
總結一下上面的兩個減小定時心跳的方法:
一、 考慮定時的時間間隔問題,這個問題其實有時候在面試的時候也會有人問,怎樣去肯定心跳計時的時間間隔或者減小心跳等,上面給的第一個就是在計時的時間間隔上考慮。這個具體的時間設置多少,咱們下面會仔細說說。
二、從最後收到消息開始進行週期計時而不是設計固定的時間間隔,這個我本身的理解,收到消息的時候你作標記計時,要是在某某時間內又有消息收到,就不進行心跳,要是在某某時間內沒有在收到消息就進行心跳。固然在你計時時間內你又收到了消息,這個計時確定也要刷新,這個涉及到具體實踐細節。
服務端的和咱們上面說的道理是同樣的!
衍生的問題: 這個時間間隔到底你該怎樣去設定? 在咱們文章開頭給的推薦文章一種就有比較詳細的解釋這個問題:
具體的緣由說到這個 -- NAT 超時,你要有興趣能夠點擊去百度具體瞭解一下什麼是NAT超時,這裏咱們就不在佔據篇幅寫這個,建議仍是在咱們最前面推薦的文章去了解一下這個NAT超時,說說咱們的結果:而國內的運營商通常NAT超時的時間爲5分鐘,因此一般咱們心跳設置的時間間隔爲3-5分鐘。
二: PingPong機制
這個的出現是爲了在咱們設置的這個心跳間隔以內出現了鏈接問題,就像參考文章說的那樣咱們在地鐵電梯這些場所當中的時候,那它具體是什麼?
當服務端發出一個Ping
,客戶端沒有在約定的時間內返回響應的ack
,則認爲客戶端已經不在線,這時咱們Server
端會主動斷開Scoket
鏈接,而且改由APNS
推送的方式發送消息。
Scoket
鏈接。
咱們本身主動去斷開的Scoket
鏈接(退出登陸,App退出到後臺等等)是不須要重連。其餘的鏈接斷開,咱們都須要進行斷線重連,通常解決方案是嘗試重連幾回,若是仍舊沒法重連成功,那麼再也不進行重連。這個簡單的瞭解一下,知道就行。
上面關於Socket的理論性的東西咱們也就說的差很少了,下面的重點是經過代碼消化一下上面說的理論知識。
看看Socket的主要方法
接下來就看看在iOS中Socket的源碼裏面都有些什麼東西,能夠先看一下,建立一個文件,導入 #import <sys/socket.h> 能夠進去看看Socket裏面的東西。它最主要是下面一組接口,給咱們提供了它的方法,咱們在下面對它進行一個仔細的註釋分析:
按照咱們前面說的整個的一個過程,咱們概括一下客戶端利用Socket發送消息的整個過程:
一、 初始化Socket 使用方法 int socket (int , int ,int )
二、鏈接Socket 使用方法 int connect(int, const struct sockaddr *, socklen_t)
三、發送、接收數據 使用方法 ssize_t send(int, const void *, size_t, int)/ssize_t recv(int, void *, size_t, int)
ssize_t sendto(int, const void *, size_t,int, const struct sockaddr *, socklen_t)和
ssize_t recvfrom(int, void *, size_t, int, struct sockaddr * __restrict, socklen_t * __restrict)
上面發送方法的是有區別的,在下面的註釋中咱們添加說明。
四、關閉Socket 使用方法 close() 這個方法在後面我在說,在Socket源碼是看不到這個方法的。
下面是重要的一些Socket方法的方法的註釋:
/*爲了保證閱讀的順序和源碼的順序對應,方便你們查看,就不調整方法順序,好比初始化的方法位置沒有調整。解釋的也是主要的,沒有所有都解釋 __BEGIN_DECLS 這個方法是在服務端用到,表示接受客戶端請求,並講客戶端的網絡地址保存在sockaddr類型指針__restrict,後面的__restrict是地址的長度 int accept(int, struct sockaddr * __restrict, socklen_t * __restrict) __DARWIN_ALIAS_C(accept); 將Socket與指定的主機地址與端口號綁定,綁定成功返回0.失敗返回-1,這個方法你能夠在CocoaAsyncSocket源碼中看到 int bind(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS(bind); 客戶端Socket的鏈接方法,成功返回0,失敗返回-1,第一個參數是你初始化一個Socket獲取到的文件描述符,初始化Socket返回的文件描述符是int類型,這個你在下面能夠看到。 第二個參數是一個指向要鏈接Socket的sockaddr結構體的指針, 第三個參數表明sockaddr結構體的字節長度 參考:https://baike.baidu.com/item/connect%28%29/10081861?fr=aladdin int connect(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(connect); 獲取Socket的地址 參考:https://baike.baidu.com/item/getpeername%28%29 int getpeername(int, struct sockaddr * __restrict, socklen_t * __restrict) __DARWIN_ALIAS(getpeername); 獲取Socket的名稱 參考:https://baike.baidu.com/item/getsockname%28%29 int getsockname(int, struct sockaddr * __restrict, socklen_t * __restrict) __DARWIN_ALIAS(getsockname); https://baike.baidu.com/item/getsockopt%28%29 int getsockopt(int, int, int, void * __restrict, socklen_t * __restrict); 用於服務端監聽客戶端,傳的兩個參數一個是初始化Socket獲取到的文件描述符, 第二個是等待鏈接隊列的最大長度 如無錯誤發生,listen()返回0。不然的話,返回-1 方法帶參數這樣 int listen( int sockfd, int backlog) ,這個方法在CocoaAsyncSocket源碼中也用作判斷 參考:https://baike.baidu.com/item/listen%28%29 int listen(int, int) __DARWIN_ALIAS(listen); 接收消息方法,詳細https://baike.baidu.com/item/recv%28%29 ssize_t recv(int, void *, size_t, int) __DARWIN_ALIAS_C(recv); 從UDP Socket中讀取數據 ssize_t recvfrom(int, void *, size_t, int, struct sockaddr * __restrict,socklen_t * __restrict) __DARWIN_ALIAS_C(recvfrom); ssize_t recvmsg(int, struct msghdr *, int) __DARWIN_ALIAS_C(recvmsg); 發送消息方法 參考: https://baike.baidu.com/item/send%28%29#3 ssize_t send(int, const void *, size_t, int) __DARWIN_ALIAS_C(send); send,sendto以及sendmsg系統調用用於發送消息到另外一個套接字。send函數在套接字處於鏈接狀態時方可以使用。而sendto和sendmsg在任什麼時候候均可使用 ssize_t sendmsg(int, const struct msghdr *, int) __DARWIN_ALIAS_C(sendmsg); sendto()適用於發送未創建鏈接的UDP數據包 ssize_t sendto(int, const void *, size_t,int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(sendto); int setsockopt(int, int, int, const void *, socklen_t); shutdown()是指禁止在一個Socket上進行數據的接收與發送。 int shutdown(int, int); int sockatmark(int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 重點理解一下Socket的初始化,完整的參數方法是這樣: int socket(int domain, int type, int protocol) domain 參數表示制定使用何種的地址類型 好比: PF_INET, AF_INET: Ipv4網絡協議 PF_INET6, AF_INET6: Ipv6網絡協議 type 參數的做用是設置通訊的協議類型 SOCK_STREAM: 提供面向鏈接的穩定數據傳輸,即TCP協議。 OOB: 在全部數據傳送前必須使用connect()來創建鏈接狀態。 SOCK_DGRAM: 使用不連續不可靠的數據包鏈接。 SOCK_SEQPACKET: 提供連續可靠的數據包鏈接。 SOCK_RAW: 提供原始網絡協議存取。 SOCK_RDM: 提供可靠的數據包鏈接。 SOCK_PACKET: 與網絡驅動程序直接通訊。 參數protocol用來指定socket所使用的傳輸協議編號。這一參數一般不具體設置,通常設置爲0便可。 參考:https://baike.baidu.com/item/socket%28%29 上面的解釋要是結合CocoaAsyncSocket的源碼再去理解,會理解的更透徹 int socket(int, int, int); int socketpair(int, int, int, int *) __DARWIN_ALIAS(socketpair); #if !defined(_POSIX_C_SOURCE) int sendfile(int, int, off_t, off_t *, struct sf_hdtr *, int); #endif !_POSIX_C_SOURCE #if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE) void pfctlinput(int, struct sockaddr *); int connectx(int, const sa_endpoints_t *, sae_associd_t, unsigned int, const struct iovec *, unsigned int, size_t *, sae_connid_t *); int disconnectx(int, sae_associd_t, sae_connid_t); #endif (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) __END_DECLS*/
原生Socket 消息發送接收Demo
這裏就沒有再作GIF格式的動圖,怕這個消息發送接收作的圖太大!效果圖仍是給出來,你能夠在這裏看到CONNECTED鏈接成功,也能夠看到收到的消息Data,最後的你斷開鏈接的時候服務端的CLOSE我就不上圖:這個Demo因爲比較的小,我把主要的代碼給出來,Demo源碼會在這個系列文章和後面的demo一塊兒傳上來:
這個是SocketManager這個單例類的.m源碼,上面全都有註釋,固然你急須要源碼。能夠加我QQ找我,我發給你!這個Demo後面整理上傳!
#import "SocketManager.h" #import <sys/socket.h> #import <sys/types.h> #import <netinet/in.h> #import <arpa/inet.h> @interface SocketManager() @property (nonatomic,assign)int clientScoket; // Socket @property (nonatomic,assign)int connetSocketResult;// Socket鏈接結果 @end @implementation SocketManager /** NOTE: 裏面涉及到一些Socket的建立的具體的方法你要是不理解能夠暫時放下 等讀完CocoaAsyncSocket的源碼的具體註釋就能夠理解 */ +(instancetype)shareInstance{ static SocketManager * manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[SocketManager alloc]init]; manager.connetSocketResult = -1; // 初始化鏈接狀態是斷開狀態 // 在建立單例的時候就去初始化Socket和開闢一條新的線程去接收消息 [manager initSocket]; [manager ReceiveMessageThread]; }); return manager; } -(void)initSocket{ //每次鏈接前,先判斷是否在鏈接狀態 0 在鏈接狀態,直接Return if (_connetSocketResult == 0) { return; } _clientScoket = creatSocket(); //建立客戶端socket const char * server_ip="127.0.0.1"; //服務器Ip short server_port = 6969; //服務器端口 //等於0說明鏈接成功,-1則鏈接失敗 if (connectionToServer(_clientScoket,server_ip, server_port) == 0) { _connetSocketResult = 0; printf("Connect to server Success\n"); return ; }else{ _connetSocketResult = -1; printf("Connect to server error\n"); } } /** 建立Socket @return 返回Socket */ static int creatSocket(){ int ClinetSocket = 0; // NOTE: Socket本質上就是int類型 ClinetSocket = socket(AF_INET, SOCK_STREAM, 0); // 返回建立的Socket return ClinetSocket; } /** 鏈接 Socket @param client_socket Socket @param server_ip 服務器IP @param port 端口 @return 返回時候鏈接成功,返回0則鏈接成功,-1鏈接失敗 */ static int connectionToServer(int client_socket,const char * server_ip,unsigned short port){ //生成一個sockaddr_in類型結構體 struct sockaddr_in sAddr={0}; sAddr.sin_len=sizeof(sAddr); //設置IPv4, 這個區分能夠看前面在解釋Socket方法的時候寫的註釋 sAddr.sin_family=AF_INET; //inet_aton是一個改進的方法來將一個字符串IP地址轉換爲一個32位的網絡序列IP地址 //若是這個函數成功,函數的返回值非零,若是輸入地址不正確則會返回零。 inet_aton(server_ip, &sAddr.sin_addr); //htons是將整型變量從主機字節順序轉變成網絡字節順序,賦值端口號 sAddr.sin_port=htons(port); // 防止發送SO_NOSIGPIPE信號致使崩潰 int nosigpipe = 1; setsockopt(client_socket, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); //用scoket和服務端地址,發起鏈接。 //客戶端向特定網絡地址的服務器發送鏈接請求,鏈接成功返回0,失敗返回 -1。 //注意:該接口調用會阻塞當前線程,直到服務器返回。 int connectResult = connect(client_socket, (struct sockaddr *)&sAddr, sizeof(sAddr)); return connectResult; } // 開闢一條線程接收消息 -(void)ReceiveMessageThread{ NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(recieveAction) object:nil]; [thread start]; } /** 鏈接Socket */ -(void)connectSocket{ [self initSocket]; } /** 斷開Socket鏈接 */ -(void)disConnectSocket{ close(self.clientScoket); } /** 發送消息 @param msg 消息內容 */ - (void)sendMsg:(NSString *)msg{ const char * send_Message = [msg UTF8String]; send(self.clientScoket,send_Message,strlen(send_Message)+1,0); } /** 死循環,看是否有消息發送過來 */ - (void)recieveAction{ while (1) { char recv_Message[1024] = {0}; recv(self.clientScoket, recv_Message, sizeof(recv_Message), 0); printf("%s\n",recv_Message); } } @end
這個是服務端代碼,先把代碼給出來,下面我會仔細和你們說說你怎樣把這個運行起來!
var net = require('net'); var HOST = '127.0.0.1'; var PORT = 6969; // 建立一個TCP服務器實例,調用listen函數開始監聽指定端口 // 傳入net.createServer()的回調函數將做爲」connection「事件的處理函數 // 在每個「connection」事件中,該回調函數接收到的socket對象是惟一的 net.createServer(function(sock) { // 咱們得到一個鏈接 - 該鏈接自動關聯一個socket對象 console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort); sock.write('服務端發出:鏈接成功'); // 爲這個socket實例添加一個"data"事件處理函數 sock.on('data', function(data) { console.log('DATA ' + sock.remoteAddress + ': ' + data); // 回發該數據,客戶端將收到來自服務端的數據 sock.write('You said "' + data + '"'); }); // 爲這個socket實例添加一個"close"事件處理函數 sock.on('close', function(data) { console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort); }); }).listen(PORT, HOST); console.log('Server listening on ' + HOST +':'+ PORT);
說說上面服務端代碼怎樣運行
爲何要說這個呢,由於不是每個iOS開發都是會懂那些JS的,上面的服務端代碼就是我給你們推薦的第一篇文章中的,我本身寫這試了試,這個運行起來推薦給你們的是這款 Sublime Text2 這款軟件,這個 Sublime Text3是已經出了,但你能下載到MAC版本上面的只能是Sublime Text2,因此你能夠先百度把這東西下載了!
接下來,你還須要安裝它 Node.js , 下載並安裝Node.js , 你下載安裝以後,須要在 Sublime Text2添加支持JS 的 Build System , 下圖選中這個 New Build System... ,添加下面代碼:
{"cmd":["node","$file"],"selector":"source.js"}
NOTE: 要是你的node是一步一步點擊直接安裝的,上面代碼中的node要變成你安裝的node的絕對路徑,怎麼找node的絕對路徑??
簡單呢:打開你的終端 which node 搞定!!
完結
這篇博客寫的也是花了許久的時間,因爲篇幅的問題,不能再寫一下去了,再寫就真的太長了,後面的心跳、重連等等問題再下一篇文章中總結,在下一篇中打算經過結合CocosAsnySocket這個三方總結Socket剩下的問題,最後,仍是給Telegram羣打個廣告,有在看這個開源項目的朋友能夠加一下下面的羣, 無論是Android 、 PC 、iOS 只要是和Telegram相關的問題,都有人會幫你解決,你找到隊伍了!一塊兒學習!篇幅長不免可能會有問題,要有什麼總結的很差的或者有問題的地方,加我QQ或者下面留言交流!
羣號能夠直接粘貼這裏:485718322