閑扯
WebSocket 以前沒用過糯崎,之前寫過一篇博客是基于原生socket的(查看)比較復(fù)雜河泳,慎入。今天另外一個APP需要接websocket了薄霜,然后便找到了facebook的 SocketRocket 框架纸兔,然后用了一天時間接上了食拜,完成了掉線自動重連,自動重登錄流强,心跳等等功能呻待,用法比原生socket簡單(原生socket基于TCP/UDP協(xié)議)。
為什么用 WebSocket
因?yàn)锳PP里面有個聊天功能奏篙,需要服務(wù)器主動推數(shù)據(jù)到APP。HTTP 通信方式只能由客戶端主動拉取为严,服務(wù)器不能主動推給客戶端第股,如果有實(shí)時的消息话原,要立刻通知客戶端就麻煩了,要么客戶端每隔幾秒鐘發(fā)一次請求涉馅,看看有沒有新數(shù)據(jù)黄虱,這種方式想想都知道耗流量電量。還一種方式就是走TCP/UDP協(xié)議服務(wù)器主動推給你盐捷,這種方式省流量默勾。還有就是用websocket,websocket是h5里面的東西滞诺,h5我不太會环疼,反正它比原生socket用法簡單。
用法
用 SocketRocket 框架淋叶,記住幾個代理方法就好了伪阶,很簡單栅贴。
1.創(chuàng)建和設(shè)置代理對象
SRWebSocket *socket = [[SRWebSocket alloc] initWithURLRequest:
[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://ip地址:端口"]];
socket.delegate = self; // 實(shí)現(xiàn)這個 SRWebSocketDelegate 協(xié)議啊
[socket open]; // open 就是直接連接了
2.連接成功會調(diào)用這個代理方法
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
NSLog(@"連接成功,可以立刻登錄你公司后臺的服務(wù)器了凝赛,還有開啟心跳");
}
3.連接失敗會調(diào)用這個方法,看 NSLog 里面的東西
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(@"連接失敗捆昏,這里可以實(shí)現(xiàn)掉線自動重連屡立,要注意以下幾點(diǎn)");
NSLog(@"1.判斷當(dāng)前網(wǎng)絡(luò)環(huán)境搀军,如果斷網(wǎng)了就不要連了勇皇,等待網(wǎng)絡(luò)到來敛摘,在發(fā)起重連");
NSLog(@"2.判斷調(diào)用層是否需要連接,例如用戶都沒在聊天界面兄淫,連接上去浪費(fèi)流量");
NSLog(@"3.連接次數(shù)限制捕虽,如果連接失敗了,重試10次左右就可以了房揭,不然就死循環(huán)了晌端。
或者每隔1咧纠,2,4梧奢,8钧椰,10,10秒重連...f(x) = f(x-1) * 2, (x<5) f(x)=10, (x>=5)");
}
4.連接關(guān)閉調(diào)用這個方法瓶埋,注意連接關(guān)閉不是連接斷開,關(guān)閉是 [socket close] 客戶端主動關(guān)閉,斷開可能是斷網(wǎng)了养筒,被動斷開的曾撤。
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
NSLog(@"連接斷開,清空socket對象晕粪,清空該清空的東西挤悉,還有關(guān)閉心跳!");
}
5.收到服務(wù)器發(fā)來的數(shù)據(jù)會調(diào)用這個方法
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSLog(@"收到數(shù)據(jù)了巫湘,注意 message 是 id 類型的装悲,學(xué)過C語言的都知道,id 是 (void *)
void* 就厲害了尚氛,二進(jìn)制數(shù)據(jù)都可以指著诀诊,不詳細(xì)解釋 void* 了");
NSLog(@"我這后臺約定的 message 是 json 格式數(shù)據(jù)
收到數(shù)據(jù)阅嘶,就按格式解析吧属瓣,然后把數(shù)據(jù)發(fā)給調(diào)用層");
}
6.向服務(wù)器發(fā)送數(shù)據(jù)
發(fā)送的時候可能斷網(wǎng),可能socket還在連接讯柔,要判斷一些情況抡蛙,寫在下面了
發(fā)送邏輯是,我有一個 socketQueue 的串行隊列魂迄,發(fā)送請求會加到這個隊列里粗截,然后一個一個發(fā)出去,如果掉線了极祸,重連連上后繼續(xù)發(fā)送慈格,對調(diào)用層透明,調(diào)用層不需要知道網(wǎng)絡(luò)斷開了遥金。
- (void)sendData:(id)data {
WEAKSELF(ws);
dispatch_async(self.socketQueue, ^{
if (ws.socket != nil) {
// 只有 SR_OPEN 開啟狀態(tài)才能調(diào) send 方法啊浴捆,不然要崩
if (ws.socket.readyState == SR_OPEN) {
[ws.socket send:data]; // 發(fā)送數(shù)據(jù)
} else if (ws.socket.readyState == SR_CONNECTING) {
NSLog(@"正在連接中,重連后其他方法會去自動同步數(shù)據(jù)");
// 每隔2秒檢測一次 socket.readyState 狀態(tài)稿械,檢測 10 次左右
// 只要有一次狀態(tài)是 SR_OPEN 的就調(diào)用 [ws.socket send:data] 發(fā)送數(shù)據(jù)
// 如果 10 次都還是沒連上的选泻,那這個發(fā)送請求就丟失了,這種情況是服務(wù)器的問題了美莫,小概率的
// 代碼有點(diǎn)長页眯,我就寫個邏輯在這里好了
} else if (ws.socket.readyState == SR_CLOSING || ws.socket.readyState == SR_CLOSED) {
// websocket 斷開了,調(diào)用 reConnect 方法重連
[ws reConnect:^{
NSLog(@"重連成功厢呵,繼續(xù)發(fā)送剛剛的數(shù)據(jù)");
[ws.socket send:data];
}];
}
} else {
NSLog(@"沒網(wǎng)絡(luò)窝撵,發(fā)送失敗,一旦斷網(wǎng) socket 會被我設(shè)置 nil 的");
NSLog(@"其實(shí)最好是發(fā)送前判斷一下網(wǎng)絡(luò)狀態(tài)比較好襟铭,我寫的有點(diǎn)晦澀碌奉,socket==nil來表示斷網(wǎng)");
}
});
}
7.心跳機(jī)制
心跳機(jī)制就不難了短曾,開個定時器,問下后臺要每隔多少秒發(fā)送一次心跳請求就好了赐劣。然后注意嫉拐,斷網(wǎng)了或者socket斷開的時候把心跳關(guān)一下,省資源魁兼,不然都斷網(wǎng)了婉徘,還在循環(huán)發(fā)心跳,浪費(fèi)CPU和電量咐汞。
8.終于接完websocket了盖呼,下班回家壓壓驚。我第一次用碉考,其實(shí)不難塌计,就是考慮的情況比較多挺身,整個邏輯有點(diǎn)多侯谁,主要代碼就是上面那些了,其他不重要的代碼我就不復(fù)制粘貼上來了章钾。