iOS websocket
最近在开发一个直播应用,需要用到弹幕功能,后台说要用websocket来实现,所以学习了一下
一、 RocketSocket
搜索了一下发现,用的最多的还是Facebook的RocketSocket库,虽然已经停止维护了,但是还能使用。
-
创建socket
- (SRWebSocket *)webSocket { if (!_webSocket) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:SOCKET_SERVER_URL]]; [request setValue:[NSString getAccessTokenString] forHTTPHeaderField:@"Authorization"]; _webSocket = [[SRWebSocket alloc] initWithURLRequest:request]; _webSocket.delegate = self; } return _webSocket; }
-
打开socket,开始连接
- (void)openWebSocket { if (self.webSocket.readyState == SR_OPEN) { return; } [self.webSocket open]; }
-
监听消息
- (void)subscribeNewMessage { NSString *sendString = [NSString stringWithFormat:@"SUBSCRIBE destination: %@ id: %@ ", SOCKET_SEND_URL(@(self.channelId)), @"sub-0"]; NSLog(@"%@", sendString); NSData *data = [sendString dataUsingEncoding:NSUTF8StringEncoding]; [self.webSocket send:data]; }
-
发送消息
- (void)sendMessage:(NSDictionary *)message { if (self.webSocket) { if (self.webSocket.readyState == SR_OPEN) { NSString *sendString = [NSString stringWithFormat:@"SEND Authorization: %@ destination: %@ %@", [NSString getAccessTokenString], SOCKET_SEND_URL(message[@"accountId"]), [NSString getJsonStringOfDictionary:message]]; NSLog(@"sendMessage :%@", sendString); NSData *data = [sendString dataUsingEncoding:NSUTF8StringEncoding]; [self.webSocket send:data]; } } else { [self openWebSocket]; } }
-
SRWebSocketDelegate, 代理事件
#pragma mark - SRWebSocketDelegate - (void)webSocketDidOpen:(SRWebSocket *)webSocket { NSLog(@"webSocketDidOpen"); if ([webSocket isEqual:self.webSocket]) { self.reopenCount = 0; // [self startHeartBeat]; // [self subscribeNewMessage]; } } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { NSLog(@"didFailWithError: %@", error); if ([webSocket isEqual:self.webSocket]) { [self reopenWebSocket]; } } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { NSLog(@"didCloseWithCode: %ld, %@, %d", (long)code, reason, wasClean); if ([webSocket isEqual:self.webSocket]) { [self closeWebSocket]; } } - (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload { NSLog(@"didReceivePong: %@", pongPayload); if ([webSocket isEqual:self.webSocket]) { } } - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { NSLog(@"didReceiveMessage: %@", message); if ([webSocket isEqual:self.webSocket]) { if (self.receiveMessage) { self.receiveMessage(message); } } }
-
心跳和重连
因为封装的比较简单,代码看起来很明了,但是有些地方需要自己完善。我这里主要加了心跳和重连机制。- (NSTimer *)heartBeatTimer { if (!_heartBeatTimer) { _heartBeatTimer = [NSTimer timerWithTimeInterval:heartBeatTimeInterval target:self selector:@selector(sendPing) userInfo:nil repeats:YES]; } return _heartBeatTimer; } - (void)startHeartBeat { if (!_heartBeatTimer) { [[NSRunLoop currentRunLoop] addTimer:self.heartBeatTimer forMode:NSRunLoopCommonModes]; // [[NSRunLoop currentRunLoop] run];//子线程需要手动启动runloop } } - (void)stopHeartBeat { if (_heartBeatTimer) { [self.heartBeatTimer invalidate]; self.heartBeatTimer = nil; } } - (void)sendPing { if (self.webSocket.readyState == SR_OPEN) { [self.webSocket sendPing:nil]; } }
- (void)reopenWebSocket { [self closeWebSocket]; if (self.reopenCount >= maxReopenCount) { if (self.reconnectFailed) { self.reconnectFailed(); } return; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [self openWebSocket]; self.reopenCount ++; }); }
二、 WebsocketStompKit
用rocketsocket写完之后,开始和后端联调,发现连接成功之后后端根本发现不了,连log都没有。对比着js中的方法,发现有用到Stomp,看源码挺简单的,只是把数据封装了一层,所以我就照着写了,在调试的时候还是不行。然后就去网上搜索iOS支持stomp协议的websocket库,就找到了这个WebsocketStompKit。
这个库封装的更好一些,用起来很方便。
-
创建client
- (STOMPClient *)webSocket { if (!_webSocket) { NSURL *url = [NSURL URLWithString:SOCKET_SERVER_URL]; NSDictionary *headers = @{ @"Authorization": [NSString getAccessTokenString], }; _webSocket = [[STOMPClient alloc] initWithURL:url webSocketHeaders:headers useHeartbeat:YES]; _webSocket.delegate = self; } return _webSocket; }
-
打开socket,开始连接
- (void)openWebSocket { NSDictionary *headers = @{ @"Authorization": [NSString getAccessTokenString], }; [self.webSocket connectWithHeaders:headers completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { NSLog(@"connectWithHeaders: %@---%@", connectedFrame, error); if (error) {//连接失败提示 dispatch_async(dispatch_get_main_queue(), ^{ }); } else {//连接成功订阅消息 [self subscribeNewMessage]; } }]; }
-
订阅消息
- (void)subscribeNewMessage { [self.webSocket subscribeTo:[NSString stringWithFormat:@"/topic/222.%lld", self.channelId] headers:@{} messageHandler:^(STOMPMessage *message) { NSLog(@"receive message: %@", message); if (self.receiveMessage) { self.receiveMessage([NSString getDictionaryFromJsonString:message.body]); } }]; }
-
发送消息
- (void)sendMessage:(NSDictionary *)message { NSDictionary *headers = @{ @"Authorization": [NSString getAccessTokenString], }; [self.webSocket sendTo:SOCKET_SEND_URL(message[@"accountId"]) headers:headers body:[NSString getJsonStringOfDictionary:message]]; }
使用过程中又崩溃的情况,我这里修改了几个地方,详情请见我的Fork
比较上面的两种库,回调方式分别用来delegate和block,看起来使用delegate的方式容易对整个流程有把控,每一步都有对应的方法,写起来稍微麻烦一些,但是容易理解;使用block的方式写起来很简单,但是理解起来稍显困难,不过更容易集中注意力到事件及回调上,其他过程可忽略。我倾向于前者。