您的位置:首页 > 移动开发 > IOS开发

iOS之CocoaAsyncSocket初尝试

2015-10-14 16:38 441 查看
最近项目需要,用到socket长链接来进行数据处理,本来一直用http的我一下子就傻了眼,没关系,只要有心什么都不是事!经过我两天的研究和查找资料,终于对tcp有了一定的初步了解,在此也记录一下!

首先,我们需要AsyncSocket这个第三方库,对此也进行了解一下:

AsyncSocket类是支持TCP的

AsyncUdpSocket是支持UDP的

AsyncSocket是封装了CFSocket和CFSteam的TCP/IP socket网络库。它提供了异步操作,本地cocoa类的基于delegate的完整支持。

在此也附上下载地址:https://github.com/robbiehanson/CocoaAsyncSocket

我们下载完成之只需要把RunLoop文件夹下的AsyncSocket.h, AsyncSocket.m, AsyncUdpSocket.h, AsyncUdpSocket.m 四个文件拷贝到自己的project中即可,不过我建议还是在项目中自己创建一个文件夹方便管理,首先我们新建一个scoket(我英文不好,应该是socket,别见笑)项目,将RunLoop下的四个文件放到项目中如图:




然后我们需要添加一个CFNetwork.framework,在targets中找到build phases然后添加如图:




接着我们创建一个单例对象,命名就为Singleton吧,以后不管在哪里我们需要用到长链接,只需要调用这个单例对象便可进行操作。

单例的创建我就不用多说了吧,直接把我的代码附上了

.h文件

[code]#import <Foundation/Foundation.h>
#import "AsyncSocket.h"

#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
static dispatch_once_t onceToken = 0; \
__strong static id sharedInstance = nil; \
dispatch_once(&onceToken, ^{ \
sharedInstance = block(); \
}); \
return sharedInstance; \

@interface Singleton : NSObject<AsyncSocketDelegate>

+ (Singleton *)sharedInstance;

@end


.m文件

[code]+(Singleton *) sharedInstance
{

static Singleton *sharedInstace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

    sharedInstace = [[self alloc] init];
});

return sharedInstace;
}


然后我们还需要在.h文件声明socket变量,和打开链接方法,并且继承AsyncSocket的代理协议,建议大家一定要看下代理方法有哪些分别都是什么用处,都是在什么时候调用,这个很重要!

于是.h文件代码:

[code]
@interface Singleton : NSObject<AsyncSocketDelegate>//感兴趣的同学可以点击进入AsyncSocketDelegate中看看代理方法
@property (nonatomic, strong) AsyncSocket    *socket;       // socket
@property (nonatomic, copy  ) NSString       *socketHost;   // socket的Host
@property (nonatomic, assign) UInt16         socketPort;    // socket的prot 
-(void)socketConnectHost;// socket连接
+ (Singleton *)sharedInstance;

@end


.m文件实现打开链接的方法

[code]/ socket连接
-(void)socketConnectHost{

    self.socket    = [[AsyncSocket alloc] initWithDelegate:self];

    NSError *error = nil;

    [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];

}


使用socket进行即时通讯还有一个必须的操作,即对服务器发送心跳包,每隔一段时间对服务器发送长连接指令(指令不唯一,由服务器端指定,包括使用socket发送消息,发送的数据和格式都是由服务器指定),如果没有收到服务器的返回消息,AsyncSocket会得到失去连接的消息,我们可以在失去连接的回调方法里进行重新连接。

因此在singleton.h中声明一个定时器

[code]@property (nonatomic, retain) NSTimer        *connectTimer; // 计时器


在.m中实现连接成功回调方法,并在此方法中初始化定时器,发送心跳在后文向服务器发送数据时说明

[code]#pragma mark  - 连接成功回调
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString  *)host port:(UInt16)port
{
    NSLog(@"socket连接成功");
//如果程序需要帐号登陆,一般在这里会先发送注册包
    // 每隔30s像服务器发送心跳包
    self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];// 在longConnectToSocket方法中进行长连接需要向服务器发送的讯息
    [[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];//最好把定时器放在子线程中(假如有一个scrowview,当你一直滚动scrowview时,主线程一直在处理滚动事件,定时器就不会再走)

    [self.connectTimer fire];

    [self.socket readDataWithTimeout:-1 tag:0];//如果不加这句则可能不会触发回调函数,收不到服务器数据
}


实现longconnecttosocket方法

[code]-(void)longConnectToSocket
{

    // 根据服务器要求发送固定格式的数据,假设为指令@"longConnect",但是一般不会是这么简单的指令

    NSString *longConnect = @"longConnect(xin tiao bao)\n";
    NSLog(@"发送心跳包");

    NSData   *dataStream  = [longConnect dataUsingEncoding:NSUTF8StringEncoding];

    [self.socket writeData:dataStream withTimeout:-1 tag:1];

}


socket 断开连接与重连

失去连接有几种情况,服务器断开,用户主动cut,还可能有如QQ其他设备登录被掉线的情况,不管那种情况,我们都能收到socket回调方法返回给我们的讯息,如果是用户退出登录或是程序退出而需要手动cut,我们在cut前对socket的userData赋予一个值来标记为用户退出,这样我们可以在收到断开信息时判断究竟是什么原因导致的掉线

因此我们需要在.h文件声明一个枚举

[code]enum{
    SocketOfflineByServer,// 服务器掉线,默认为0
    SocketOfflineByUser,  // 用户主动cut
};


声明断开连接方法

[code]-(void)cutOffSocket; // 断开socket连接


.m实现此方法

