• iOS IM开发准备工作(四)CocoaAsyncSocket的使用


      上一篇乱说了一阵socket,这篇要说说怎么干活了。毕竟用的起来才行。

      我的项目里面使用的是CocoaAsyncSocket,这个是对CFSocket的封装。如果你觉得自己可以实现封装或者直接用原生的,我可以告诉你,很累;关键是等你弄出来,项目可能都要交了。这个库,支持TCP和UDP;有GCD和RunLoop两种选择。UDP相比TCP的话,可靠性低一点,一般用来传输视频,少个一两帧没有什么影响。这里我就说一下TCP的使用,当然为了发挥Apple设备的牛X性能,我用GCD。

        

    建立socket 单例

      先说一点,要保持长连接。我们需要建一个全局的单例。标准单例模式写法⬇️

    1 + (instancetype)ShareBaseClient {
    2     static SocketClient * iSocketCilent;
    3     static dispatch_once_t onceToken;
    4     dispatch_once(&onceToken, ^{
    5         iSocketCilent = [[SocketClient alloc]init];
    6     });
    7     return iSocketCilent;
    8 }
     1 #define USE_SECURE_CONNECTION    0 // 是否需要使用安全连接
     2 #define USE_CFSTREAM_FOR_TLS     0 // Use old-school CFStream style technique
     3 #define MANUALLY_EVALUATE_TRUST  1 // 是否需要人工验证
     4 
     5 
     6 - (id)init {
     7     self = [super init];
     8     // Start the socket stuff 最后的那个是你的要放在哪个线程里面操作 果断全局
     9     asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    10     NSError *error = nil;
    11     uint16_t port = DefalutPORT;// 默认的端口号
    12     // 连接是否成功 
    13     // WWW_HOST 连接的地址
    14     // 50秒 是我设置的 超时时间
    15     if (![asyncSocket connectToHost:WWW_HOST onPort:port withTimeout:50.0f error:&error]){
    16         
    17         NSLog(@"Unable to connect to due to invalid configuration: %@", error);
    18 
    19     }else{
    20         NSLog(@"Connecting to "%@" on port %hu...", WWW_HOST, port);
    21     }
    22     {
    23 #if USE_SECURE_CONNECTION
    24         
    25 #if USE_CFSTREAM_FOR_TLS
    26         {
    27             // Use old-school CFStream style technique
    28             
    29             NSDictionary *options = @{
    30                                       GCDAsyncSocketUseCFStreamForTLS : @(YES),
    31                                       GCDAsyncSocketSSLPeerName : CERT_HOST
    32                                       };
    33             
    34             DDLogVerbose(@"Requesting StartTLS with options:
    %@", options);
    35             [asyncSocket startTLS:options];
    36         }
    37 #elif MANUALLY_EVALUATE_TRUST
    38         {
    39             // Use socket:didReceiveTrust:completionHandler: delegate method for manual trust evaluation
    40             
    41             NSDictionary *options = @{
    42                                       GCDAsyncSocketManuallyEvaluateTrust : @(YES),
    43                                       GCDAsyncSocketSSLPeerName : CERT_HOST
    44                                       };
    45             
    46             DDLogVerbose(@"Requesting StartTLS with options:
    %@", options);
    47             [asyncSocket startTLS:options];
    48         }
    49 #else
    50         {
    51             // Use default trust evaluation, and provide basic security parameters
    52             
    53             NSDictionary *options = @{
    54                                       GCDAsyncSocketSSLPeerName : CERT_HOST
    55                                       };
    56             
    57             DDLogVerbose(@"Requesting StartTLS with options:
    %@", options);
    58             [asyncSocket startTLS:options];
    59         }
    60 #endif
    61 #endif
    62     }
    63     return self;
    64 }

     

    接受报文 

      如果你连接成功了,那你就可以开始收发报文了。我这里用的是delegate模式,所以我们要实现一下几个比较重要的代理:

      连接上的第一步,我们要准备读数据。我用一个简单的枚举标识了每次读取数据的区域,一般报文都是先读报头,再去解析正文的。所以连接上之后,要首先接受报头。

     1 #define READ_HEADER_LINE_BY_LINE  0
     2 
     3 typedef NS_ENUM(long, ReadTagType){ //读取数据的类型
     4     ReadTagTypehead = 1,// 报头
     5     ReadTagTypebody = 2 // 主体
     6 };
     7 
     8 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
     9 {
    10     NSLog(@"socket:didConnectToHost:%@ port:%hu", host, port);
    11 
    12     // 是否一行一行的读数据,我这里设置的是 0
    13 #if READ_HEADER_LINE_BY_LINE
    14     // Now we tell the socket to read the first line of the http response header.
    15     // As per the http protocol, we know each header line is terminated with a CRLF (carriage return, line feed).
    16     [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
    17     
    18 #else
    19     // Now we tell the socket to read the full header for the http response.
    20     // As per the http protocol, we know the header is terminated with two CRLF's (carriage return, line feed).
    21     // sizeof(protocol_head)  一个报文头的长度
    22     // ReadTagTypehead 先读的是报文的头的
    23     // -1代表没有超时时间
    24     [asyncSocket readDataToLength:sizeof(protocol_head) withTimeout:-1 tag:ReadTagTypehead];
    25     
    26 #endif
    27 }

      开始读取后,有数据的话会进入这个代理。

     1 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
     2     
     3     switch (tag) {
     4         case ReadTagTypehead:// 先 读取 head部分
     5         {
     6              // 拿到之后,我们处理一下这段头
     7              // 获得head里面的命令CMD 和 下一段正文的长度BodyLength
     8              // 把这些参数传到一个方法里面处理一下(万一是空头呢。)
     9             
    10             [self willReadBody:CMD size: BodyLength data:data];
    11         }
    12             break;
    13         case ReadTagTypebody:// 再 读取 body部分
    14         {
    15             // 加上这一段,我们取得了完整的数据了,可以给需要的地方发过去了
    16             [self haveReadData:data];
    17             // 然后我们去读下一个报文,还是先读报文头
    18             [asyncSocket readDataToLength:sizeof(protocol_head) withTimeout:-1 tag:ReadTagTypehead];
    19         }
    20             break;
    21         default:
    22             break;
    23     }
    24 }
    25 
    26 // 处理用的方法 下面两个是我写的私有方法 不是CocoaAsyncSocket的代理方法
    27 // 准备读取body的数据 CMD需要记录下来的
    28 - (void)willReadBody:(int)CMD size:(long)size data:(NSData *)data{
    29    gCMD = CMD;
    30     alldata = [[NSMutableData alloc]initWithData: data];
    31     if(size == 0) {// 如果是空头,就丢掉这段数据,继续读下一个头
    32         [self haveReadData:nil];
    33         [asyncSocket readDataToLength:sizeof(protocol_head) withTimeout:-1 tag:ReadTagTypehead];
    34     }else {// 如果不是的话,就去读下一段 报文的正文
    35         [asyncSocket readDataToLength:dataBufferSize withTimeout:-1 tag:ReadTagTypebody];
    36     }
    37 }
    38 
    39 // 读取完数据了 要回调代理
    40 - (void)haveReadData:(NSData *)data {
    41     // 把头和正文拼接起来 构成完整的数据
    42     if(data && data.length>0)
    43         [alldata appendData:data];
    44     
    45 //    NSLog(@"receiveData: %@",alldata);
    46     if(alldata.length>0) {
    47        // 这里去告诉 你需要处理数据的地方 让他去处理数据
    48     }
    49     alldata = nil;
    50 }

      到上面这一步的话,你的接受数据就算完成了。

    发送报文

      [asyncSocket writeData:data withTimeout:-1.0 tag:0]; 

      就一行,不用怀疑。把你的信息转为NSData后 调用这个方法就好了。成功的话,会进入这个代理。

      - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; 

     

    socket 断开

      当然,socket也有断开的时候,所以你需要处理这个代理。这里面你可以发个通知,让你的application断线重连一下。PS:移动网络和Wi-Fi切换,socket会断开哦。

    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err

    附加:

      当我们连续收到错误报文的时候,我们需要主动断开socket。

    // 先去掉代理 再断开连接
    self.asyncSocket.delegate = nil;
    [self.asyncSocket disconnect];
  • 相关阅读:
    iOS7 iOS8 毛玻璃效果的分别实现
    关于app transfer之后的开发
    微信登录后,请求用户信息时, 返回地址信息是拼音的解决方案
    获得设备型号(含iPhone6 , iPhone 6+)
    iOS 开发者计划申请 2014 年最新心得[转]
    Facebook的Pop动画库相关资料
    iOS添加自定义字体方法
    iOS8新增加的frameworks, 在目前基于7以上开发的情况下, 使用下列sdk要注意设置成optional
    iOS开发 .framework的Optional(弱引用)和Required(强引用)区别, 有错误 Library not found………………
    python接口自动化测试二:常用操作
  • 原文地址:https://www.cnblogs.com/akforsure/p/5235559.html
Copyright © 2020-2023  润新知