CocoaAsyncSocket網絡通信使用之數據編碼和解碼(二)

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, sendData);
    [output didEncode:sendData 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

qq羣:330585393


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章