[code]// 切断socket
-(void)cutOffSocket{

    self.socket.userData = SocketOfflineByUser;// 声明是由用户主动切断

    [self.connectTimer invalidate];

    [self.socket disconnect];
}


一旦socket链接出现断开,将会执行代理方法-(void)onSocketDidDisconnect:(AsyncSocket *)sock,因此我们需要在这个方法中判断是否需要重新连接

.m文件实现此代理方法如下:

[code]-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
    NSLog(@"sorry the connect is failure %ld",sock.userData);
    if (sock.userData == SocketOfflineByServer) {
        // 服务器掉线,重连
        [self socketConnectHost];
    }
    else if (sock.userData == SocketOfflineByUser) {
        // 如果由用户断开,不进行重连
        return;
    }

}


socket 接收到服务器数据时会执行-(void)onSocket:(AsyncSocket )sock didReadData:(NSData )data withTag:(long)tag方法

[code]-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSLog(@"收到数据");
    // 对得到的data值进行解析与转换即可

    NSLog(@"------%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    [self.socket readDataWithTimeout:-1 tag:0];

}


到此,我们的socket算是初步已经完成,现在我把Singleton.h和Singleton.m文件的全部代码贴出来供大家参考:

[code]#import <Foundation/Foundation.h>
#import "AsyncSocket.h"

#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
static dispatch_once_t onceToken = 0; \
__strong static id sharedInstance = nil; \
dispatch_once(&onceToken, ^{ \
sharedInstance = block(); \
}); \
return sharedInstance; \

@interface Singleton : NSObject<AsyncSocketDelegate>
@property (nonatomic, strong) AsyncSocket    *socket;       // socket
@property (nonatomic, copy  ) NSString       *socketHost;   // socket的Host
@property (nonatomic, assign) UInt16         socketPort;    // socket的prot
@property (nonatomic, strong) NSTimer        *connectTimer; // 计时器
enum{
    SocketOfflineByServer,// 服务器掉线,默认为0
    SocketOfflineByUser,  // 用户主动cut
};
-(void)cutOffSocket; // 断开socket连接
-(void)socketConnectHost;//开启socket链接
+ (Singleton *)sharedInstance;

@end


[code]//
//  Singleton.m
//  scoket
//
//  Created by huasu on 15/10/12.
//  Copyright (c) 2015年 HS. All rights reserved.
//

#import "Singleton.h"

@implementation Singleton
+(Singleton *) sharedInstance
{

    static Singleton *sharedInstace = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        sharedInstace = [[self alloc] init];
    });

    return sharedInstace;
}
-(void)socketConnectHost{

    self.socket = [[AsyncSocket alloc] initWithDelegate:self];

    NSError *error = nil;

    [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];

}
//pragma mark  - 连接成功回调
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString  *)host port:(UInt16)port
{
    NSLog(@"socket连接成功");

    // 每隔30s像服务器发送心跳包
    self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];// 在longConnectToSocket方法中进行长连接需要向服务器发送的讯息
    [[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];//最好把定时器放在子线程中(假如有一个scrowview,当你一直滚动scrowview时,主线程一直在处理滚动事件,定时器就不会再走)
    [self.connectTimer fire];

    [self.socket readDataWithTimeout:-1 tag:0];

}
// 切断socket
-(void)cutOffSocket{

    self.socket.userData = SocketOfflineByUser;// 声明是由用户主动切断

    [self.connectTimer invalidate];

    [self.socket disconnect];
}
-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
    NSLog(@"sorry the connect is failure %ld",sock.userData);
    if (sock.userData == SocketOfflineByServer) {
        // 服务器掉线,重连
        [self socketConnectHost];
    }
    else if (sock.userData == SocketOfflineByUser) {
        // 如果由用户断开,不进行重连
        return;
    }

//    NSData   *dataStream  = [@8 dataUsingEncoding:NSUTF8StringEncoding];
//    
//    [self.socket writeData:dataStream withTimeout:1 tag:1];

}
-(void)longConnectToSocket
{

    // 根据服务器要求发送固定格式的数据,假设为指令@"longConnect",但是一般不会是这么简单的指令

    NSString *longConnect = @"longConnect(xin tiao bao)\n";
    NSLog(@"发送心跳包");

    NSData   *dataStream  = [longConnect dataUsingEncoding:NSUTF8StringEncoding];

    [self.socket writeData:dataStream withTimeout:-1 tag:1];

}
- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
{
    NSLog(@"----%ld",partialLength);
}
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSLog(@"收到数据");
    // 对得到的data值进行解析与转换即可

    NSLog(@"------%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    [self.socket readDataWithTimeout:-1 tag:0];

}
@end


现在我们让创建一个viewcontroller来简单适用一下:

[code]#import "ViewController.h"
#import "Singleton.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor=[UIColor redColor];
    [Singleton sharedInstance].socketHost = @"192.168.5.109";// host设定
    [Singleton sharedInstance].socketPort =  7701;// port设定

    // 在连接前先进行手动断开
    [Singleton sharedInstance].socket.userData = SocketOfflineByUser;
    [[Singleton sharedInstance] cutOffSocket];

//    // 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
    [Singleton sharedInstance].socket.userData = SocketOfflineByServer;
    [[Singleton sharedInstance] socketConnectHost];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end


此时无论是突然断网,还是服务器主动断开连接,只要网络再次连接 都会进行从新链接

上面的socketHost时服务器地址,而socketPort是服务器端口

ui处理:

//1、发送消息 单例添加一个公共方法,传入一个字符串或Data

//2、接收消息 单例收到Socket消息,发送本地推送。相应的VC收听这个消息,实现后续逻辑
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: