• 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使用

  • 相关阅读:
    Redis下载地址
    form序列化
    隐藏GET请求URL参数
    IntelliJ IDEA中显示方法说明快键键
    Myeclipse2014在线安装SVN插件
    Spring Cloud Gateway 集成 Sentinel 网关限流(2)
    Spring Cloud Gateway 集成 Sentinel 网关限流(1)
    Spring Cloud Gateway 集成 Nacos 实现请求负载
    微服务网关之Spring Cloud Gateway
    Nacos 的安装
  • 原文地址:https://www.cnblogs.com/shenyuiOS/p/14285352.html
Copyright © 2020-2023  润新知