iOS基础扩展iOS 多线程网络socket、XMPP及即时通讯

CocoaAsyncSocket 学习 (一)

2016-07-16  本文已影响1571人  Theshy

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
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);
    }  
}
#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 打印东西多, 看关键哈~


此时已经连接成功
#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]));
    }
}

实现代理方法

/**
 *   服务器监听到有客户端接入会调用这个代理方法
 *
 *   @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];
}

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

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

解决方案:

- 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

上一篇 下一篇

猜你喜欢

热点阅读