iOS高级理论:CocoaAsyncSocket 介绍与使用

2024-03-01 01:04

本文主要是介绍iOS高级理论:CocoaAsyncSocket 介绍与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、简介

CocoaAsyncSocket为Mac和iOS提供了易于使用和功能强大的异步套接字库,主要包含两个类:

GCDAsyncSocket:用GCD搭建的基于TCP/IP协议的socket网络库

GCDAsyncUdpSocket:用GCD搭建的基于UDP/IP协议的socket网络库.

本文主要介绍 GCDAsyncSocket的使用,他是一个TCP库,建在Grand Central Dispatch上面的。

二 客户端的使用

2.1 常用的API方法
2.1.1 初始化
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

需要delegate和delegate_queue才能使GCDAsyncSocket调用您的委托方法。提供delegateQueue是一个新概念。delegateQueue要求必须是一个串行队列,使得委托方法在delegateQueue队列中执行。

2.1.2 连接服务器
NSError *err = nil;
if (![self.clientSocket connectToHost:@"ip地址" onPort: "端口号" error:&err])  //异步!
{//  如果有错误,很可能是"已经连接"或"没有委托集"NSLog(@"I goofed: %@", err);
}

连接方法是异步的。这意味着当您调用connect方法时,它们会启动后台操作以连接到所需的主机/端口。

2.1.3 发送数据
//  发送数据
- (void)sendData:(NSData *)data{// -1表示超时时间无限大// tag:消息标记[self.clientSocket writeData:data withTimeout:-1 tag:0];
}

通过调用writeData: withTimeout: tag:方法,即可发送数据给服务器。

2.2 常用的委托方法
2.2.1 连接成功
// socket连接成功会执行该方法
- (void)socket:(GCDAsyncSocket*)sock didConnectToHost:(NSString*)host port:(UInt16)port{NSLog(@"--连接成功--");[sock readDataWithTimeout:-1 tag:0];
}
2.2.2 收到服务端数据
// 收到服务器发送的数据会执行该方法
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{NSString *serverStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];NSLog(@"服务端回包了--回包内容--%@---长度%lu",serverStr,(unsigned long)data.length);[sock readDataWithTimeout:-1 tag:0];
}
2.2.3 断开
// 断开连接会调取该方法
- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err{NSLog(@"--断开连接--");//  sokect断开连接时,需要清空代理和客户端本身的socket.self.clientSocket.delegate = nil;self.clientSocket = nil;
}

以上几个委托方法,使我们比较使用比较频繁的委托代理方法。

5、心跳包

心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令,按照一定的时间间隔发送,类似于心跳
用来判断对方(设备,进程或其它网元)是否正常运行,采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经离线。

