zoukankan      html  css  js  c++  java
  • 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方法,可实现对server的连接。

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

    3-服务端向client推送数据,会触发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


    总结:

    眼下没来得及提供server代码,只是socket框架已经基本完整,能够依据不同的协议扩展实现自己定义的编码器和解码器。

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

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


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

    转载请注明出处。谢谢

    email: zhu410289616@163.com

    qq: 410289616

    qq群:330585393


  • 相关阅读:
    爬虫伪装头部
    selenium的简单使用
    selenium 安装与 chromedriver安装
    python多线程和线程池
    分析微信好友列表信息(json)
    BeautifulSoup简介
    Java泛型中extends和super的理解
    java 泛型--桥方法
    java 资源文件的读取
    java 清单文件
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/6868803.html
Copyright © 2011-2022 走看看