【iOS】—— 实现WebSocket发送消息(SocketRocket第三方库的使用和解析)

文章目录

    • 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 协议有着良好的兼容性。默认端口也是80443,并且握手阶段采用 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);
}

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

后端发给我的信息是这样的:
请添加图片描述
在接收到消息时本想用字典存储,转化的时候刚开始一直有问题,试了好多次才转化成功。

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

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

请添加图片描述

请添加图片描述

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

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信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/19713.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

获取两个日期间时长 (XX天XX时XX分)

使用场景&#xff1a; 发货日期与到货日期 计算运输时长 代码&#xff1a; private String getMinuteTime(String startTime, String endTime) {String minuteTime null;if (StrUtil.isNotBlank(startTime) && StrUtil.isNotBlank(endTime)) {long minute DateUti…

华为OD机试真题 Java 实现【猜字谜】【2023Q1 100分】

一、题目描述 小王设计了一人简单的清字谈游戏&#xff0c;游戏的迷面是一人错误的单词&#xff0c;比如nesw&#xff0c;玩家需要猜出谈底库中正确的单词。猜中的要求如 对于某个谜面和谜底单词&#xff0c;满足下面任一条件都表示猜中&#xff1a; 变换顺序以后一样的&…

寅家科技完成近亿元B1轮融资,加速高阶智能驾驶布局

近日&#xff0c;寅家科技宣布完成近亿元人民币B1轮融资&#xff0c;本轮融资由东方富海、深创投、深圳高新投联合投资&#xff0c;所募资金主要用于公司高阶智能驾驶技术产品的研发迭代&#xff0c;以及智能驾驶产品量产、海外市场开拓&#xff0c;从而进一步提升核心产品的市…

【重新定义matlab强大系列三】MATLAB清洗离群数据(查找、填充或删除离群值)

&#x1f517; 运行环境&#xff1a;matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

应届生如何在职场中提高竞争力?这些方法和策略不容错过!

当前就业形势严峻&#xff0c;对于即将步入职场的应届生来说&#xff0c;提高自己的竞争力显得尤为重要。那么&#xff0c;要如何提高自己的职场竞争力呢&#xff1f;本文将为你分享一些有效的方法和策略&#xff0c;帮助你在职场中获得更好的发展。 一、提高自身素质 职场中&…

关于ADC的笔记1

ADC&#xff0c;全称Anlog-to-Digital Converter&#xff0c;模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件&#xff0c;我们能通过ADC将外界的电压值读入我们的单片机中. 常见的ADC有两种 1.并联比较型&#xff1a; 它的优点是转换速度最快&#x…

VMware 产品下载汇总 2023 持续更新中

本站 VMware 产品下载汇总&#xff1a;vSphere、NSX、Tanzu、Aria、Cloud… 请访问原文链接&#xff1a;https://sysin.org/blog/vmware/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 本站提供的 VMware 软件全部为 “试用版…

数据分析04——Pandas简介/Series对象/DataFrame对象

1、Pandas简介&#xff1a; Pandas是基于NumPy开发的数据分析三大剑客之一&#xff0c;Python数据分析的核心库提供快速、灵活、明确的数据结构Series对象&#xff1a;一维数组结构&#xff0c;由index和value构成DataFrame对象&#xff1a;二维数组结构&#xff0c;由index、…

106.(cesium篇)cesium椎体旋转

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en"> <

RT-Thread 5.0.1 qemu-virt64-aarch64 解决编译问题

前言 最近在最新的 RT-Thread 上搭建 bsp qemu-virt64-aarch64 的编译环境&#xff0c;发现较新的 gcc 交叉编译器编译失败了。 经过尝试较旧版本的 gcc 交叉编译工具链&#xff0c;终于编译通过了 下载 gcc 交叉编译工具链&#xff0c;这里推荐使用 arm 官方的 gcc 下载地址…

眼球追踪、HDR、VST,从代码挖掘Valve下一代VR头显

擅长爆料、挖掘线索的Brad Lynch&#xff0c;此前发布了Quest Pro等设备的线索文章引发关注。​近期&#xff0c;又公布一系列与“Valve Deckard”VR头显相关消息&#xff0c;比如支持眼球追踪、HDR、VST透视、Wi-Fi网络等等。在SteamVR 1.26.1测试版更新、Steam用户端、Gamesc…

掌控MySQL并发:深度解析锁机制与并发控制

前一篇MySQL读取的记录和我想象的不一致——事物隔离级别和MVCC 讲了事务在并发执行时可能引发的一致性问题的各种现象。一般分为下面3种情况&#xff1a; 读 - 读情况&#xff1a;并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响&#xff0c;不会引起什么问题&…

基于matlab使用主动声纳系统进行水下目标检测

一、前言 此示例演示如何模拟具有两个目标的主动单基地声纳方案。声纳系统由各向同性投影仪阵列和单个水听器元件组成。投影仪阵列呈球形。反向散射信号由水听器接收。接收到的信号包括直接和多路径贡献。 二、水下环境 在浅水环境中&#xff0c;声源和目标之间存在多个传播路径…

探索深度学习中的计算图:PyTorch的动态图解析

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

MySQL的高级语句

一、SQL高级语句 1、 SELECT 显示表格中一个或数个栏位的所有资料 语法&#xff1a;SELECT "字段" FROM "表名"; select * from test1; select name from test1; select name,sex from test1;2、DISTINCT 不显示重复的内容 语法&#xff1a;SELECT D…

2023年主流的选择仍是Feign, http客户端Feign还能再战

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 微服务组件之http客户端Feign 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f649;。 …

古典密码体制--代换和置换

一、介绍与分类 1.介绍&#xff1a; 古典密码时期一般认为是从古代到19世纪末,这个时期生产力水平低,加密、解密方法主要以纸、笔或简单的器械来实现,在这个时期提出和使用的密码称为古典密码。古典密码是密码学发展的初级阶段。尽管古典密码大都较简单,但由于其安全性差&…

基于小程序制作一个ChatGPT聊天机器人

在AI技术日新月异的浪潮中,将ChatGPT与实战开发相结合,制作一个随身携带的聊天机器人,紧贴前沿的同时稳固基础。 一、前言1.1、什么是ChatGPT1.2、什么是文本完成二、API2.1、ChatGPT官网申请API所需要的key2.2、搭建API2.3、创建控制器及动作方法三、小程序3.1、页面创建3.…

彻底解决 Lost connection to MySQL server at ‘reading initial communication packet’, system error: 0 解决方法

当我遇到这错误的时候,我去网上也找过对应解决方法,出现这个的原因有很多种情况 大多是解决Linux系统里的 我是windows系统里的MySQL服务出问题了,所有那些方法对我来说毫无意义. 好了,说一下我的解决办法,其实也很简单 只需要卸载mysql服务,注册表也要删干净,也要把环境变…

信通初试第一:无科研无竞赛一战上岸上海交大819学硕感悟

笔者来自通信考研小马哥23上交819全程班学员 信通初试第一&#xff1a;无科研无竞赛一战上岸上海交大819学硕感悟 原创2023-04-27 11:04通信考研小马哥 笔者来自通信考研小马哥23上交819全程班学员 本人情况&#xff1a; 本人是19届交本&#xff0c;本科成绩很差&#xff0c;…