GCDAsyncSoceket使用及其SSL/TLS的实现
关于GCDAsyncSocket:首先我们得知道GCDAsnyc是由第三方开发的一个苹果系统的socket库,功能强大,而且简单易用.当然关于它的使用方法国内也有不少博客对他进行介绍.但是由于各方面原因,笔者能在网上得到的关于GCDAsyncSocket的较好的文章真的很少,至少笔者是花了很多时间对其进行了解,学习,一点一点摸索,最后才能算得上相对了解其使用的.因此做了一些总结.写下这篇文章.
GCDAsyncSoceket是第三方的socket是使用库,功能强大,使用方便.也叫CocoaAsyncSocket(找资料的时候被这名字搞惨了).在github上有,贡献开发人员很多.在做即时通讯方面有较为出色的表现.当然,假如你做的是http协议的一般网络通信,可以使用另外一个库,AFnetworking(object-c)/AlamoFire(Swift).本文只对GCDAsyncSocket进行绍,AFNetworking请读者们自行了解.附上GitHub地址:
https://github.com/robbiehanson/CocoaAsyncSocket/
第一部分:关于GCDAsyncSocket的引入
关于GCDAsyncSocket的引入方式,笔者使用的是Pods方式将库引入到工程当中,主要是为了以后可以较好的对其进行更新,因为现在swift的尴尬(大家都懂的,一直都在更新,更新以后源码就需要改),导致后续的操作可能需要不断的对库文件进行更新,所以最好还是使用pods进行库的引入.这样下次更新的时候就只需要简单的一句"pods update"啦.当然自身代码当中的可能还需要小许改动.但是相信那对聪明读者来说,都是可以接受的.关于pods的使用我这里就不再赘述啦,大家可以去看看这篇文章:
http://blog.csdn.net/showhilllee/article/details/38398119
进入正题了哈:只需要在podfile中添加这两句
use_frameworks!
#假如你是针对IOS8+或者swift进行开发的话,需要加这句话
pod 'CocoaAsyncSocket'
然后"pod install"一下,就可以啦.你会看到在项目文件夹下会有pods文件夹,里面有你pod进项目的第三方库文件.同时还会生成一个podfile.lock文件.引入到程序中的方法如下:
Object-C:
//使用ios8+框架
@import CocoaAsyncSocket;
// 不使用Framwork或ios7-
#import "GCDAsyncSocket.h" //用于TCP
#import "GCDAsyncUdpSocket.h" //用于UDP
Swift:
import CocoaAsyncSocket
第二部分:GCDAsyncSoceket的基本使用:
前面已经把库引入到我们的文件当中了,下面介绍一下关于GCDASyncSocket的使用:开始之前先吐槽一下.笔者前期使用的是swift开发的,刚刚学了两个礼拜,没多久就发现实在是乏力,大部分主流的库开始都是Object-c开发的,swift虽然用起来比较爽,可读性比较强,但是毕竟起步晚,网上好多资料都还是Object-C的.所以对于笔者这种没有VPN的屌丝开发者来说是非常痛苦的.加上笔者本身C功底不是很扎实,所以导致花了不少时间去探索.笔者使用到的主要是TCP通讯比较多,UDP没有做介绍,不过相信读者看完这篇文章以后也会自行领悟其中的方法的.所以,假如想单纯靠swift进行IOS开发的话,笔者是不建议的,一个是资料少,一个是siwft还处于不稳定状态,总是维护,完全使用swift开发无疑会增加后期维护的成本的,所以还是建议大家还是学学ObjectC比较好,通过swift与Object-C的配合使用进行IOS的开发.
其实GCDAsyncSocket的使用还是不难的,笔者因为是菜鸟,因为深受晚上的水文章的迫害而被迫写下这篇个人的使用总结,所以如果有大神看到千万不要吐槽我.我只是希望给自己做个总结,以及跟正在入门路上的鸟友们分享下而已.
整体来说,其实只需要编写一个类继承我们的GCDAsyncSocket中的一个协议:GCDAsyncSoceketDelegate有兴趣的读者可以"command+鼠标左键"进去该协议的源码看看里面的各个方法的使用说明,英文不好的读者的话就算了...当然只是靠继承协议是不够的,还需要配合GCDAsyncSocket自身的函数才可以的.现在开始介绍下GCDAsyncSocket,由于GCDAsyncSocket与GCDAsyncSocketDelegate的结合使用还是挺紧密的,这里我们就两个一起介绍.
1.GCDAsyncSocket的创建:
通过commond+鼠标左键,在源码当中我们可以看到有好几个init的函数的.笔者使用的是两个参数的那个,一个参数是队列,一个是代理,因为这个是一个异步socket库,当然是需要队列的啦,然后它里面的代理方法实在是太好用了,所以这里就毫不犹豫的使用了这个.
let socketClient = GCDAsyncSocket(delegate,queue);
//当中的两个参数,delegate为继承了GCDAsyncSocketDelegate的类对象,queue就是队列啦.关于协议的继承的概念大家可以去百度下,这里就不赘述了.网上讲的都挺全的,队列的话笔者这里推荐一篇文章给大家,笔者刚刚开始IOS多线程的时候,就是看的这篇文章的链接:
http://www.cocoachina.com/ios/20150731/12819.html?sukey=fc78a68049a14bb23cd17add213504a0d0822cd271a04ca78928acf7355b332f2113c1ae97d12a2ab9f0c488348482c3
2.GCDAsyncSocket的socket连接函数:
对于GCDAsyncSocket来说,发起连接还是相对比较的方便的,可以使用Host也可以使用url方式,大家这里可以直接看看GCDAsyncSocket的源码了解下,怎么看?command+鼠标左键,你懂的.这里举个host的例子:
socketClient!.connectToHost("你的服务器域名或者IP",onPOrt:portServer,withTimeOut:time)
//参数其实还算是挺明了的吧,param1是服务器地址啦,param2是服务器端口啦,param3是超时时间/的设置啦
3.重写GCDAsyncSocketDelegate的didConnectToHost函数.有些读者可能会问,哪里重写,怎么我的编译器不提示这个函数...当然是在继承了GCDAsyncSocketDelegate协议的类那里去重写呀.
eg:
func socket(sock:GCDAsyncSocket!,didiCOnnecteToHost host:String!, port:UInt16){
//你的处理函数
}
//这个函数的意思相信大家大概能猜到,就是连接上服务器以后的回调函数,当设备连接上服务器以后,回调此函数.我们可以在这里进行自己想要的操作,比如可以在确认连接上服务器以后,再进行数据的发送,或者是我们SSL的认证(这部分我们后面会给大家介绍).
4.既然前面有连接上的,当然也是会有断开的啦.socketDidDisconnect函数.
eg:
func socketDidDisconnect(sock:GCDAsyncSocket!,withError err:NSError!){
//你的处理函数
}
//此函数跟3的函数类似,就是我们的设备跟服务器断开,或者是连接失败的回调函数,我们可以在这个函数里面进行重连等操作,具体的当然是看我们做项目的时候的需求啦.当然,这个函数也是在GCDAsyncSocketDelegate协议里面的.
5.连接上服务器之后,我们就要开始数据的收发了对吧,先来看看发送.GCDAsyncSocket中有一个writeStream跟writeData函数,笔者用的是writedata,因为我比较熟悉这个,想前面提的,笔者学了也没多久,到写这个文章位置还不到一个月.过程中把swift跟Object-c那些都看了,加上熟悉开发工具,环境的时间,其实入门没几天.
eg:
socketClient?.writeData(data,withTimeOut:time,tag:num)
//param1是需要发送的数据,数据类型是NSData,String怎么转NSData?不懂的读者百度下吧,直接贴出来就没意思了,还是读者自己多去了解下比较好,param2是发送超时时间,param3是个值得注意的东西,这个tag就是我们给这次socket通讯添加的一个标志,它关系到我们后面几个函数的使用,等下大家如果还是看不懂,可以看看上下文的tag,对比,或者自己亲身试验下.
6.在发送数据之后,假如成功发送完毕,会调用GCDAsyncSocketDelegate协议中的一个函数
didiWriteDataWithTag.大家注意,这里有一个Tag,就是5当中提高的tag
eg:
func socket(sock:GCDAsyncSocket!,didWriteDataWithTag tag:Int){
//你的处理函数
}
//此处的tag为前面发送的时候所定义的tag.根据这个tag我们可以清晰的知道,什么消息给我们成功发送到服务器上了.
7.说完发送就到了接受啦,在GCDAsyncSocket当中有多个read函数,Command+鼠标左键或输入GCDAsyncSocket之后ESC就看以看到,包括readDataWithData等,这里介绍其中的一个笔者使用比较多的,readDataWithTimeOut,名字上看,无疑就是设置规定时间内读取socket中的数据
eg:
socketClient.readDataWithTimeOut(time,tag:num)
//param1为超时时间设置,单位为S,tag跟上文提到的tag功能一样.注意的是这里的时间可以为负数,假如是负数的话,意思就是不管时间,一直读.
8.同writeData函数一样,readDataWithTimeOut函数,在遇到各种情况也会调用不同的函数,当socket在readDataWithTimeOut规定的时间以内完成了数据的读取之后,将会调用到协议中的didReadData函数.
eg:
func socket(sock:GCDAsyncSocket!,didReadData data:NSData!,withTag tag:int){
//你的处理函数
}
//其实到这里大家应该大致了解其tag的作用了,无疑是为了标记不同的数据通信的.该函数是socket读取数据后的回调函数,data就是读取到的数据.到这里大家应该也大致明白了GCDAsyncSocket与GCDAsyncSocketDelegate之间的联系,无疑GCDAsyncSocket是对GCDAsyncSocke连接以及数据发送的过程的监听,回调,过程中繁琐的各种分成处理,都已经替我们完成,我们只需要在该协议类上进行操作就可以很方便的完成socket通讯.以及进行我们对应操作处理.
9.前面8所提到的didReadData是在超时前完成对数据的读取,那么超时了呢,在GCDAsyncSocketDelegate协议里面有一个回调shouldTimeoutReadWithTag,这个函数是在我们GCDAsyncSocket读取数据的时候,假如是超时了,将会回调该函数.
eg:
func socket(sock:GCDAsyncSocket!,shouldTimeoutReadWithTag tag:Int,elapsedNSTimeInterval,
byteDoneLength:UInt) -> NSTimeInterval{
//你的处理函数
return time
}
//这里我们可以看到有一个时间的返回值,单位是s,其主要是表示在超时的时候,我们是否需要返回一个时间,让socket继续去读取服务器的数据.假如是负数的话,那么就是不再继续读取数据.
10.前面提到的是超时的情况,那么我们可以对读取时间以内去获取我们的socket的数据读取情况吗?答案是肯定的,GCDAsyncSoceketDelegate当中有一个回调didReadPartialDataOfLength,这个函数就是对读取过程监听的回调,写数据的时候有一个回调didWriteParatilDataOfLength,有兴趣的读者可以自行去探讨,可以对数据读写过程进行相应的处理,比如对UI的更新,最常见的就是诸如进度条之类的进度的更新.
11.最后就是我们的SSL通讯啦,这里的话笔者在学习的时候,主要是为了实现SSL/TLS的双向认证的,看过GCDAsyncSocket的源码的读者应该知道,在GCDAsyncSocket中有一个startTLS的函数,我们只需要设置调用这个函数就可以了,细心的读者在GCDAsyncSocket中应该也可以看到GCDAsyncSocket创作团队对他们这个作品的描述中知道,只需要调用一个函数就可以完成SSL/TLS的操作,当然我们继续深入到GCDAsyncSocket源码去看看,其实它的实质也是对IOS的SSL/TLS设置的封装.许多的参数设置意义都可以从xcode的Preference中去了解到的.这里需要提到我们之前所提到的一个问题,就是现在大部分的一个IOS开发库的资料都是基于Object-C的,这也是现在Swift的一个尴尬期的一个体现,所以笔者在学习这块的时候也是花了九牛二虎之力,本身就对SSL这块不是特别在行,所以查找资料啥的也是花了不少时间,最后实在没法,只能灰溜溜的回去看Object-C,因为关于IOS的sslSocket的资料本来就少,然后是使用GCDAsyncSocket的,还是swift语言的就更加少了.所以还是建议那些跟笔者当初那样有幻想所有东西的开发纯用swift的读者们,还是回去看看Object-C比较好.当然假如你够牛,这都不是问题.笔者是参考了CSDN上一片关于IOS的sslSocket的思路的文章得到启发的链接如下:
http://blog.csdn.net/fanyiyao980404514/article/details/44859783
同时附上一个封装自己的socket库的文章分析地址:
http://blog.csdn.net/jueyi1127/article/details/51003147
当然,这两个文章都是以Object-C为主的,但是这并没有什么关系,Swift可以跟Object-C互相调用呀.然后由于源码不少,这部分就不贴上来了,笔者也是根据CSDN上述文章当中的Object-C中得到启发,写了自己的库的,相信聪明的笔者们应该可以自行完成,这里就说说当中部分参数的设置.双向认证的话,需要公钥也需要私钥,两个文件,大家不要搞错了,私钥是SecIdentityRef,公钥是SecCertificateRef,思路大致就是将私钥文件跟公钥文件读取出来,然后合成SecIDentityRef对象以及SecCetificateRef对象,再放到设置字典里(读者看看CSDN的那个文章就知道这个设置需要创建的字典了)然后调用startTLS函数就可以了,startTLS函数的参数就是这个字典.最后还有两点需要说明的.一个就是在苹果中,一般是不允许自签名证书的,假如我们需要用自签名证书做SSL通讯的话,这里有几个参数需要设置的,一个是GCDAsyncSOcketUseCFStreamForTLS,这个参数设置为true,还有一个就是
KCFStreamSSLValidatesCertificateChain,这个参数需要设置为false,假如有看过苹果SecurityReference的读者可能看过当中的描述,使用SSL需要将其改成stream流的通讯方式.还有一个就是,苹果对SSL的安全性的限制比较强,有传言说非官方CA机构颁发的证书不允许放到APPStore当中,当然笔者刚刚开始学习,这个可能需要以后上传的时候再进行证实才可以,这个问题的话,我们后续更新的时候再说吧.
最后简单的提几个Socket通讯中我们可能会无可避免的遇到JSON的解析,字符串处理等问题,这里针对
这两块谈谈笔者最近做项目的时候使用到一些东西.
首先当然就是文章题目当中的SwiftyJson啦,这个也是在GitHub发布的一个对JSON解析的库,名字里我们就知道这是一个用Swift写的库了.IOS自带的笔者在这里不谈论了,有兴趣的读者可以执行百度下.SwiftyJSON的gitHub地址:
https://github.com/lingoer/SwiftyJSON
下载完成以后,我们只需要将两个文件复制到工程目录下,导入就可以了这两个文件就是SwiftyJSON.h和SwiftyJSON.swift 有读者可能会有疑问,为什么不像GCDAsyncSocket那样使用pods.笔者在写这篇文章的时候SwiftyJSON主页里还没有提到支持pods的,笔者写这篇文章时间为:2016/04/09.当然不排除是笔者资质愚钝,没有找到对应方法,查阅SwiftyJSON的源码之后,笔者使用该库的方法如下:
eg:
let json = JSON.parse(stringData) //stringData为需要转换成JSON的字符串变量
let data = json["data"]
//获取data标签下的数据可能是JSON也可能是字符
SwiftyJSON的使用大致就是这样,其他具体的比如JSON数组的使用,读者们查阅下相关资料应该就可以懂得其使用方法,这里就不再赘述了.另外我们在处理数据字符串可能会遇到字符替换等问题,这里笔者推荐一个函数,
stirng.stringByReplaceingOcuurencesOfString(parma1,withString:param2)
在这个函数中,param1是我们需要换成什么字符,而param2当然就是我们需要替换的字符啦.有时候可能需要去掉字符串总结的空格,或者说是压缩,这里有一个自带的函数
let whiteSpace = NSCharacterset.whitespaceAndNewLineCharacterSet
String.stringByTrimingCharactersInSet(whiteSpace)
好啦,假如读者看到这里,相信也花了不少时间了,谢谢耐心看完.当然笔者是个菜鸟,希望大神不要吐槽.文章中如果有错误的,需要更正的地方希望读者可以发邮件给笔者,笔者好及时更新,免得误人子弟,让部分读者走了弯路就罪过了,笔者也是受过现在网络信息泛滥的痛苦的,懂得当中的厉害关系,笔者邮箱790707677@qq.com .欢迎大神指导交流.也欢迎各位鸟友一起交流.有需要源码的也可以找我.
2017-12-11
关于SSLSocket单向认证,有需求的读者可以参考以下的博文:
https://stackoverflow.com/questions/36050743/gcdasyncsocket-starttls-does-not-work
需要添加如下设置:
[settings setObject:[NSNumber numberWithBool:YES]
forKey:GCDAsyncSocketManuallyEvaluateTrust];