CocoaAsyncSocket 学习 (一)

socket 连接

即时通讯最大的特点就是实时性,基本感觉不到延时或是掉线,所以必须对socket的连接进行监视与检测,在断线时进行重新连接,如果用户退出登录,要将socket手动关闭,否则对服务器会造成一定的负荷。

一般来说,一个用户(对于ios来说也就是我们的项目中)只能有一个正在连接的socket,所以这个socket变量必须是全局的,这里可以考虑使用单例或是AppDelegate进行数据共享,本文使用单例。如果对一个已经连接的socket对象再次进行连接操作,会抛出异常(不可对已经连接的socket进行连接)程序崩溃,所以在连接socket之前要对socket对象的连接状态进行判断

一 下载完包结构

RunLoopGCD两个文件夹中有两套
一种基于NSRunloop,一种基于GCD,后面讲的都是用基于GCDCocoaAsyncSocket,因为RunLoop中的将被废弃

__deprecated_msg("The RunLoop versions of CocoaAsyncSocket are deprecated and will be removed in a future release. Please migrate to GCDAsyncSocket.")
二 项目中应用CocoaAsyncSocket
  • GCD下四个文件拖入项目
GCDAsyncSocket.h
GCDAsyncSocket.m
GCDAsyncUdpSocket.h
GCDAsyncUdpSocket.m
  • 创建单例类
//  YHSocket.h
@interface YHSocket : NSObject
+ (instancetype)sharedSocket;
@end
//  YHSocket.m
#import "YHSocket.h"
#import "GCDAsyncSocket.h"

@interface YHSocket ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *asyncSocket;
@end

@implementation YHSocket

+ (instancetype)sharedSocket {
    static YHSocket *scoket;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        scoket = [[self alloc] init];
    });
    return scoket;
}

/*
全局队列(代理的方法是在子线程被调用)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)

主队列(代理的方法会在主线程被调用)
dispatch_get_main_queue()
代理里的动作是耗时的动作,要在子线程中操作
代理里的动作不是耗时的动作,就可以在主线程中调用
看情况写队列
*/
- (instancetype)init {
    if (self = [super init]) {
        _asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    }
    return self;
}
  • 然后写连接方法
    添加端口和服务器地址属性
    添加连接方法
//  YHSocket.h
@interface YHSocket : NSObject

@property (nonatomic, assign) uint16_t port; // 端口
@property (nonatomic, copy) NSString *socketHost; // 服务器地址

+ (instancetype)sharedSocket;
- (void)startConnectSocket;

@end

实现

- (void)startConnectSocket {
    
    NSError *error = nil;
    [_asyncSocket acceptOnPort:self.port error:&error];
    
    if (!error) {
        NSLog(@"服务开启成功");
    } else {
        NSLog(@"服务开启失败 %@", error);
    }  
}
  • main.m中测试
#import "YHSocket.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        YHSocket *socket = [YHSocket sharedSocket];
        socket.port = 5528; // 测试端口
        socket.socketHost = @"192.168.1.114"; // 我自己电脑IP
        [socket startConnectSocket];
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

运行后控制台打印

现在用的xcode8 beta2 打印东西多, 看关键哈~


此时已经连接成功

  • 此时当程序停止运行的话 服务器就会停掉 实际中我们服务器会一直开启,365天不停止,开启运行循环
#import "YHSocket.h"
int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        YHSocket *socket = [YHSocket sharedSocket];
        socket.port = 5528; // 测试端口
        socket.socketHost = @"192.168.1.114"; // 我自己电脑IP
        [socket startConnectSocket];
        
        [[NSRunLoop mainRunLoop] run]; // 开启运行循环
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • 接下来模拟客户端接入到服务器
    遵守协议
    @interface YHSocket ()<GCDAsyncSocketDelegate>

实现代理方法

/**
 *   服务器监听到有客户端接入会调用这个代理方法
 *
 *   @param sock        服务端
 *   @param newSocket   客户端
 *
 */

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"服务端  %@", sock);
    NSLog(@"客户端  %@", newSocket);
}

代码运行 控制台输出

2016-07-15 21:53:29.517 Socket[7227:381191] 服务开启成功

终端中测试 用telnet命令


此时已经连接成功

socket 通信流程图

socket.png

看这句话 Connection closed by foreign host.
连接被外部服务器关闭了,服务器连上之后 还没来得及读和写就被释放了

socket1.png

因为 客户端 socket对象是局部的, 被释放了
所以我们要保存连接到服务器的客户端
定义数组 懒加载
@property (nonatomic, strong) NSMutableArray *clientSockets;// 客户端socket

- (NSMutableArray *)clientSockets {
    if (_clientSockets == nil) {
        _clientSockets = [NSMutableArray array];
    }
    return _clientSockets;
}

连接到服务器上后 存储起来

// 客户端socket 连接到服务器
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"服务端  %@", sock);
    NSLog(@"客户端  %@", newSocket);
    [self.clientSockets addObject:newSocket];
    
}

此时我们在终端再次连接 发现 连接上之后就不再断开了

  • 服务器接收客户端发送的数据
// 服务器读取客户端发送数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    NSLog(@"客户端  %@", sock);
    NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"data --- %@", dataStr);
}

// 在读取数据之前 服务端还需要监听 客户端有没有写入数据

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"服务端  %@", sock);
    NSLog(@"客户端  %@", newSocket);
    [self.clientSockets addObject:newSocket];
    
    // 监听客户端是否写入数据
    // timeOut: -1 暂时不需要 超时时间  tag暂时不需要 传0
    [newSocket readDataWithTimeout:-1 tag:0];
}

此时连接服务器 输入hello world 打印

WARNING: 读取一次数据 需要重新监听一次客户端的输入

不然再次发送数据 无法读取到

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    NSLog(@"客户端  %@", sock);
    NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"data --- %@", dataStr);
    
    [sock readDataWithTimeout:-1 tag:0];
}

// 服务端接收到客户端发送的数据之后 返回数据给 客户端

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    NSLog(@"客户端  %@", sock);
    NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"data --- %@", dataStr);
    
    
    // 服务端接收到客户端发送的数据之后  返回数据给 客户端
    // 我们将接受到的数据返回回去
    [sock writeData:data withTimeout:-1 tag:0];
    
    [sock readDataWithTimeout:-1 tag:0];
}

输入 数据 终端直接返回数据 控制台会打印

上边所有客户端与服务器的交互 都是由客户端 与服务器连接上的客户端交互
服务端负责连接客户端

  • 警告问题⚠️
    使用 CocoaAsyncSocket “kCFStreamNetworkServiceTypeVoIP is deprecated in iOS 9 ” warning 解决方案
    在iOS 9.0 中,kCFStreamNetworkServiceTypeVoIP 被弃用

解决方案:

- 1 导入` #import <PushKit/PushKit.h>  `头文件
- 2 `r1`` r2` 中的 `kCFStreamNetworkServiceTypeVoIP` 替换为 “PKPushTypeVoIP”
r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, PKPushTypeVoIP);

r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, PKPushTypeVoIP);

相关链接:
https://github.com/robbiehanson/CocoaAsyncSocket/issues/402

参考

http://www.superqq.com/blog/2015/04/03/ioskai-fa-zhi-asyncsocketshi-yong-jiao-cheng/?utm_source=tuicool&utm_medium=referral

http://my.oschina.net/joanfen/blog/287238

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

推荐阅读更多精彩内容