@property(nonatomic, strong) NSTimer *heartbeatTimer;- (void)beginSendHeartbeat{// 创建心跳定制器self.heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(sendHeartbeat:) userInfo:nil repeats:YES];[[NSRunLoop mainRunLoop] addTimer:self.heartbeatTimer forMode:NSRunLoopCommonModes];
}- (void)sendHeartbeat:(NSTimer *)timer {if (timer != nil) {char heartbeat[4] = {0xab,0xcd,0x00,0x00}; // 心跳字节,和服务器协商NSData *heartbeatData = [NSData dataWithBytes:&heartbeat length:sizeof(heartbeat)];[self.clientSocket writeData:heartbeatData withTimeout:-1 tag:0];}
}
2.2 服务端

GCDAsyncSocket还允许您创建服务器,并接受传入的连接; 服务端使用基本和客户类似,只不过需要开启端口进行监听客户端连接。

1、初始化

@property(nonatomic, strong) GCDAsyncSocket *serverSocket;// 初始化服务端socket
self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

2、开放服务端的指定端口

 //  开放服务端的指定端口.NSError *error = nil;BOOL result = [self.serverSocket acceptOnPort:port error:&error];if (result) {NSLog(@"端口开启成功,并监听客户端请求连接...");}else {NSLog(@"端口开启失...");}

3、发送数据给客户端

 //  发送数据,和客户端使用一致
- (void)sendData:(NSData *)data{// -1表示超时时间无限大// tag:消息标记[self.serverSocket writeData:data withTimeout:-1 tag:0];
}

4、委托方法

(1) 监听到新的客户端socket连接委托:

 /* 存储所有连接的客户端 socket*/
@property(nonatomic, strong) NSMutableArray *arrayClient;//  监听到新的客户端socket连接,会执行该方法
- (void)socket:(GCDAsyncSocket *)serveSock didAcceptNewSocket:(GCDAsyncSocket *) newSocket{NSLog(@"%@ IP: %@: %hu 客户端请求连接...",newSocket,newSocket.connectedHost,newSocket.localPort);// 将客户端socket保存起来if (![self.arrayClient containsObject:newSocket]) {[self.arrayClient addObject:newSocket];}[newSocket readDataWithTimeout:-1 tag:0];
}监听到客户端连接,将客户端 socket 保存起来,因为服务器可能会收到很多客户端连接。

(2) 读取客户端数据:

 //  读取客户端发送的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {//  记录客户端心跳char heartbeat[4] = {0xab,0xcd,0x00,0x00}; // 心跳NSData *heartbeatData = [NSData dataWithBytes:&heartbeat length:sizeof(heartbeat)];if ([data isEqualToData:heartbeatData]) {NSLog(@"*************心跳**************");self.heartbeatDateDict[sock.connectedHost] = [NSDate date];}[sock readDataWithTimeout:-1 tag:0];
}

(3) 断开连接:

 //  断开连接
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{    NSLog(@"断开连接");
}

(4) 监听心跳包:

 // 子线程用于监听心跳包
@property(nonatomic, strong) NSThread *checkThread;
// 记录每个心跳缓存
@property (nonatomic, strong) NSMutableDictionary *heartbeatDateDict;
// 初始化子线程,并启动
self.checkThread = [[NSThread alloc]initWithTarget:sharedInstance selector:@selector(checkClientOnline) object:nil];
[self.checkThread start];#pragma checkTimeThread//  这里设置10检查一次 数组里所有的客户端socket 最后一次通讯时间,这样的话会有周期差(最多差10s),可以设置为1s检查一次,这样频率快
//  开启线程 启动runloop 循环检测客户端socket最新time
- (void)checkClientOnline{@autoreleasepool {[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(repeatCheckClinetOnline) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] run];}
}//  移除 超过心跳时差的 client
- (void)repeatCheckClinetOnline{if (self.arrayClient.count == 0) {return;}NSDate *date = [NSDate date];for (GCDAsyncSocket *socket in self.arrayClient ) {if ([date timeIntervalSinceDate:self.heartbeatDateDict[socket.connectedHost]]>10) {[self.arrayClient removeObject:socket];}}
}

三、CocoaAsyncSocket 读/写操作

3.1 排队 读/写 操作

CocoaAsyncSocket库的最佳功能之一是“排队 读/写 操作”。

写操作: 套接字未连接,但我还是可以开始写它,库将排队我的所有写操作,在套接字连接后,它将自动开始执行我的写操作!

NSError * err = nil ;
NSError *err = nil;
if (![self.clientSocket connectToHost:@"ip地址" onPort: "端口号" error:&err])  //异步!
{NSLog(@"I goofed: %@", err);
}//此时套接字未连接。
//但我还是可以开始写它!
//库将排队我的所有写操作,
//在套接字连接后,它将自动开始执行我的写操作![socket writeData: data1 withTimeout: - 1  tag: 1 ];[socket writeData: data2 withTimeout: - 1  tag: 2 ];

排队读: 我们可以通过长度获取到相应长度的数据,可以很好解决粘包问题。

[socket readDataToLength: datalength withTimeout: -1  tag: 0];
3.2 Tag参数了解

CocoaAsyncSocket中的tag参数是不通过套接字发送的,也不是从套接字读取的。tag参数只需通过各种委托方法回显给您。它旨在帮助简化委托方法中的代码。

  [socket writeData: data1 withTimeout: - 1  tag: 1 ];[socket writeData: data2 withTimeout: - 1  tag: 2 ];
