在App与服务器需要高频通信,或者服务器主动推送消息到App的情况下,就需要通过长连接来实现。比如聊天和股票软件。
下面介绍iOS中如何通过GCDAsyncSocket来实现长连接。
-(GCDAsyncSocket*)socket
{
if (_socket == nil)
{
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
return _socket;
}
NSError *error = nil;
// host:域名或ip,port:端口号,timeout:超时时间
if (![self.socket connectToHost:host onPort:port withTimeout:timeout error:&error])
{
NSLog(@"socket连接服务器错误:%@", error);
}
#pragma mark- GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"socket成功建立。");
}
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
{
NSLog(@"链接出错:error:%@", error);
}
// 打包到App中的根证书
-(NSData *)certData
{
if(!_certData)
{
_certData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"root_cert"
ofType:@"cer"]];
}
return _certData;
}
#pragma mark- GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"socket成功建立。");
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
//允许自签名证书手动验证
[settings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust];
//GCDAsyncSocketSSLPeerName
//[settings setObject:@"example.com" forKey:GCDAsyncSocketSSLPeerName];
// 如果不是自签名证书,而是那种权威证书颁发机构注册申请的证书
// 那么这个settings字典可不传。
NSLog(@"socket开始TLS握手");
[sock startTLS:settings];
}
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler
{
NSLog(@"socket TLS开始校验证书。");
OSStatus status = -1;
SecTrustResultType result = kSecTrustResultDeny;
if(self.certData)
{
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)self.certData);
// 设置证书用于验证
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)[NSArray arrayWithObject:(__bridge id)cert]);
// 验证服务器证书和本地证书是否匹配
status = SecTrustEvaluate(trust, &result);
CFRelease(cert);
}
else
{
NSLog(@"local certificates could not be loaded");
completionHandler(NO);
}
if ((status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)))
{
//成功通过验证,证书可信
completionHandler(YES);
}
else
{
CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust);
TPDiskLog(@"error in connection occured\n%@", arrayRefTrust);
CFRelease(arrayRefTrust);
completionHandler(NO);
}
NSLog(@"socket TLS校验证书完毕。");
}
- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
NSLog(@"socket TLS握手成功,安全通信已经建立连接。");
}
typedef struct {
uint32_t length;// 数据长度
} header_t;
[self.socket readDataToLength:lenght withTimeout:-1 tag:tag];
#pragma mark- GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
// 读取到数据,通过tag来区分是header还是body
}
[self.socket writeData:data withTimeout:-1 tag:0];
#pragma mark- GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"didWriteDataWithTag:%ld",tag);
}
typedef struct {
uint32_t length;// 数据长度
} header_t;
#define HeaderLength (sizeof(header_t))
// 通信层的超时时间,都设置成infinite
#define SocketTimeOutNone (-1) // 不超时,防止socket断开
#define ReadTagPacketHeader (101)
#define ReadTagPacketBody (102)
@interface SocketManager() <GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *socket;
@property (nonatomic, strong) NSData *certData;
@end
@implementation SocketManager
-(GCDAsyncSocket*)socket
{
if (_socket == nil)
{
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
return _socket;
}
-(NSData *)certData
{
if(!_certData)
{
_certData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"root_cert" ofType:@"cer"]];
}
return _certData;
}
#pragma mark- func
-(BOOL)connectToServer
{
NSLog(@"socket开始连接服务器");
NSError *error = nil;
if (![self.socket connectToHost:host onPort:port withTimeout:15 error:&error])
{
NSLog(@"socket连接服务器错误:%@", error);
return NO;
}
return YES;
}
-(void)disconnect
{
[self.socket disconnect];
}
-(void)sendData:(NSData*)data
{
header_t h = {};
h.length = (uint32_t)data.length;
[self.socket writeData:[NSData dataWithBytes:&h length:HeaderLength] withTimeout:SocketTimeOutNone tag:0];
[self.socket writeData:data withTimeout:SocketTimeOutNone tag:0];
}
-(void)readDataLenght:(NSUInteger)lenght tag:(long)tag
{
[self.socket readDataToLength:lenght withTimeout:SocketTimeOutNone tag:tag];
}
#pragma mark- GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"socket成功建立。");
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];
//允许自签名证书手动验证
[settings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust];
//GCDAsyncSocketSSLPeerName
//[settings setObject:@"example.com" forKey:GCDAsyncSocketSSLPeerName];
// 如果不是自签名证书,而是那种权威证书颁发机构注册申请的证书
// 那么这个settings字典可不传。
NSLog(@"socket开始TLS握手");
[sock startTLS:settings]; // 开始TLS握手
}
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler
{
NSLog(@"socket TLS开始校验证书。");
OSStatus status = -1;
SecTrustResultType result = kSecTrustResultDeny;
if(self.certData)
{
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)self.certData);
// 设置证书用于验证
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)[NSArray arrayWithObject:(__bridge id)cert]);
// 验证服务器证书和本地证书是否匹配
status = SecTrustEvaluate(trust, &result);
CFRelease(cert);
}
else
{
NSLog(@"local certificates could not be loaded");
completionHandler(NO);
}
if ((status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)))
{
//成功通过验证,证书可信
completionHandler(YES);
}
else
{
CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust);
NSLog(@"error in connection occured\n%@", arrayRefTrust);
CFRelease(arrayRefTrust);
completionHandler(NO);
}
NSLog(@"socket TLS校验证书完毕。");
}
- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
NSLog(@"socket TLS握手成功,安全通信已经建立连接。");
[self readDataLenght:kHeaderLength tag:ReadTagPacketHeader];
}
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
{
NSLog(@"链接出错:error:%@\n", error);
}
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"didWriteDataWithTag:%ld",tag);
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
static header_t h = {};
if (tag == ReadTagPacketHeader)
{
if (data.length == HeaderLength)
{
header_t *header = (header_t*)data.bytes;
h.length = header->length;
if (header->length == 0)// 只有头部
{
[self readDataLenght:HeaderLength tag:ReadTagPacketHeader];
}
else
{
[self readDataLenght:header->length tag:ReadTagPacketBody];
}
}
else
{
NSLog(@"exception occur");
}
}
else if (tag == ReadTagPacketBody)
{
// 这里的data,就是server发送的数据
[self readDataLenght:HeaderLength tag:ReadTagPacketHeader];
}
}
@end