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

news2024/12/26 1:03:40

文章目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣刷题19天

106.从中序与后序遍历序列构造二又树(1、在中序、前序和后序&#xff0c;每轮取得时候数量都一样. 2、必须要有中序才能推测出来) 这道题下面是前提&#xff1a; 如果没有这个前提&#xff0c;会出现下面情况(前序遍历会变成新的树)&#xff1a; 运行代码&#xff1a; class S…

MySQL简介与基本的select语句

1.SQL的分类 DDL(Data Definition Languages、数据定义语言)&#xff0c;这些语句定义了不同的数据库、表、视图、索引等数据库对象&#xff0c;还可以用来创建、删除、修改数据库和数据库表的结构. 主要的语句关键字包括 CREATE、DROP、ALTER等。 DML(Data Manipulation Lan…

CSS3 的其他特性(了解)

目录 1.CSS3滤镜filter&#xff1a; 2.CSS3 calc函数 3.CSS3 过度&#xff08;重点&#xff09; 4.进度条案例 5.侠义的HTML5 CSS3 1.CSS3滤镜filter&#xff1a; filter CSS属性及那个模糊或颜色偏移等图形效果应用于元素。 filter: 函数()&#xff1b; 例如&#xff1a;…

端子排电压继电器 动作时间短 35mm卡槽安装 JY-7GA/2 80-320VAC

JY-7GA/2端子排电压继电器品牌:JOSEF约瑟名称:端子排电压继电器型号:JY-7GA/2电压整定范围:60200VAC/90300VDC功率消耗:&#xff1c;3W触点容量:250V5A返回系数:过压0.920.96/欠压1.041.08 系列型号&#xff1a; JY-7GA/DK/220端子排电压继电器&#xff1b; JY-7GB/DK/220端…

sqoop将hive中的数据导入MySQL不能正常显示中文——已解决

问题&#xff1a; 原因&#xff1a; 结果查看 问题&#xff1a; 在做练习利用sqoop工具将hive中的表导入到MySQL之后&#xff0c;在MySQL查看中文部分不能正常显示 输入sqoop执行语句 sqoop export -connect "jdbc:mysql://HadoopMaster:3306/hive_to_mysql?useUnicode…

要不要提前去实习?

大家好&#xff0c;我是帅地。 秋招结束&#xff0c;很多人在纠结要不要提前去公司实习&#xff0c;最近也有一些帅友问了这个问题 说说我去年在腾讯实习的经历给你做个参考吧。 入职前的学习 20年初那会&#xff0c;还是蛮激动&#xff0c;之前我从来没有去打过工&#xff…

如何用Xcode安装ipa

Xcode安装ipa iOS APP上架App Store其中一个步骤就是要把ipa文件上传到App Store&#xff01;​ 下面进行步骤介绍&#xff01;​ 利用Appuploader这个软件&#xff0c;可以在Windows、Linux或Mac系统中申请ios和上传IPA到App Store Connect。​ 非常的方便&#xff0c;没有…

08-01 分布式系统理论

分布式一致性和CAP理论 C&#xff08;一致性&#xff09;&#xff1a;在分布式环境中&#xff0c;一致性是指数据在多个副本之间能否保持一致的特性A&#xff08;可用性&#xff09;&#xff1a;系统提供的服务必须一直处于可用的状态&#xff0c;对于用户的每一个操作请求总是…

butter滤波器

文章目录 什么是巴特沃斯滤波器&#xff1f;滤波器的阶数的含义matlab中的butterscipy.signal butter 中有哪些参数&#xff0c;各参数分别是什么含义&#xff1f; 本节围绕以下问题展开 butter 是什么&#xff1f;滤波器的阶数是什么意思&#xff1f;matlab中的butter参数含义…

00后工资太低想转行?选择云计算如何

00后已经成为目前从业最年轻的群体&#xff0c;有活力也需求明确&#xff0c;搞钱已经成为上班最核心的目的。但也有一波比较迷茫的00后&#xff0c;比如原专业学得不好&#xff0c;没有对口就业、工作内容不喜欢、工资太低养不活自己等等。而工资太低也是大部分00后想转行的主…

Linux 服务器设置 jar包开机自启动

一、新建jar包启动sh文件 新建startup.sh脚本文件&#xff0c;启动项目jar包 nohup java -jar test.jar >/dev/null 2>&1 & 二、设置自动启动命令 1.进入rc.d目录 执行cd /etc/rc.d 命令&#xff0c;进入rc.d目录 如图所示&#xff08;示例&#xff09;&…

如何将参考文献格式改成目标期刊要求的格式?

最近在改投期刊&#xff0c;所以要对参考文献的格式进行修改&#xff0c;记录一下修改过程中遇到的问题和解决方法&#xff0c;希望对小伙伴们有所帮助&#xff01; (1)问题&#xff1a;不知道目标期刊的参考文献格式是什么怎么办&#xff1f; (2)解决&#xff1a;下载目标期刊…

论文解读:DeepZ:一种用于Z-DNA预测的深度学习方法

标题 DeepZ: A Deep Learning Approach for Z-DNA Prediction. DOI 10.1007/978-1-0716-3084-6_15 期刊 Methods in molecular biology 作者 Nazar Beknazarov; Maria Poptsova 出版日期 2023-01-01 Github:https://github.com/Nazar1997/Sparse-vector 网址 https://doi…

一篇文章了解 Java IO 流

一&#xff1a;IO 流的概述 1. 什么是 IO 流&#xff1f; 存储和读取数据的解决方法 I&#xff1a;input O&#xff1a;output 流&#xff1a;像水流一样传输数据 2. IO 流的作用&#xff1f; 用于读写数据&#xff08;本地文件&#xff0c;网络&#xff09; 3. IO 流按…

linux系统 数组 的运用

这里写目录标题 数组数组的定义数组的定义方式输出数组的方式数组包括的数据类型数组的删除与追加数组的删除数组的追加 数组切片与替换切片替换 冒泡排序 数组 数组的定义 数组是存放相同类型数据的集合&#xff0c;在内存中开辟了连续的空间&#xff0c;通常配合循环使用 …

爬虫逆向——某建筑市场监管平台的滑块验证码分析

目录 网址链接&#xff1a; 正文&#xff1a; 一、思路分析 二、图片处理 三、完整代码 网址链接&#xff1a; aHR0cHM6Ly9nY3htLmh1bmFuanMuZ292LmNuL2RhdGFzZXJ2aWNlLmh0bWw &#xff08;bs64解密可见&#xff09; 正文&#xff1a; 注&#xff1a;分步的代码为示例…

C语言中数据结构——带头双向循环链表

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;数据结构 &#x1f525;座右铭&#xff1a;“不要等到什么都没…

如何删除打印机任务?三种快速删除打印机任务的方法

打印机用户可能会经常遇到添加错的打印任务&#xff0c;或是一不小心重复选择过多的打印任务&#xff0c;环保人人有责&#xff0c;杜绝纸张浪费&#xff0c;驱动人生就为大家带来快速删除打印任务的方法。 方法一&#xff1a;使用Windows自带的清理功能 在Windows操作系统中…

谷歌全线反击!PaLM 2部分性能已经超越GPT-4

ChatGPT横空出世&#xff0c;所有人都能够明确感知到AI的惊人潜力&#xff0c;瞬间改变了整个AI行业的节奏&#xff0c;不紧不慢的谷歌也开始紧张了。 ChatGPT舆论热潮仍未消退&#xff0c;红色警报又拉响 北京时间5月11日凌晨1点&#xff0c;Google I/O 2023开发者大会上发布…

C++ | 深拷贝和浅拷贝

C 深拷贝和浅拷贝 当类的函数成员存在__指针成员__时会产生深拷贝和浅拷贝和问题。 在进行对象拷贝时会使用默认拷贝构造函数&#xff0c;默认进行浅拷贝&#xff0c;即只会拷贝指针的值&#xff0c;新拷贝的指针与原来的指针指向同一内存&#xff1b; 浅拷贝带来的问题是&…