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, 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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-20 09:03:16

CocoaAsyncSocket网络通信使用之数据编码和解码(二)的相关文章

CocoaAsyncSocket 网络通信使用之http协议测试(三)

CocoaAsyncSocket 网络通信使用之http协议测试(三) 通过前一篇CocoaAsyncSocket网络通信使用之数据编码和解码(二),我们已经搭建好了socket的框架. 框架主要分为以下5个模块: 1-网络连接模块(socket connection) 2-数据协议框架(socket packet content protocol) 3-发送数据前的编码模块(socket encoder protocol) 4-接收数据后的解码模块(socket decoder protoco

CocoaAsyncSocket网络通信使用之tcp连接(一)

CocoaAsyncSocket网络通信使用之tcp连接(一) 简述: 在互联网世界中,网络访问是必不可少的一部分,而对于程序员来说,网络编程却是一个比较复杂的存在,特别是socket处理方面. 在android平台中,java类库丰富,封装良好,比如:mina,netty等等. 而在ios平台中,也有出名的socket库,CocoaAsyncSocket. 最近碰到一些朋友在socket的应用上一直不是特别熟悉,自己在接触过socket底层库,使用过mina,netty和CocoaAsyncS

windows8运行zxing源码 生成与解码二维码 详解(含注释与图解可直接运行)

1 下载zxing2.1 2 本代码配置环境:eclipse.java1.6.windows8.zxing2.1 3 解压后将文件夹里面core/src下面的com文件夹导入到eclipse工程(工程可以自己建,如QrCode)中,图示如下: 注意:在源码中需要修改其编码配置为UTF-8,否则后面解码后面的文件中中文会乱码,修改图示如下: 4 TestEnDeCode.java源代码 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

javascript数据相关处理,序列化反序列化,数据编码与解码

对象序列化简而言之,将对象转为字符串.在数据的传输过程中,经常会使用到对象序列化. javascript中常用的对象序列化:JSON.stringify(); javascript中常用的对象反序列化: JSON.parse(); 注意:JSON.stringify兼容ie8+,ie7以及一下可用 json2.js,对应的资源可网上下载 var a = {"name":"mary","age":"100"} var b = J

c#网络通信框架networkcomms内核解析之二 消息处理流程

networkcomms.net 来自英国的网络通信框架 官方网址 www.networkcomms.net 中文网址www.networkcomms.cn   发送端发送消息给接收端 ,接收端进行处理    举例:客户端把某用户信息(用户ID,用户密码)传输给服务器,服务器存储到数据库中. 发送方 1.契约类(用户信息类) [ProtoContract] public class UserInfo { [ProtoMember(1)] public string UserID; [ProtoM

android Java BASE64编码和解码二:图片的编码和解码

1.准备工作 (1)在项目中集成 Base64 代码,集成方法见第一篇博文:android Java BASE64编码和解码一:基础 (2)添加 ImgHelper 工具类 package com.app21; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import android.graphics.Bitmap; import android

六、网络数据编码与解码

编码:是指将一组字符转换为一个字节序列的过程. 解码:将一个编码字节序列转换为一组字符的过程. 为什么需要编码和解码因为通过网络传递的数据必须是字节序列. 常用编码:ASCII.Unicode.UTF.国标码 常用编码 ASCII码: 每个字符均为7位,主要针对英文. UNICODE码:每个字符均占两个字节. UTF码:通用转换码,主要解决编码容量问题, 常用有 (1)UTF-8:用1到4个字节编码一个UNICODE字符 (2)UTF-16:将每个字符编码为1至2个16位整数组成的序列 (3)U

Android网络通信Volley框架源代码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

Java实现二维码QRCode的编码和解码

涉及到的一些主要类库,方便大家下载: 编码lib:Qrcode_swetake.jar   (官网介绍-- http://www.swetake.com/qr/index-e.html) 解码lib:qrcode.jar                 (官网介绍-- http://sourceforge.jp/projects/qrcode/) [一].编码: Java代码QRCodeEncoderHandler.java package michael.qrcode; import java