iOS websocket

iOS websocket

最近在开发一个直播应用,需要用到弹幕功能,后台说要用websocket来实现,所以学习了一下

一、 RocketSocket
搜索了一下发现,用的最多的还是Facebook的RocketSocket库,虽然已经停止维护了,但是还能使用。

  1. 创建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;
    }
    
  2. 打开socket,开始连接

    - (void)openWebSocket {
        if (self.webSocket.readyState == SR_OPEN) {
            return;
        }
        [self.webSocket open];
    }
    
  3. 监听消息

    - (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];
    }
    
  4. 发送消息

    - (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];
        }
    }
    
  5. 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);
            }
        }
    }
    
  6. 心跳和重连
    因为封装的比较简单,代码看起来很明了,但是有些地方需要自己完善。我这里主要加了心跳和重连机制。

    - (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

这个库封装的更好一些,用起来很方便。

  1. 创建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;
    }
    
  2. 打开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];
            }
        }];
    }
    
  3. 订阅消息

    - (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]);
            }
        }];
    }
    
  4. 发送消息

    - (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的方式写起来很简单,但是理解起来稍显困难,不过更容易集中注意力到事件及回调上,其他过程可忽略。我倾向于前者。

参考:
iOS WebSocket长连接
WebSocket Demo
WebsocketStompKit使用

原文地址:https://www.cnblogs.com/shenyuiOS/p/14285352.html