原文地址:iOS 基于GCDAsyncSocket快速开发Socket通信 · 宫城
以及Socket重构设计:iOS Socket重构设计
看到留言区很多人都在使用中遇到问题,为了方便大家一起学习交流,我建了一个微信群,可以通过关注我的公众号,点击菜单栏【加群交流】,大家一起来交流
GCDAsyncSocket是CocoaAsyncSocket第三方库中的其中一个类,本文介绍的就是基于这一个类来做快速的socket通信开发
这个是我对GCDAsyncSocket的一层封装调用,它包含了建连、断开、重连、心跳、自定义请求
GitHub - Yuzeyang/GCDAsyncSocketManager
好用的话,请帮我star、fork一下喔~
首先,介绍一下CocoaAsyncSocket第三方库的用途
CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for Mac and iOS.
翻译成:
CocoaAsyncSocket为Mac和iOS提供了易于使用且强大的异步通信库
在Podfile文件中,只要加上这句话就可以使用了
pod'CocoaAsyncSocket', '7.4.1'
简单的Socket通信包括了建连、断开连接、发送socket业务请求、重连这四个基本功能
下面,我就按照这个四个基本功能来讲一下,怎么来使用CocoaAsyncSocket中GCDAsyncSocket这个类来开发Socket通信
首先,Socket在第一步时,需要建连才能开始通信
在GCDAsyncSocket中提供了四种初始化的方法
- (id)init;
- (id)initWithSocketQueue:(dispatch_queue_t)sq;
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq;
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
@property(atomic,weak,readwrite)id delegate;
@property(atomic,assign,readwrite)dispatch_queue_t delegateQueue;
- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
sq是socket的线程,这个是可选的设置,如果你写null,GCDAsyncSocket内部会帮你创建一个它自己的socket线程,如果你要自己提供一个socket线程的话,千万不要提供一个并发线程,在频繁socket通信过程中,可能会阻塞掉,个人建议是不用创建
aDelegate就是socket的代理
dq是delegate的线程
必须要需要设置socket的代理以及代理的线程,否则socket的回调你压根儿就不知道了,
比如:
self.socket= [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
接着,在设置代理之后,你需要尝试连接到相应的地址来确定你的socket是否能连通了
- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError**)errPtr;
host是主机地址,port是端口号
如果建连成功之后,会收到socket成功的回调,在成功里面你可以做你需要做的一些事情,我这边的话,是做了心跳的处理
- (void)socket:(GCDAsyncSocket*)sock didConnectToHost:(NSString*)host port:(UInt16)port
如果建连失败了,会收到失败的回调,我这边在失败里面做了重连的操作
- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err
重连操作其实比较简单,只需要再调用一次建连请求,我这边设置的重连规则是重连次数为5次,每次的时间间隔为2的n次方,超过次数之后,就不再去重连了
- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err {
_status= -1;
if(_reconnection_time>=0 && _reconnection_time <= kMaxReconnection_time) {
[_timer invalidate];
_timer=nil;
int time =pow(2,_reconnection_time);
_timer= [NSTimer scheduledTimerWithTimeInterval:time target:selfselector:@selector(reconnection) userInfo:nil repeats:NO];
_reconnection_time++;
NSLog(@"socket did reconnection,after %ds try again",time);
}else{
_reconnection_time=0;
NSLog(@"socketDidDisconnect:%p withError: %@", sock, err);
}
}
这里我用status来标记socket的连接状态
那么socket已经建连了,该怎么发起socket通信呢?
你需要和后端开发人员商定好socket协议格式,比如:
[NSString stringWithFormat:@"{\"version\":%d,\"reqType\":%d,\"body\":\"%@\"}\r\n",PROTOCOL_VERSION,reqType,reqBody];
中间应该大家都能看得懂,那为什么后面需要加上\r\n呢?
这个\r\n是socket消息的边界符,是为了防止发生消息黏连,没有\r\n的话,可能由于某种原因,后端会收到两条socket请求,但是后端不知道怎么拆分这两个请求
同理,在收到socket请求回调时,也会根据这个边界符去拆分
那为什么要用\r\n呢?
而且GCDAsyncSocket不支持自定义边界符,它提供了四种边界符供你使用\r\n、\r、\n、空字符串
在拼装好socket请求之后,你需要调用GCDAsyncSocket的写方法,来发送请求,然后在写完成之后你会收到写的回调
[self.socket writeData:requestData withTimeout:-1 tag:0];
timeout是超时时间,这个根据实际的需要去设置
这个是写的回调
- (void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag;
在写之后,需要再调用读方法,这样才能收到你发出请求后从服务器那边收到的数据
[self.socketreadDataToData:[GCDAsyncSocket CRLFData] withTimeout:10 maxLength:50000 tag:0];
[GCDAsyncSocket CRLFData]这里是设置边界符,maxLength是设置你收到的请求数据内容的最大值
在读回调里面,你可以根据不同业务来执行不同的操作
- (void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag;
最后一个则是断开连接,这个只需要调用
[self.socket disconnect];
ok,这样的话,最简单基础的socket通信,你已经大致能完成了~
----------------------------------------------------
2016.4.26更新
有人提问说在网络环境以及其他因素下,很有可能会造成客户端或者后端没有接收到回调或者请求,那该怎么办呢?
我们需要加上消息回执的处理
客户端发出请求的时候,可以将该请求放到存到数组里面,等到后端的相应回调,如果该请求超时,说明后端没有接收到我们的请求,我们可以将该请求重新发送
客户端接收请求的时候,后端将数据发给客户端,客户端需要增加回执处理,告诉后端,客户端接收到数据了,如果后端没接收到,也重新推一遍数据,客户端和后端双向保护解决丢失问题
2016.8.5更新
有些时候,不能定位是否是后端问题还是客户端/SDK问题的时候,可以用命令行抓一下socket包看看(用Charles只能抓http和https包)
命令行如下:
sudo tcpdump -i any -n -X port 7070
Tip:7070端口号请根据实际的调试端口号修改
效果如下:
红色部分就是socket包的内容了如果有什么意见或者建议,欢迎大家留言,知识是需要交流的,我相信会有更好更简洁的方法来处理
这个是我的个人微信公众号,会不定期发表一些iOS开发文章以及疑难问题和我在阅读技术和非技术书籍的一些感悟,欢迎大家订阅!
宫城Dev
网友评论
2017-10-18 11:08:58.683046+0800 GCDAsyncSocketManagerDemo[3009:92968] socketDidDisconnect:0x7fac79d0d0c0 withError: Error Domain=GCDAsyncSocketErrorDomain Code=4 "Read operation timed out" UserInfo={NSLocalizedDescription=Read operation timed out}
有这个可以实时监听后台数据,实时获取后台数据吗
@interface ViewController : NSViewController
{
@private
GCDAsyncSocket *asyncSocket1;
GCDAsyncSocket *asyncSocket2;
}
这是第一个连接 :
dispatch_queue_t mainQueue = dispatch_get_main_queue();
asyncSocket1 = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];
{
NSString *host = @"192.168.1.101";
uint16_t port = 8080;
NSLog(@"Connecting to \"%@\" on port %hu...", host, port);
NSError *error = nil;
if (![asyncSocket1 connectToHost:host onPort:port error:&error])
{
NSLog(@"Error connecting: %@", error);
}
}
第二个连接如下
asyncSocket2 = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];
{
NSString *host = @"192.168.1.102";
uint16_t port = 8080;
NSLog(@"Connecting to \"%@\" on port %hu...", host, port);
NSError *error = nil;
if (![asyncSocket connectToHost:host onPort:port error:&error])
{
NSLog(@"Error connecting: %@", error);
}
}
现在我想连的服务器不确定几个,我该怎么建立n个socket连接呢?即一个 GCDAsyncSocket client 实例如何建立多个TCP连接 ?
什么是连接鉴权呀,什么时候出发心跳呢
出现这些信息什么意思
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type PKTAP (Packet Tap), capture size 262144 bytes
請問 我在使用上發生一個問題想請教你
環境是 iPad 連線 樹莓派,區域網路連線,iPad是client端, 樹莓派是Server端
如果在運行期間,忽然把樹莓派網路拔掉,我iPad這邊是沒有辦法馬上知道斷線
要等待一段時間才能知道斷線了
想說你是不是知道GCDAsyncSocket有什麼方法可以知道呢?
但是有一个问题,所有的都正常连接,但是在接收数据的时候,如果一个请求服务器返回大量数据会分包返回给我,但是我只能收到一部分,5条只能收到3-4条,就收不到了 .我们这边服务器返回的都是结构体数据,每次也都打印了 大概就只能接受5000字节的数据,大于的就接受不到了 但是服务器确实给了!求教
[NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:&error];只要把options置0,得到的data通过UTF-8转字符串就是无格式的jsonStr;
直接用""(空)替换" "(空格)会替换掉所有的,会出事的
我这里有个需求类似于给服务端发送更新包,简单来说就是连续发送很多个包,但是我的循环不知道哪里有问题,总是收不到返回的信息,有人说是我没有开辟线程池什么的,叫我加上runloop,想问您一下,如果要是连续发送很多包,这个循环该怎么弄啊
2016-09-03 11:37:22.050 GCDAsyncSocketManagerDemo[1694:48314] socketDidDisconnect:0x7f8fd3667880 withError: Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused" UserInfo={NSLocalizedDescription=Connection refused, NSLocalizedFailureReason=Error in connect() function}
2016-09-03 11:37:22.086 GCDAsyncSocketManagerDemo[1694:48314] socketDidDisconnect:0x7f8fd3677390 withError: Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused" UserInfo={NSLocalizedDescription=Connection refused, NSLocalizedFailureReason=Error in connect() function}
2016-09-03 11:37:23.055 GCDAsyncSocketManagerDemo[1694:48314] socketDidDisconnect:0x7f8fd3677390 withError: Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused" UserInfo={NSLocalizedDescription=Connection refused, NSLocalizedFailureReason=Error in connect() function}
2016-09-03 11:37:25.477 GCDAsyncSocketManagerDemo[1694:48314] socket 未连通
2016-09-03 11:37:26.075 GCDAsyncSocketManagerDemo[1694:48314] socket 未连通
2016-09-03 11:37:26.275 GCDAsyncSocketManagerDemo[1694:48314] socket 未连通
2016-09-03 11:37:26.473 GCDAsyncSocketManagerDemo[1694:48314] socket 未连通
2016-09-03 11:38:09.386 GCDAsyncSocketManagerDemo[1694:48314] socketDidDisconnect:0x7f8fd37125e0 withError: Error Domain=GCDAsyncSocketErrorDomain Code=3 "Attempt to connect to host timed out" UserInfo={NSLocalizedDescription=Attempt to connect to host timed out}
是什么原因呢?帮忙指点指点。
楼主 这个问题怎么解决啊?
我已经通讯连接了,但是为什么发送第二次数据是可以的,却接收不了第二次数据啊?
好忧伤啊,能加个qq详聊吗?或者我把demo发你帮我看下啊,谢谢啦
谢谢啦
刚入iOS的坑,这里关于初始化时候的队列问题没有弄明白,劳烦楼主帮忙看看。
就是初始化时候我们一般这么做 [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
但如果我将这个处理封装在一个库中,再由另一个程序去调用,那么这里的initWithDelegate和delegateQueue该如何处理,我才可以在库中就能够直接使用委托来获得回调结果,比如我在封装的库中去调用 -(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port,这个是没法被触发到的
应该如何初始化委托和队列,才能够直接在库中去触发回调呢?
_reconnection_time和kMaxReconnection_time是什么的属性 是不是设置了初始值