文章目录

  • WebSocket
  • WebSocket特点
  • SocketRocket
  • 导入头文件设置代理
  • SRWebSocket的初始化和建立连接
  • SRWebSocketDelegate 代理方法实现
  • 加上简单UI实现两个用户之间简单通信
  • 浅看了一点点源码(理解的不深)



偶然之间了解到了利用WebSocket实现后端和前端的相互发送消息,就查了查在iOS里这个东西该怎么写,用舍友写的接口简单实现了两个用户的通信。

WebSocket

  • WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它允许客户端和服务器之间建立一个持久性的连接,以便可以在任何时间点进行双向通信。
  • 传统的 HTTP 请求-响应模式只支持客户端发起请求,服务器做出响应。也就是说,当一个页面被加载时,它通常会发起一个 HTTP 请求获取某些资源,例如 HTMLCSSJavaScript 文件等。一旦这些资源被发送到浏览器,连接就被关闭了,除非有其他请求发送。
  • 但是,对于某些 Web 应用程序,需要更加实时的交互。这就是 WebSocket 所提供的优势:它们允许客户端和服务器之间保持开放的连接,使得双方可以随时发送消息。
  • WebSocket 使用 HTTP 协议进行握手,然后升级到 WebSocket协议。一旦这个升级完成,客户端和服务器将使用 WebSocket 协议进行通信。WebSocket 通过使用标准的 TCP/IP 套接字来实现,在网络上发送和接收数据。
  • WebSocket 在很多场景下都非常实用,比如在线游戏、实时聊天、股票行情推送等,可以大大提高应用程序的交互效果和用户体验。

WebSocket特点

1. 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3. 数据格式比较轻量,性能开销小,通信高效。
4. 可以发送文本,也可以发送二进制数据。
5. 没有同源限制,客户端可以与任意服务器通信。
6. 协议标识符是ws(如果加密,则为wss),服务器网址就是URL。`ws://example.com:80/some/path`
7. 全双工通信。

SocketRocket

使用方法和之前的第三方库一样,利用cocoapods导库,具体操作就不详写了,在之前的博客里也写到过。

导入头文件设置代理

在需要用的地方导入#import <SocketRocket/SRWebSocket.h>
并遵循代理 SRWebSocketDelegate
声明一个属性 SRWebSocket *webSocket;

SRWebSocket的初始化和建立连接

self.socket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://101.43.193.62:9090/ws?id=%d", self.senderSubscriberNumber]]]];
    self.socket.delegate = self;
    NSLog(@"Opening Connection...");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    dispatch_async(queue, ^{
        [self.socket open];
    });

在这里看到,我为什么把 [self.socket open];这句代码放在了一个异步并发队列里,在之前我直接在主线程调用open方法,编译器给我报了一堆奇怪的东西,查找之后告诉我是线程优先级倒置导致的:
在iOS中,每个线程都有一个与之关联的Quality of Service(QoS)级别,用于指定它的相对优先级。 QOS_CLASS_USER_INTERACTIVE 表示线程需要立即响应并具有最高优先级,而 QOS_CLASS_DEFAULT 表示线程具有默认优先级。

这个问题的原因应该是这个第三方库底层的原因,我看了一些源码,发现调用了很多GCD的东西,暂时还没发现原因。

SRWebSocketDelegate 代理方法实现

//socket 连接成功
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    NSLog(@"Websocket Connected");
}

//socket 连接失败
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    NSLog(@":( Websocket Failed With Error %@", error);
    self.socket = nil;
    // 断开连接后每过1s重新建立一次连接
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self setSocket];
    });
}

//socket连接断开
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    NSLog(@"WebSocket closed");
    self.socket = nil;
}

//收到消息
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
    NSLog(@"Received \"%@\"", message);
}

四个最常用方法分别表示,连接成功,连接失败,连接断开,和收到消息,我们在每个协议函数中做自己该做的事即可。

后端发给我的信息是这样的:

ios不支持opengl iOS不支持websocket吗_websocket


在接收到消息时本想用字典存储,转化的时候刚开始一直有问题,试了好多次才转化成功。

