CocoaAsyncSocket网络通信使用之数据编码和解码(二)

来源:互联网 发布:mysql去重复查询总数 编辑:程序博客网 时间:2024/05/05 18:30

在上一篇CocoaAsyncSocket网络通信使用之tcp连接(一)中,我们已经利用CocoaAsyncSocket封装了自己的socket connection。

本篇主要是通过引入编码器和解码器,将可以共用的内容模块化。


简述:

在tcp的应用中,都是以二机制字节的形式来对数据做传输。

一般会针对业务协议构造对应的数据结构/数据对象,然后在使用的时候针对协议转换成二进制数据发送给服务端。

但是我们在不同的app中,不同的业务场景使用不同的tcp协议,这样每次socket模块的重用性就特别差,即使是完全一样的底层内容,也因为实现的时候耦合性太高,而导致需要全部重新开发。为了实现模块化的重用,我仿照mina和netty,引入编码器和解码器。


接口框架设计:

为了后续扩展和自定义实现自己的编码器/解码器,有了以下的设计接口。


数据包

数据包基本接口定义( RHSocketPacket.h):

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @protocol RHSocketPacket <NSObject>  
  4.   
  5. @property (nonatomic, assign, readonly) NSInteger tag;  
  6. @property (nonatomicstrongreadonlyNSData *data;  
  7.   
  8. - (instancetype)initWithData:(NSData *)data;  
  9.   
  10. @optional  
  11.   
  12. - (void)setTag:(NSInteger)tag;  
  13. - (void)setData:(NSData *)data;  
  14.   
  15. @end  


数据包内容接口定义(RHSocketPacketContent.h):(增加timeout超时字段,主要是针对发送的数据包)

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketPacket.h"  
  3.   
  4. @protocol RHSocketPacketContent <RHSocketPacket>  
  5.   
  6. @property (nonatomicreadonly) NSTimeInterval timeout;  
  7.   
  8. @optional  
  9.   
  10. - (void)setTimeout:(NSTimeInterval)timeout;  
  11.   
  12. @end  


tcp编码器

编码器接口定义( RHSocketEncoderProtocol.h):

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketPacketContent.h"  
  3.   
  4. @protocol RHSocketEncoderOutputDelegate <NSObject>  
  5.   
  6. @required  
  7.   
  8. - (void)didEncode:(NSData *)data timeout:(NSTimeInterval)timeout tag:(NSInteger)tag;  
  9.   
  10. @end  
  11.   
  12. @protocol RHSocketEncoderProtocol <NSObject>  
  13.   
  14. @required  
  15.   
  16. - (void)encodePacket:(id<RHSocketPacketContent>)packet encoderOutput:(id<RHSocketEncoderOutputDelegate>)output;  
  17.   
  18. @end  

tcp解码器

解码器接口定义( RHSocketDecoderProtocol.h):

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketPacketContent.h"  
  3.   
  4. @protocol RHSocketDecoderOutputDelegate <NSObject>  
  5.   
  6. @required  
  7.   
  8. - (void)didDecode:(id<RHSocketPacketContent>)packet tag:(NSInteger)tag;  
  9.   
  10. @end  
  11.   
  12. @protocol RHSocketDecoderProtocol <NSObject>  
  13.   
  14. @required  
  15.   
  16. - (NSUInteger)decodeData:(NSData *)data decoderOutput:(id<RHSocketDecoderOutputDelegate>)output tag:(long)tag;//这里有返回值,是了为了处理数据包拼包  
  17.   
  18. @end  


ok,经过几次调整,程序员内心无数次纠结后,接口定义终于完成了,接下来我们看看怎么组合使用。

前面的socket connection在使用时,还是需要实现delegate的委托方法的,

在不同的app间使用还是需要copy,再实现数据[编码]、[解码]、[分发],

然后才到对应的场景。其实在[编码]、[分发]之前都是可以模块化独立的,我们就从这里入手。


架构整合调用

首先引入一个service,来帮我们做[连接]、[编码]、[解码]、[分发]的事情。

废话不多说,直接贴代码。


RHSocketService.h文件:

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketEncoderProtocol.h"  
  3. #import "RHSocketDecoderProtocol.h"  
  4.   
  5. extern NSString *const kNotificationSocketServiceState;  
  6. extern NSString *const kNotificationSocketPacketRequest;  
  7. extern NSString *const kNotificationSocketPacketResponse;  
  8.   
  9. @interface RHSocketService : NSObject <RHSocketEncoderOutputDelegate, RHSocketDecoderOutputDelegate>  
  10.   
  11. @property (nonatomiccopyNSString *serverHost;  
  12. @property (nonatomic, assign) int serverPort;  
  13.   
  14. @property (nonatomicstrongid<RHSocketEncoderProtocol> encoder;  
  15. @property (nonatomicstrongid<RHSocketDecoderProtocol> decoder;  
  16.   
  17. @property (assign, readonlyBOOL isRunning;  
  18.   
  19. + (instancetype)sharedInstance;  
  20.   
  21. - (void)startServiceWithHost:(NSString *)host port:(int)port;  
  22. - (void)stopService;  
  23.   
  24. - (void)asyncSendPacket:(id<RHSocketPacketContent>)packet;  
  25.   
  26. @end  

RHSocketService.m文件:

[objc] view plain copy
  1. #import "RHSocketService.h"  
  2. #import "RHSocketConnection.h"  
  3. #import "RHSocketDelimiterEncoder.h"  
  4. #import "RHSocketDelimiterDecoder.h"  
  5.   
  6. NSString *const kNotificationSocketServiceState = @"kNotificationSocketServiceState";  
  7. NSString *const kNotificationSocketPacketRequest = @"kNotificationSocketPacketRequest";  
  8. NSString *const kNotificationSocketPacketResponse = @"kNotificationSocketPacketResponse";  
  9.   
  10. @interface RHSocketService () <RHSocketConnectionDelegate>  
  11. {  
  12.     RHSocketConnection *_connection;  
  13. }  
  14.   
  15. @end  
  16.   
  17. @implementation RHSocketService  
  18.   
  19. + (instancetype)sharedInstance  
  20. {  
  21.     static id sharedInstance = nil;  
  22.     static dispatch_once_t onceToken;  
  23.     dispatch_once(&onceToken, ^{  
  24.         sharedInstance = [[self alloc] init];  
  25.     });  
  26.     return sharedInstance;  
  27. }  
  28.   
  29. - (instancetype)init  
  30. {  
  31.     if (self = [super init]) {  
  32.         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectSocketPacketRequest:) name:kNotificationSocketPacketRequest object:nil];  
  33.         _encoder = [[RHSocketDelimiterEncoder alloc] init];  
  34.         _decoder = [[RHSocketDelimiterDecoder alloc] init];  
  35.     }  
  36.     return self;  
  37. }  
  38.   
  39. - (void)dealloc  
  40. {  
  41.     [[NSNotificationCenter defaultCenter] removeObserver:self];  
  42. }  
  43.   
  44. - (void)startServiceWithHost:(NSString *)host port:(int)port  
  45. {  
  46.     NSAssert(_encoder, @"error, _encoder is nil...");  
  47.     NSAssert(_decoder, @"error, _decoder is nil...");  
  48.     NSAssert(host.length > 0@"error, host is nil...");  
  49.       
  50.     if (_isRunning) {  
  51.         return;  
  52.     }  
  53.       
  54.     _serverHost = host;  
  55.     _serverPort = port;  
  56.       
  57.     [self openConnection];  
  58. }  
  59.   
  60. - (void)stopService  
  61. {  
  62.     _isRunning = NO;  
  63.     [self closeConnection];  
  64. }  
  65.   
  66. - (void)asyncSendPacket:(id<RHSocketPacketContent>)packet  
  67. {  
  68.     if (!_isRunning) {  
  69.         NSDictionary *userInfo = @{@"msg":@"Send packet error. Service is stop!"};  
  70.         NSError *error = [NSError errorWithDomain:@"RHSocketService" code:1 userInfo:userInfo];  
  71.         [self didDisconnectWithError:error];  
  72.         return;  
  73.     }  
  74.     [_encoder encodePacket:packet encoderOutput:self];  
  75. }  
  76.   
  77. #pragma mar -  
  78. #pragma mark recevie response data  
  79.   
  80. - (void)detectSocketPacketRequest:(NSNotification *)notif  
  81. {  
  82.     id object = notif.object;  
  83.     [self asyncSendPacket:object];  
  84. }  
  85.   
  86. #pragma mark -  
  87. #pragma mark RHSocketConnection method  
  88.   
  89. - (void)openConnection  
  90. {  
  91.     [self closeConnection];  
  92.     _connection = [[RHSocketConnection alloc] init];  
  93.     _connection.delegate = self;  
  94.     [_connection connectWithHost:_serverHost port:_serverPort];  
  95. }  
  96.   
  97. - (void)closeConnection  
  98. {  
  99.     if (_connection) {  
  100.         _connection.delegate = nil;  
  101.         [_connection disconnect];  
  102.         _connection = nil;  
  103.     }  
  104. }  
  105.   
  106. #pragma mark -  
  107. #pragma mark RHSocketConnectionDelegate method  
  108.   
  109. - (void)didDisconnectWithError:(NSError *)error  
  110. {  
  111.     RHSocketLog(@"didDisconnectWithError: %@", error);  
  112.     _isRunning = NO;  
  113.     NSDictionary *userInfo = @{@"isRunning":@(_isRunning), @"error":error};  
  114.     [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketServiceState object:@(_isRunning) userInfo:userInfo];  
  115. }  
  116.   
  117. - (void)didConnectToHost:(NSString *)host port:(UInt16)port  
  118. {  
  119.     _isRunning = YES;  
  120.     NSDictionary *userInfo = @{@"host":host, @"port":@(port), @"isRunning":@(_isRunning)};  
  121.     [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketServiceState object:@(_isRunning) userInfo:userInfo];  
  122. }  
  123.   
  124. - (void)didReceiveData:(NSData *)data tag:(long)tag  
  125. {  
  126.     NSUInteger remainDataLen = [_decoder decodeData:data decoderOutput:self tag:tag];  
  127.     if (remainDataLen > 0) {  
  128.         [_connection readDataWithTimeout:-1 tag:tag];  
  129.     } else {  
  130.         [_connection readDataWithTimeout:-1 tag:0];  
  131.     }  
  132. }  
  133.   
  134. #pragma mark -  
  135. #pragma mark RHSocketEncoderOutputDelegate method  
  136.   
  137. - (void)didEncode:(NSData *)data timeout:(NSTimeInterval)timeout tag:(NSInteger)tag  
  138. {  
  139.     [_connection writeData:data timeout:timeout tag:tag];  
  140. }  
  141.   
  142. #pragma mark -  
  143. #pragma mark RHSocketDecoderOutputDelegate method  
  144.   
  145. - (void)didDecode:(id<RHSocketPacketContent>)packet tag:(NSInteger)tag  
  146. {  
  147.     NSDictionary *userInfo = @{@"RHSocketPacketBody":packet, @"tag":@(tag)};  
  148.     [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketPacketResponse object:nil userInfo:userInfo];  
  149. }  
  150.   
  151. @end  

测试代码如下:

[objc] view plain copy
  1. NSString *host = @"www.baidu.com";  
  2. int port = 80;  
  3. [[RHSocketService sharedInstance] startServiceWithHost:host port:port];  

[objc] view plain copy
  1. [RHSocketHttpService sharedInstance].encoder = [[RHSocketHttpEncoder alloc] init];  
  2. [RHSocketHttpService sharedInstance].decoder = [[RHSocketHttpDecoder alloc] init];  
  3. [[RHSocketHttpService sharedInstance] startServiceWithHost:host port:port];  

代码调用方法和过程说明:

1-通过startServiceWithHost方法,可实现对服务器的连接。

2-客户端给服务端发送数据,在连接成功后,可以调用asyncSendPacket方法发送数据包。也可以通过notification发送kNotificationSocketPacketRequest通知,发送数据包。在发送数据的过程中,会通过编码器,编码完成后通过connection发送给服务端。

3-服务端向客户端推送数据,会触发didReceiveData方法的回调,通过解码器,解码完成后发出通知给对应的分发器(分发器针对业务调整实现)


代码中是默认的解码器和编码器,针对数据中的分隔符处理,也可以自定义分隔符和数据帧的最大值,代码如下:


分隔符编码器

RHSocketDelimiterEncoder.h文件:

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketEncoderProtocol.h"  
  3.   
  4. /** 
  5.  *  针对数据包分隔符编码器 
  6.  *  默认数据包中每帧最大值为8192(maxFrameSize == 8192) 
  7.  *  默认数据包每帧分隔符为0xff(delimiter == 0xff) 
  8.  */  
  9. @interface RHSocketDelimiterEncoder : NSObject <RHSocketEncoderProtocol>  
  10.   
  11. @property (nonatomic, assign) NSUInteger maxFrameSize;  
  12. @property (nonatomic, assign) uint8_t delimiter;  
  13.   
  14. @end  

RHSocketDelimiterEncoder.m文件:

[objc] view plain copy
  1. #import "RHSocketDelimiterEncoder.h"  
  2. #import "RHSocketConfig.h"  
  3.   
  4. @implementation RHSocketDelimiterEncoder  
  5.   
  6. - (instancetype)init  
  7. {  
  8.     if (self = [super init]) {  
  9.         _maxFrameSize = 8192;  
  10.         _delimiter = 0xff;  
  11.     }  
  12.     return self;  
  13. }  
  14.   
  15. - (void)encodePacket:(id<RHSocketPacketContent>)packet encoderOutput:(id<RHSocketEncoderOutputDelegate>)output  
  16. {  
  17.     NSData *data = [packet data];  
  18.     NSMutableData *sendData = [NSMutableData dataWithData:data];  
  19.     [sendData appendBytes:&_delimiter length:1];  
  20.     NSAssert(sendData.length < _maxFrameSize, @"Encode frame is too long...");  
  21.       
  22.     NSTimeInterval timeout = [packet timeout];  
  23.     NSInteger tag = [packet tag];  
  24.     RHSocketLog(@"tag:%ld, timeout: %f, data: %@", (long)tag, timeout, sendData);  
  25.     [output didEncode:sendData timeout:timeout tag:tag];  
  26. }  
  27.   
  28. @end  

分隔符解码器

RHSocketDelimiterDecoder.h文件:

[objc] view plain copy
  1. #import <Foundation/Foundation.h>  
  2. #import "RHSocketDecoderProtocol.h"  
  3.   
  4. /** 
  5.  *  针对数据包分隔符解码器 
  6.  *  默认数据包中每帧最大值为8192(maxFrameSize == 8192) 
  7.  *  默认数据包每帧分隔符为0xff(delimiter == 0xff) 
  8.  */  
  9. @interface RHSocketDelimiterDecoder : NSObject <RHSocketDecoderProtocol>  
  10.   
  11. @property (nonatomic, assign) NSUInteger maxFrameSize;  
  12. @property (nonatomic, assign) uint8_t delimiter;  
  13.   
  14. @end  

RHSocketDelimiterDecoder.m文件:

[objc] view plain copy
  1. #import "RHSocketDelimiterDecoder.h"  
  2. #import "RHPacketBody.h"  
  3.   
  4. @interface RHSocketDelimiterDecoder ()  
  5. {  
  6.     NSMutableData *_receiveData;  
  7. }  
  8.   
  9. @end  
  10.   
  11. @implementation RHSocketDelimiterDecoder  
  12.   
  13. - (instancetype)init  
  14. {  
  15.     if (self = [super init]) {  
  16.         _maxFrameSize = 8192;  
  17.         _delimiter = 0xff;  
  18.     }  
  19.     return self;  
  20. }  
  21.   
  22. - (NSUInteger)decodeData:(NSData *)data decoderOutput:(id<RHSocketDecoderOutputDelegate>)output tag:(long)tag  
  23. {  
  24.     @synchronized(self) {  
  25.         if (_receiveData) {  
  26.             [_receiveData appendData:data];  
  27.         } else {  
  28.             _receiveData = [NSMutableData dataWithData:data];  
  29.         }  
  30.           
  31.         NSUInteger dataLen = _receiveData.length;  
  32.         NSInteger headIndex = 0;  
  33.           
  34.         for (NSInteger i=0; i<dataLen; i++) {  
  35.             NSAssert(i < _maxFrameSize, @"Decode frame is too long...");  
  36.             uint8_t byte;  
  37.             [_receiveData getBytes:&byte range:NSMakeRange(i, 1)];  
  38.             if (byte == _delimiter) {  
  39.                 NSInteger packetLen = i - headIndex;  
  40.                 NSData *packetData = [_receiveData subdataWithRange:NSMakeRange(headIndex, packetLen)];  
  41.                 RHPacketBody *body = [[RHPacketBody alloc] initWithData:packetData];  
  42.                 [output didDecode:body tag:0];  
  43.                 headIndex = i + 1;  
  44.             }  
  45.         }  
  46.           
  47.         NSData *remainData = [_receiveData subdataWithRange:NSMakeRange(headIndex, dataLen-headIndex)];  
  48.         [_receiveData setData:remainData];  
  49.           
  50.         return _receiveData.length;  
  51.     }//@synchronized  
  52. }  
  53.   
  54. @end  


总结:

目前没来得及提供服务器代码,不过socket框架已经基本完整,可以根据不同的协议扩展实现自定义的编码器和解码器。

测试代码中访问的是百度的首页,为http协议,需要额外实现http协议的编码器和解码器。

下一篇,我们来实现针对http的简单编码器和解码器。


--------------------

转载请注明出处,谢谢

email: zhu410289616@163.com

qq: 410289616

qq群:330585393


0 0