CocoaAsyncSocket网络通信使用之数据编码和解码(二)
在上一篇CocoaAsyncSocket网络通信使用之tcp连接(一)中,我们已经利用
CocoaAsyncSocket封装了自己的socket connection。
本篇主要是通过引入编码器和解码器,将可以共用的内容模块化。
简述:
在tcp的应用中,都是以二机制字节的形式来对数据做传输。
一般会针对业务协议构造对应的数据结构/数据对象,然后在使用的时候针对协议转换成二进制数据发送给服务端。
但是我们在不同的app中,不同的业务场景使用不同的tcp协议,这样每次socket模块的重用性就特别差,即使是完全一样的底层内容,也因为实现的时候耦合性太高,而导致需要全部重新开发。为了实现模块化的重用,我仿照mina和netty,引入编码器和解码器。
接口框架设计:
为了后续扩展和自定义实现自己的编码器/解码器,有了以下的设计接口。
数据包
数据包基本接口定义( RHSocketPacket.h):
#import <Foundation/Foundation.h> @protocol RHSocketPacket <NSObject> @property (nonatomic, assign, readonly) NSInteger tag; @property (nonatomic, strong, readonly) NSData *data; - (instancetype)initWithData:(NSData *)data; @optional - (void)setTag:(NSInteger)tag; - (void)setData:(NSData *)data; @end
数据包内容接口定义(RHSocketPacketContent.h):(增加timeout超时字段,主要是针对发送的数据包)
#import <Foundation/Foundation.h> #import "RHSocketPacket.h" @protocol RHSocketPacketContent <RHSocketPacket> @property (nonatomic, readonly) NSTimeInterval timeout; @optional - (void)setTimeout:(NSTimeInterval)timeout; @end
tcp编码器
编码器接口定义( RHSocketEncoderProtocol.h):
#import <Foundation/Foundation.h> #import "RHSocketPacketContent.h" @protocol RHSocketEncoderOutputDelegate <NSObject> @required - (void)didEncode:(NSData *)data timeout:(NSTimeInterval)timeout tag:(NSInteger)tag; @end @protocol RHSocketEncoderProtocol <NSObject> @required - (void)encodePacket:(id<RHSocketPacketContent>)packet encoderOutput:(id<RHSocketEncoderOutputDelegate>)output; @end
tcp解码器
解码器接口定义( RHSocketDecoderProtocol.h):
#import <Foundation/Foundation.h> #import "RHSocketPacketContent.h" @protocol RHSocketDecoderOutputDelegate <NSObject> @required - (void)didDecode:(id<RHSocketPacketContent>)packet tag:(NSInteger)tag; @end @protocol RHSocketDecoderProtocol <NSObject> @required - (NSUInteger)decodeData:(NSData *)data decoderOutput:(id<RHSocketDecoderOutputDelegate>)output tag:(long)tag;//这里有返回值,是了为了处理数据包拼包 @end
ok,经过几次调整,程序员内心无数次纠结后,接口定义终于完成了,接下来我们看看怎么组合使用。
前面的socket connection在使用时,还是需要实现delegate的委托方法的,
在不同的app间使用还是需要copy,再实现数据[编码]、[解码]、[分发],
然后才到对应的场景。其实在[编码]、[分发]之前都是可以模块化独立的,我们就从这里入手。
架构整合调用
首先引入一个service,来帮我们做[连接]、[编码]、[解码]、[分发]的事情。
废话不多说,直接贴代码。
RHSocketService.h文件:
#import <Foundation/Foundation.h> #import "RHSocketEncoderProtocol.h" #import "RHSocketDecoderProtocol.h" extern NSString *const kNotificationSocketServiceState; extern NSString *const kNotificationSocketPacketRequest; extern NSString *const kNotificationSocketPacketResponse; @interface RHSocketService : NSObject <RHSocketEncoderOutputDelegate, RHSocketDecoderOutputDelegate> @property (nonatomic, copy) NSString *serverHost; @property (nonatomic, assign) int serverPort; @property (nonatomic, strong) id<RHSocketEncoderProtocol> encoder; @property (nonatomic, strong) id<RHSocketDecoderProtocol> decoder; @property (assign, readonly) BOOL isRunning; + (instancetype)sharedInstance; - (void)startServiceWithHost:(NSString *)host port:(int)port; - (void)stopService; - (void)asyncSendPacket:(id<RHSocketPacketContent>)packet; @end
RHSocketService.m文件:
#import "RHSocketService.h" #import "RHSocketConnection.h" #import "RHSocketDelimiterEncoder.h" #import "RHSocketDelimiterDecoder.h" NSString *const kNotificationSocketServiceState = @"kNotificationSocketServiceState"; NSString *const kNotificationSocketPacketRequest = @"kNotificationSocketPacketRequest"; NSString *const kNotificationSocketPacketResponse = @"kNotificationSocketPacketResponse"; @interface RHSocketService () <RHSocketConnectionDelegate> { RHSocketConnection *_connection; } @end @implementation RHSocketService + (instancetype)sharedInstance { static id sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } - (instancetype)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectSocketPacketRequest:) name:kNotificationSocketPacketRequest object:nil]; _encoder = [[RHSocketDelimiterEncoder alloc] init]; _decoder = [[RHSocketDelimiterDecoder alloc] init]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)startServiceWithHost:(NSString *)host port:(int)port { NSAssert(_encoder, @"error, _encoder is nil..."); NSAssert(_decoder, @"error, _decoder is nil..."); NSAssert(host.length > 0, @"error, host is nil..."); if (_isRunning) { return; } _serverHost = host; _serverPort = port; [self openConnection]; } - (void)stopService { _isRunning = NO; [self closeConnection]; } - (void)asyncSendPacket:(id<RHSocketPacketContent>)packet { if (!_isRunning) { NSDictionary *userInfo = @{@"msg":@"Send packet error. Service is stop!"}; NSError *error = [NSError errorWithDomain:@"RHSocketService" code:1 userInfo:userInfo]; [self didDisconnectWithError:error]; return; } [_encoder encodePacket:packet encoderOutput:self]; } #pragma mar - #pragma mark recevie response data - (void)detectSocketPacketRequest:(NSNotification *)notif { id object = notif.object; [self asyncSendPacket:object]; } #pragma mark - #pragma mark RHSocketConnection method - (void)openConnection { [self closeConnection]; _connection = [[RHSocketConnection alloc] init]; _connection.delegate = self; [_connection connectWithHost:_serverHost port:_serverPort]; } - (void)closeConnection { if (_connection) { _connection.delegate = nil; [_connection disconnect]; _connection = nil; } } #pragma mark - #pragma mark RHSocketConnectionDelegate method - (void)didDisconnectWithError:(NSError *)error { RHSocketLog(@"didDisconnectWithError: %@", error); _isRunning = NO; NSDictionary *userInfo = @{@"isRunning":@(_isRunning), @"error":error}; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketServiceState object:@(_isRunning) userInfo:userInfo]; } - (void)didConnectToHost:(NSString *)host port:(UInt16)port { _isRunning = YES; NSDictionary *userInfo = @{@"host":host, @"port":@(port), @"isRunning":@(_isRunning)}; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketServiceState object:@(_isRunning) userInfo:userInfo]; } - (void)didReceiveData:(NSData *)data tag:(long)tag { NSUInteger remainDataLen = [_decoder decodeData:data decoderOutput:self tag:tag]; if (remainDataLen > 0) { [_connection readDataWithTimeout:-1 tag:tag]; } else { [_connection readDataWithTimeout:-1 tag:0]; } } #pragma mark - #pragma mark RHSocketEncoderOutputDelegate method - (void)didEncode:(NSData *)data timeout:(NSTimeInterval)timeout tag:(NSInteger)tag { [_connection writeData:data timeout:timeout tag:tag]; } #pragma mark - #pragma mark RHSocketDecoderOutputDelegate method - (void)didDecode:(id<RHSocketPacketContent>)packet tag:(NSInteger)tag { NSDictionary *userInfo = @{@"RHSocketPacketBody":packet, @"tag":@(tag)}; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketPacketResponse object:nil userInfo:userInfo]; } @end
测试代码如下:
NSString *host = @"www.baidu.com"; int port = 80; [[RHSocketService sharedInstance] startServiceWithHost:host port:port];
[RHSocketHttpService sharedInstance].encoder = [[RHSocketHttpEncoder alloc] init]; [RHSocketHttpService sharedInstance].decoder = [[RHSocketHttpDecoder alloc] init]; [[RHSocketHttpService sharedInstance] startServiceWithHost:host port:port];
代码调用方法和过程说明:
1-通过startServiceWithHost方法,可实现对服务器的连接。
2-客户端给服务端发送数据,在连接成功后,可以调用asyncSendPacket方法发送数据包。也可以通过notification发送kNotificationSocketPacketRequest通知,发送数据包。在发送数据的过程中,会通过编码器,编码完成后通过connection发送给服务端。
3-服务端向客户端推送数据,会触发didReceiveData方法的回调,通过解码器,解码完成后发出通知给对应的分发器(分发器针对业务调整实现)
代码中是默认的解码器和编码器,针对数据中的分隔符处理,也可以自定义分隔符和数据帧的最大值,代码如下:
分隔符编码器
RHSocketDelimiterEncoder.h文件:
#import <Foundation/Foundation.h> #import "RHSocketEncoderProtocol.h" /** * 针对数据包分隔符编码器 * 默认数据包中每帧最大值为8192(maxFrameSize == 8192) * 默认数据包每帧分隔符为0xff(delimiter == 0xff) */ @interface RHSocketDelimiterEncoder : NSObject <RHSocketEncoderProtocol> @property (nonatomic, assign) NSUInteger maxFrameSize; @property (nonatomic, assign) uint8_t delimiter; @end
RHSocketDelimiterEncoder.m文件:
#import "RHSocketDelimiterEncoder.h" #import "RHSocketConfig.h" @implementation RHSocketDelimiterEncoder - (instancetype)init { if (self = [super init]) { _maxFrameSize = 8192; _delimiter = 0xff; } return self; } - (void)encodePacket:(id<RHSocketPacketContent>)packet encoderOutput:(id<RHSocketEncoderOutputDelegate>)output { NSData *data = [packet data]; NSMutableData *sendData = [NSMutableData dataWithData:data]; [sendData appendBytes:&_delimiter length:1]; NSAssert(sendData.length < _maxFrameSize, @"Encode frame is too long..."); NSTimeInterval timeout = [packet timeout]; NSInteger tag = [packet tag]; RHSocketLog(@"tag:%ld, timeout: %f, data: %@", (long)tag, timeout, data); [output didEncode:data timeout:timeout tag:tag]; } @end
分隔符解码器
RHSocketDelimiterDecoder.h文件:
#import <Foundation/Foundation.h> #import "RHSocketDecoderProtocol.h" /** * 针对数据包分隔符解码器 * 默认数据包中每帧最大值为8192(maxFrameSize == 8192) * 默认数据包每帧分隔符为0xff(delimiter == 0xff) */ @interface RHSocketDelimiterDecoder : NSObject <RHSocketDecoderProtocol> @property (nonatomic, assign) NSUInteger maxFrameSize; @property (nonatomic, assign) uint8_t delimiter; @end
RHSocketDelimiterDecoder.m文件:
#import "RHSocketDelimiterDecoder.h" #import "RHPacketBody.h" @interface RHSocketDelimiterDecoder () { NSMutableData *_receiveData; } @end @implementation RHSocketDelimiterDecoder - (instancetype)init { if (self = [super init]) { _maxFrameSize = 8192; _delimiter = 0xff; } return self; } - (NSUInteger)decodeData:(NSData *)data decoderOutput:(id<RHSocketDecoderOutputDelegate>)output tag:(long)tag { @synchronized(self) { if (_receiveData) { [_receiveData appendData:data]; } else { _receiveData = [NSMutableData dataWithData:data]; } NSUInteger dataLen = _receiveData.length; NSInteger headIndex = 0; for (NSInteger i=0; i<dataLen; i++) { NSAssert(i < _maxFrameSize, @"Decode frame is too long..."); uint8_t byte; [_receiveData getBytes:&byte range:NSMakeRange(i, 1)]; if (byte == _delimiter) { NSInteger packetLen = i - headIndex; NSData *packetData = [_receiveData subdataWithRange:NSMakeRange(headIndex, packetLen)]; RHPacketBody *body = [[RHPacketBody alloc] initWithData:packetData]; [output didDecode:body tag:0]; headIndex = i + 1; } } NSData *remainData = [_receiveData subdataWithRange:NSMakeRange(headIndex, dataLen-headIndex)]; [_receiveData setData:remainData]; return _receiveData.length; }//@synchronized } @end
总结:
目前没来得及提供服务器代码,不过socket框架已经基本完整,可以根据不同的协议扩展实现自定义的编码器和解码器。
测试代码中访问的是百度的首页,为http协议,需要额外实现http协议的编码器和解码器。
下一篇,我们来实现针对http的简单编码器和解码器。
--------------------
转载请注明出处,谢谢
email: [email protected]
qq: 410289616
版权声明:本文为博主原创文章,未经博主允许不得转载。