iOS開發(fā)之Socket實現(xiàn)HTTPS GET請求通過Body傳參

這篇文章主要介紹以下幾個技術(shù)點:
  • 使用CocoaAsyncSocket進(jìn)行socket連接
  • - (void)startTLS:(NSDictionary *)tlsSettings這個字典內(nèi)容相關(guān)設(shè)置,(https的設(shè)置)
  • HTTP請求頭和請求體的自定義設(shè)置(其實就是拼接的一長串帶指定格式的字符串轉(zhuǎn)換成data)
  • 對網(wǎng)絡(luò)請求參數(shù)的暫存高蜂,應(yīng)對多條網(wǎng)絡(luò)請求同時發(fā)生的情況
具體實現(xiàn):

Cocoa框架里,無論是用OS層基于 C 的BSD socket還是用對BSD socket進(jìn)行了輕量級的封裝的CFNetwork创南,對于我這種C語言不及格的同學(xué),那都是極其痛苦的體驗志电,因此我們就用CocoaAsyncSocket來進(jìn)行socket連接驯鳖,完全OC風(fēng)格,非常愉快竭望。

  • 你需要用CocoaPods導(dǎo)入這個庫:
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/Artsy/Specs.git'

platform :ios, ‘9.0’

target ‘你的項目名稱’ do

pod 'CocoaAsyncSocket'

end
  • 而后我們新建一個DCSocketManager類來管理這個類的一些代理方法,并生成一個本類的單例裕菠,單例就不再寫了(自己寫吧網(wǎng)上很多):

.h文件里沒有什么內(nèi)容只是暴露了一個供外界調(diào)用的請求接口咬清,后面介紹,主要是.m文件里的擴(kuò)展屬性:

@interface DCSocketManager()  <GCDAsyncSocketDelegate>
{
    NSString       *_serverHost;//IP或者域名
    int             _serverPort;//端口奴潘,https一般是443
    GCDAsyncSocket *_asyncSocket;//一個全局的對象
}
@property (nonatomic, strong) NSMutableData     *sendData;//最終拼接好的需要發(fā)送出去的數(shù)據(jù)
@property (nonatomic, copy)   NSString          *uriString;//具體請求哪個接口旧烧,比如https://xxx.xxxxx.com/verificationCode里的verificationCode
@property (nonatomic, strong) NSDictionary      *paramters;//Body里面需要傳遞的參數(shù)
@property (nonatomic, copy)   CompletionHandler  completeHandler;//收到返回數(shù)據(jù)后的回調(diào)Block
@property (nonatomic, strong) NSMutableArray *dcNetArr;//網(wǎng)絡(luò)請求參數(shù)的暫存數(shù)組,后面會用到
@end

GCDAsyncSocketDelegate代理的實現(xiàn):

@implementation DCSocketManager

Singleton_Implementation(DCSocketManager)//單例