//  当我们发送数据时候使用 tag 标记后,发送后可以在委托方法中根据 tag 看到那条数据已经发送出去了。
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag :( long)tag{if(tag == 1)NSLog(@"First request sent ");else  if(tag == 2)NSLog(@"Second request sent ");
}

在读取时数据时,tag 也很有帮助:

[socket readDataWithTimeout:-1 tag:0];
// 读取 tag 与上面方法的 tag 值是一一对应的。
#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12
- (void)socket:(GCDAsyncSocket *)sender didReadData :( NSData *)data withTag :( long)tag{if(tag == TAG_WELCOME){//  忽略欢迎信息}else  if(tag == TAG_CAPABILITIES){[self  processCapabilities:data];}else if (tag == TAG_MSG){[self  processMessage: data];}
}

四、Tcp 粘包

4.1 什么是tcp粘包?

TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,就会出现粘包现象。

4.2 TCP粘包解决方案

目前应用最广泛的是在消息的头部添加数据包长度,接收方根据消息长度进行接收;在一条TCP连接上,数据的流式传输在接收缓冲区里是有序的,其主要的问题就是第一个包的包尾与第二个包的包头共存接收缓冲区,所以根据长度读取是十分合适的;

4.2.1 解决发送方粘包

方案一: 发送产生是因为Nagle算法合并小数据包,那么可以禁用掉该算法;

方案二: TCP提供了强制数据立即传送的操作指令push,当填入数据后调用操作指令就可以立即将数据发送,而不必等待发送缓冲区填充自动发送;

方案三: 数据包中加头,头部信息为整个数据的长度(最广泛最常用);

//  `方案三`发送方解决粘包的代码部分:
- (void)sendData:(NSData *)data{NSMutableData *sendData = [NSMutableData data];// 获取数据长度NSInteger datalength = data.length;//  NSInteger长度转 NSDataNSData *lengthData = [NSData dataWithBytes:&datalength length:sizeof(datalength)];// 长度几个字节和服务器协商好。这里我们用的是4个字节存储长度信息NSData *newLengthData = [lengthData subdataWithRange:NSMakeRange(0, 4)];// 拼接长度信息[sendData appendData:newLengthData];//拼接数据[sendData appendData:data];// 发送加了长度信息的包[self.clientSocket writeData:[sendData copy] withTimeout:-1 tag:0];
}
4.2.2 解决接收方粘包

1、解析数据包头部信息,根据长度来接收;(最广泛最常用)

/**数据缓冲区*/
@property (nonatomic, strong) NSMutableData *dataBuffer;;//  读取客户端发送的数据,通过包头长度进行拆包
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {//  数据存入缓冲区[self.dataBuffer appendData:data];// 如果长度大于4个字节,表示有数据包。4字节为包头,存储包内数据长度while (self.dataBuffer.length >= 4) {NSInteger  datalength = 0;// 获取包头,并获取长度[[self.dataBuffer subdataWithRange:NSMakeRange(0, 4)] getBytes:&datalength length:sizeof(datalength)];//  判断缓存区内是否有包if (self.dataBuffer.length >= (datalength+4)) {// 获取去掉包头的数据NSData *realData = [[self.dataBuffer subdataWithRange:NSMakeRange(4, datalength)] copy];// 解析处理[self handleData:realData socket:sock];// 移除已经拆过的包self.dataBuffer = [NSMutableData dataWithData:[self.dataBuffer subdataWithRange:NSMakeRange(datalength+4, self.dataBuffer.length - (datalength+4))]];}else{break;}}[sock readDataWithTimeout:-1 tag:0];
}

自定义数据格式:在数据中放入开始、结束标识;解析时根据格式抓取数据,缺点是数据内不能含有开始或结束标识;
短连接传输,建立一次连接只传输一次数据就关闭;(不推荐)
注:以上代码仅提供粘包的解决思路,具体如何解包以及包头数据结构可以和服务器进行商定。

这篇关于iOS高级理论:CocoaAsyncSocket 介绍与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760672

相关文章

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma