iOS开发之Socket实现HTTPS GET请求通过Body传参

这篇文章主要介绍以下几个技术点:
  • 使用CocoaAsyncSocket进行socket连接
  • - (void)startTLS:(NSDictionary *)tlsSettings这个字典内容相关设置,(https的设置)
  • HTTP请求头和请求体的自定义设置(其实就是拼接的一长串带指定格式的字符串转换成data)
  • 对网络请求参数的暂存,应对多条网络请求同时发生的情况
具体实现:

Cocoa框架里,无论是用OS层基于 C 的BSD socket还是用对BSD socket进行了轻量级的封装的CFNetwork,对于我这种C语言不及格的同学,那都是极其痛苦的体验,因此我们就用CocoaAsyncSocket来进行socket连接,完全OC风格,非常愉快。

  • 你需要用CocoaPods导入这个库:
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/Artsy/Specs.git'

platform :ios, ‘9.0’

target ‘你的项目名称’ do

pod 'CocoaAsyncSocket'

end
  • 而后我们新建一个DCSocketManager类来管理这个类的一些代理方法,并生成一个本类的单例,单例就不再写了(自己写吧网上很多):

.h文件里没有什么内容只是暴露了一个供外界调用的请求接口,后面介绍,主要是.m文件里的扩展属性:

@interface DCSocketManager()  <GCDAsyncSocketDelegate>
{
    NSString       *_serverHost;//IP或者域名
    int             _serverPort;//端口,https一般是443
    GCDAsyncSocket *_asyncSocket;//一个全局的对象
}
@property (nonatomic, strong) NSMutableData     *sendData;//最终拼接好的需要发送出去的数据
@property (nonatomic, copy)   NSString          *uriString;//具体请求哪个接口,比如https://xxx.xxxxx.com/verificationCode里的verificationCode
@property (nonatomic, strong) NSDictionary      *paramters;//Body里面需要传递的参数
@property (nonatomic, copy)   CompletionHandler  completeHandler;//收到返回数据后的回调Block
@property (nonatomic, strong) NSMutableArray *dcNetArr;//网络请求参数的暂存数组,后面会用到
@end

GCDAsyncSocketDelegate代理的实现:

@implementation DCSocketManager

Singleton_Implementation(DCSocketManager)//单例

- (instancetype)init {//对socket进行初始化
    if (self = [super init]) {
        _serverHost = @"xxx.xxxxxx.com";
        _serverPort = 443;
        _asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue() socketQueue:nil];
        _dcNetArr = [NSMutableArray arrayWithCapacity:20];
    }
    return self;
}

#pragma mark GCDAsyncSocketDelegate method

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{//断开连接时会调用
    NSLog(@"didDisconnect...");
    if (self.dcNetArr.count > 0) {
        [_asyncSocket connectToHost:_serverHost onPort:_serverPort error:nil];
    }
}

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{//连接上服务器时会调用
    [self doTLSConnect:sock];//连接上服务器就要进行tls认证,后面介绍,如果只是http连接就不需要这句
   NSLog(@"didConnectToHost: %@, port: %d", host, port);
    if (self.dcNetArr.count > 0) {
        DCNetCache *net = [self.dcNetArr firstObject];
        self.uriString = net.uri;
        self.paramters = net.params;
        self.completeHandler = net.completeHandler;
        [self.dcNetArr removeObjectAtIndex:0];
    }
    [sock writeData:self.sendData withTimeout:-1 tag:0];//往服务器传递请求数据,之后会介绍self.sendData的拼接
    [sock readDataWithTimeout:-1 tag:0];//马上读取一下
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{//读取到返回数据时会调用
    NSLog(@"didReadData length: %lu, tag: %ld", (unsigned long)data.length, tag);
    if (nil != self.completeHandler) {//如果请求成功,读取到服务器返回的data数据一般是一串字符串,需要根据返回数据格式做相应处理解析出来
        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        //NSLog(@"%@", string);
        NSRange start = [string rangeOfString:@"{"];
        NSRange end = [string rangeOfString:@"}\r\n"];
        NSString *sub;
        if (end.location != NSNotFound && start.location != NSNotFound) {//如果返回的数据中不包含以上符号,会崩溃
            sub = [string substringWithRange:NSMakeRange(start.location, end.location-start.location+1)];//这就是服务器返回的body体里的数据
            NSData *subData = [sub dataUsingEncoding:NSUTF8StringEncoding];;
            NSDictionary *subDic = [NSJSONSerialization JSONObjectWithData:subData options:0 error:nil];
            self.completeHandler(subDic);
        }
    }
    [sock readDataWithTimeout:-1 tag:0];
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{//成功发送数据时会调用
    NSLog(@"didWriteDataWithTag: %ld", tag);
    [sock readDataWithTimeout:-1 tag:tag];
}

- (void)socketDidSecure:(GCDAsyncSocket *)sock 
{//https安全认证成功时会调用
    NSLog(@"SSL握手成功,安全通信已经建立连接!");
}
@end

这里重点说一下sendData这个属性的拼接(很重要,这里的格式决定了你发送的请求数据是否被服务器认可,并给你返回信息,相当于NSURLRequest的作用,其实就是拼接一个http协议):

- (NSMutableData *)sendData {
    NSMutableData *packetData = [[NSMutableData alloc] init];
    NSData *crlfData = [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding];//回车换行是http协议中每个字段的分隔符

    [packetData appendData:[[NSString stringWithFormat:@"GET /%@ HTTP/1.1", self.uriString] dataUsingEncoding:NSUTF8StringEncoding]];//拼接的请求行
    [packetData appendData:crlfData];//每个字段后面都要跟一个回车换行

    [packetData appendData:[@"DCVer: 1" dataUsingEncoding:NSUTF8StringEncoding]];//拼接的请求头字段,这个键值对和服务器协商内容,一般不止一个
    [packetData appendData:crlfData];
    [packetData appendData:[@"DCAid: test" dataUsingEncoding:NSUTF8StringEncoding]];//拼接的请求头字段,这个键值对和服务器协商内容,一般不止一个
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Content-Type: application/json; charset=utf-8" dataUsingEncoding:NSUTF8StringEncoding]];//发送数据的格式
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"User-Agent: GCDAsyncSocket8.0" dataUsingEncoding:NSUTF8StringEncoding]];//代理类型,用来识别用户的操作系统及版本等信息,这里我随便填的,一般情况没什么用
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Host: xxx.xxxxxx.com" dataUsingEncoding:NSUTF8StringEncoding]];//IP或者域名
    [packetData appendData:crlfData];
    
    NSError *error;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:self.paramters
                                                       options:0
                                                         error:&error];
    NSString *bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding];//生成请求体的内容
    [packetData appendData:[[NSString stringWithFormat:@"Content-Length: %ld", bodyString.length] dataUsingEncoding:NSUTF8StringEncoding]];//说明请求体内容的长度
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Connection:close" dataUsingEncoding:NSUTF8StringEncoding]];
    [packetData appendData:crlfData];
    [packetData appendData:crlfData];//注意这里请求头拼接完成要加两个回车换行
  //以上http头信息就拼接完成,下面继续拼接上body信息
    NSString *encodeBodyStr = [NSString stringWithFormat:@"%@\r\n\r\n", bodyString];//请求体最后也要加上两个回车换行说明数据已经发送完毕
    [packetData appendData:[encodeBodyStr dataUsingEncoding:NSUTF8StringEncoding]];
    return packetData;
}