- (instancetype)init {//對socket進(jìn)行初始化
    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
{//斷開連接時會調(diào)用
    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
{//連接上服務(wù)器時會調(diào)用
    [self doTLSConnect:sock];//連接上服務(wù)器就要進(jìn)行tls認(rèn)證画髓,后面介紹掘剪,如果只是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];//往服務(wù)器傳遞請求數(shù)據(jù),之后會介紹self.sendData的拼接
    [sock readDataWithTimeout:-1 tag:0];//馬上讀取一下
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{//讀取到返回數(shù)據(jù)時會調(diào)用
    NSLog(@"didReadData length: %lu, tag: %ld", (unsigned long)data.length, tag);
    if (nil != self.completeHandler) {//如果請求成功奈虾,讀取到服務(wù)器返回的data數(shù)據(jù)一般是一串字符串夺谁,需要根據(jù)返回數(shù)據(jù)格式做相應(yīng)處理解析出來
        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) {//如果返回的數(shù)據(jù)中不包含以上符號,會崩潰
            sub = [string substringWithRange:NSMakeRange(start.location, end.location-start.location+1)];//這就是服務(wù)器返回的body體里的數(shù)據(jù)
            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
{//成功發(fā)送數(shù)據(jù)時會調(diào)用
    NSLog(@"didWriteDataWithTag: %ld", tag);
    [sock readDataWithTimeout:-1 tag:tag];
}

- (void)socketDidSecure:(GCDAsyncSocket *)sock 
{//https安全認(rèn)證成功時會調(diào)用
    NSLog(@"SSL握手成功肉微,安全通信已經(jīng)建立連接!");
}
@end

這里重點說一下sendData這個屬性的拼接(很重要匾鸥,這里的格式?jīng)Q定了你發(fā)送的請求數(shù)據(jù)是否被服務(wù)器認(rèn)可,并給你返回信息碉纳,相當(dāng)于NSURLRequest的作用,其實就是拼接一個http協(xié)議):

- (NSMutableData *)sendData {
    NSMutableData *packetData = [[NSMutableData alloc] init];
    NSData *crlfData = [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding];//回車換行是http協(xié)議中每個字段的分隔符

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

    [packetData appendData:[@"DCVer: 1" dataUsingEncoding:NSUTF8StringEncoding]];//拼接的請求頭字段勿负,這個鍵值對和服務(wù)器協(xié)商內(nèi)容,一般不止一個
    [packetData appendData:crlfData];
    [packetData appendData:[@"DCAid: test" dataUsingEncoding:NSUTF8StringEncoding]];//拼接的請求頭字段劳曹,這個鍵值對和服務(wù)器協(xié)商內(nèi)容奴愉,一般不止一個
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Content-Type: application/json; charset=utf-8" dataUsingEncoding:NSUTF8StringEncoding]];//發(fā)送數(shù)據(jù)的格式
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"User-Agent: GCDAsyncSocket8.0" dataUsingEncoding:NSUTF8StringEncoding]];//代理類型,用來識別用戶的操作系統(tǒng)及版本等信息铁孵,這里我隨便填的锭硼,一般情況沒什么用
    [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];//生成請求體的內(nèi)容
    [packetData appendData:[[NSString stringWithFormat:@"Content-Length: %ld", bodyString.length] dataUsingEncoding:NSUTF8StringEncoding]];//說明請求體內(nèi)容的長度
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Connection:close" dataUsingEncoding:NSUTF8StringEncoding]];
    [packetData appendData:crlfData];
    [packetData appendData:crlfData];//注意這里請求頭拼接完成要加兩個回車換行
  //以上http頭信息就拼接完成,下面繼續(xù)拼接上body信息
    NSString *encodeBodyStr = [NSString stringWithFormat:@"%@\r\n\r\n", bodyString];//請求體最后也要加上兩個回車換行說明數(shù)據(jù)已經(jīng)發(fā)送完畢
    [packetData appendData:[encodeBodyStr dataUsingEncoding:NSUTF8StringEncoding]];
    return packetData;
}

以上就是建立HTTP連接收發(fā)數(shù)據(jù)的全部內(nèi)容库菲,如果不需要支持https的話,這個GET請求已經(jīng)可以完成志膀,下面介紹進(jìn)行https連接需要進(jìn)行的設(shè)置(在.m文件里實現(xiàn)):(上面提到的[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"]];//已經(jīng)支持https的網(wǎng)站會有CA證書熙宇,給服務(wù)器要一個導(dǎo)出的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];//最后調(diào)用一下GCDAsyncSocket這個方法進(jìn)行ssl設(shè)置就Ok了
}

至此發(fā)送HTTPS GET請求并且用body傳遞參數(shù)就實現(xiàn)了鳖擒,是不是很神奇。下面封裝一個對外調(diào)用的接口(在.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類用來暫存網(wǎng)絡(luò)請求的參數(shù)烫止,它是這樣子滴:**

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換成自己的,這里還有許多不完善的地方馆蠕,我只是實現(xiàn)了簡單的GET請求并暫存請求參數(shù)期升,至于你需要其他的功能自己加上就是了。

附一篇講GCDAsyncSocket的干貨文章互躬,非常值得一讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末播赁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吼渡,更是在濱河造成了極大的恐慌容为,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寺酪,死亡現(xiàn)場離奇詭異坎背,居然都是意外死亡,警方通過查閱死者的電腦和手機寄雀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門得滤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盒犹,你說我怎么就攤上這事懂更。” “怎么了阿趁?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵膜蛔,是天一觀的道長。 經(jīng)常有香客問我脖阵,道長皂股,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任命黔,我火速辦了婚禮呜呐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悍募。我一直安慰自己蘑辑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布坠宴。 她就那樣靜靜地躺著洋魂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上副砍,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天衔肢,我揣著相機與錄音,去河邊找鬼豁翎。 笑死角骤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的心剥。 我是一名探鬼主播邦尊,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼优烧!你這毒婦竟也來了蝉揍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤匙隔,失蹤者是張志新(化名)和其女友劉穎疑苫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纷责,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡捍掺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了再膳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挺勿。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喂柒,靈堂內(nèi)的尸體忽然破棺而出不瓶,到底是詐尸還是另有隱情,我是刑警寧澤灾杰,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布蚊丐,位于F島的核電站,受9級特大地震影響艳吠,放射性物質(zhì)發(fā)生泄漏麦备。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一昭娩、第九天 我趴在偏房一處隱蔽的房頂上張望凛篙。 院中可真熱鬧,春花似錦栏渺、人聲如沸呛梆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽填物。三九已至纹腌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滞磺,已是汗流浹背壶笼。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雁刷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓保礼,卻偏偏與公主長得像沛励,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炮障,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容