NSString *messageString = (NSString *)message;
    NSData *messageData = [messageString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *messageDictionary = [NSJSONSerialization JSONObjectWithData:messageData options:kNilOptions error:nil];

加上简单UI实现两个用户之间简单通信

ios不支持opengl iOS不支持websocket吗_HTTP_02

ios不支持opengl iOS不支持websocket吗_ios不支持opengl_03

浅看了一点点源码(理解的不深)

1.基本初始化方法
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates

底层调用了_SR_commonInit方法

//初始化
- (void)_SR_commonInit;
{
    //得到url schem小写
    NSString *scheme = _url.scheme.lowercaseString;
        //如果不是这几种,则断言错误
    assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);

    _readyState = SR_CONNECTING;
    _webSocketVersion = 13;
     //初始化工作的队列,串行
    _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    
    //给队列设置一个标识,标识为指向自己的,上下文对象为这个队列
    dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
    
    //设置代理queue为主队列
    _delegateDispatchQueue = dispatch_get_main_queue();
    
    //retain主队列?
    sr_dispatch_retain(_delegateDispatchQueue);
    
    //读Buffer
    _readBuffer = [[NSMutableData alloc] init];
    //输出Buffer
    _outputBuffer = [[NSMutableData alloc] init];
    //当前数据帧
    _currentFrameData = [[NSMutableData alloc] init];
    //消费者数据帧的对象
    _consumers = [[NSMutableArray alloc] init];
    
    _consumerPool = [[SRIOConsumerPool alloc] init];
    //注册的runloop
    _scheduledRunloops = [[NSMutableSet alloc] init];
    ....省略了一部分代码
}

整个过程:

  • 判断协议类型
  • 初始化了_workQueue,这个GCD队列用来处理主要的业务逻辑,包括处理错误、发送内容、关闭连接等。
  • 初始化_delegateDispatchQueue,这个队列是用来向外发送通知的。我们可以通过- (void)setDelegateOperationQueue:(NSOperationQueue*) queue- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue来自定义这个队列。

2.创建输入输出流

//初始化流
- (void)_initializeStreams;
{
    //断言 port值小于UINT32_MAX
    assert(_url.port.unsignedIntValue <= UINT32_MAX);
    //拿到端口
    uint32_t port = _url.port.unsignedIntValue;
    //如果端口号为0,给个默认值,http 80 https 443;
    if (port == 0) {
        if (!_secure) {
            port = 80;
        } else {
            port = 443;
        }
    }
    NSString *host = _url.host;
    
    CFReadStreamRef readStream = NULL;
    CFWriteStreamRef writeStream = NULL;
    //用host创建读写stream,Host和port就绑定在一起了
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
    
    //绑定生命周期给ARC  _outputStream = __bridge transfer
    _outputStream = CFBridgingRelease(writeStream);
    _inputStream = CFBridgingRelease(readStream);
    
    //代理设为自己
    _inputStream.delegate = self;
    _outputStream.delegate = self;
}

3.open方法

//开始连接
- (void)open;
{
    assert(_url);
    //如果状态是正在连接,直接断言出错
    NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once");

    //自己持有自己
    _selfRetain = self;
    //判断超时时长
    if (_urlRequest.timeoutInterval > 0)
    {
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC);
        //在超时时间执行
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            //如果还在连接,报错
            if (self.readyState == SR_CONNECTING)
                [self _failWithError:[NSError errorWithDomain:@"com.squareup.SocketRocket" code:504 userInfo:@{NSLocalizedDescriptionKey: @"Timeout Connecting to Server"}]];
        });
    }
    //开始建立连接
    [self openConnection];
}
//开始连接
- (void)openConnection;
{
    //更新安全、流配置
    [self _updateSecureStreamOptions];
    
    //判断有没有runloop
    if (!_scheduledRunloops.count) {
        //SR_networkRunLoop会创建一个带runloop的常驻线程,模式为NSDefaultRunLoopMode。
        [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    //开启输入输出流
    [_outputStream open];
    [_inputStream open];
}

- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
{
    [_outputStream scheduleInRunLoop:aRunLoop forMode:mode];
    [_inputStream scheduleInRunLoop:aRunLoop forMode:mode];
    
    //添加到集合里,数组
    [_scheduledRunloops addObject:@[aRunLoop, mode]];
}

再往后就没看了,浅看了这两三个方法,后面还会调的方法:

  • didConnect
  • 构建HTTP Header
  • 发送HTTP Header
  • 注册一个接收服务器返回Header信息的监听,并在回调内进行相应处理
  • - (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
  • 这是NSStream的回调方法,输入和输出流的共同回调
  • NSStreamEventOpenCompleted 连接打开;NSStreamEventHasBytesAvailable 可读取;NSStreamEventHasSpaceAvailable 可写入数据
  • NSStreamEventOpenCompleted里面的[self _pumpScanner];用来触发第5条中的3,来处理服务器返回的握手Header信息