以上就是建立HTTP连接收发数据的全部内容,如果不需要支持https的话,这个GET请求已经可以完成,下面介绍进行https连接需要进行的设置(在.m文件里实现):(上面提到的[self doTLSConnect:sock]这个方法)

- (void)doTLSConnect:(GCDAsyncSocket *)sock {
    //HTTPS
    NSMutableDictionary *sslSettings = [[NSMutableDictionary alloc] init];
    NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"xxx.xxxxxxx.com" ofType:@"p12"]];//已经支持https的网站会有CA证书,给服务器要一个导出的p12格式证书
    CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(pkcs12data);
    CFStringRef password = CFSTR("xxxxxx");//这里填写上面p12文件的密码
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    
    OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
    CFRelease(options);
    CFRelease(password);
    
    if (securityError == errSecSuccess) {
        NSLog(@"Success opening p12 certificate.");
    }
    
    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
    SecIdentityRef myIdent = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    SecIdentityRef  certArray[1] = { myIdent };
    CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
    [sslSettings setObject:(id)CFBridgingRelease(myCerts) forKey:(NSString *)kCFStreamSSLCertificates];
    [sslSettings setObject:@"api.pandaworker.com" forKey:(NSString *)kCFStreamSSLPeerName];
    [sock startTLS:sslSettings];//最后调用一下GCDAsyncSocket这个方法进行ssl设置就Ok了
}

至此发送HTTPS GET请求并且用body传递参数就实现了,是不是很神奇。下面封装一个对外调用的接口(在.h文件中把这个接口暴露出去就行了):

- (void)getRequestUriName:(NSString *)uri Param:(NSDictionary *)params Complete:(CompletionHandler)handler{
    DCNetCache *net = [[DCNetCache alloc] initWithUri:uri Params:params CompleteHandler:handler];
    [self.dcNetCacheArr addObject:net];
    [_asyncSocket connectToHost:_serverHost onPort:_serverPort error:nil];
}

** 其中的DCNetCache类用来暂存网络请求的参数,它是这样子滴:**

typedef void(^CompletionHandler)(NSDictionary *response);
@interface DCNetCache : NSObject

@property (nonatomic, copy) NSString *uri;
@property (nonatomic, strong) NSDictionary *params;
@property (nonatomic, copy) CompletionHandler completeHandler;

- (instancetype)initWithUri:(NSString *)uri Params:(NSDictionary *)params CompleteHandler:(CompletionHandler)handler;

@end

@implementation DCNetCache

- (instancetype)initWithUri:(NSString *)uri Params:(NSDictionary *)params CompleteHandler:(CompletionHandler)handler{
    self = [super init];
    if (self) {
        _uri = uri;
        _params = params;
        _completeHandler = handler;
    }
    return self;
}
@end

这样子就大功告成了,注意把上面的host换成自己的,这里还有许多不完善的地方,我只是实现了简单的GET请求并暂存请求参数,至于你需要其他的功能自己加上就是了。

附一篇讲GCDAsyncSocket的干货文章,非常值得一读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,966评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,170评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,909评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,959评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,851评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,583评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,956评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,590评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,878评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,892评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,719评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,501评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,957评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,124评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,440评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,003评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,211评论 2 339

推荐阅读更多精彩内容