CocoaAsyncSocket網路通訊使用之資料編碼和解碼(二)

來源:互聯網
上載者:User

標籤:編碼   socket   架構   ios   tcp   

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













著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

CocoaAsyncSocket網路通訊使用之資料編碼和解碼(二)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.