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

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

  1. 在Linux终端中快速生成、解码二维码

    我们要实现两个功能: 解码Linux屏幕上的二维码,将结果输出在终端 在终端中将字符串转为二维码,直接显示二维码在终端中以供扫描 实现方法 生成二维码 qrencode是一个常见的生成二维码的CLI程 ...

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

    1 下载zxing2.1 2 本代码配置环境:eclipse.java1.6.windows8.zxing2.1 3 解压后将文件夹里面core/src下面的com文件夹导入到eclipse工程(工程 ...

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

    1.准备工作 (1)在项目中集成 Base64 代码,集成方法见第一篇博文:android Java BASE64编码和解码一:基础 (2)添加 ImgHelper 工具类 package com.a ...

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

    对象序列化简而言之,将对象转为字符串.在数据的传输过程中,经常会使用到对象序列化. javascript中常用的对象序列化:JSON.stringify(); javascript中常用的对象反序列化 ...

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

    尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列: ...

  6. 使用zbar 解码二维码 条形码

    #!/usr/bin/env python # coding: u8 import os import zbar import Image import urllib import uuid def ...

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

    涉及到的一些主要类库,方便大家下载: 编码lib:Qrcode_swetake.jar   (官网介绍-- http://www.swetake.com/qr/index-e.html) 解码lib: ...

  8. 用python随随便便做一个二维码叭~~~

    Python是目前最好的编程语言之一.由于其可读性和对初学者的友好性,已被广泛使用. 那么要想学会并掌握Python,可以实战的练习项目是必不可少的. 接下来,我将给大家介绍非常实用的Python项目 ...

  9. c#网络通信框架networkcomms内核解析 序言

    NetworkComms网络通信框架序言 networkcomms是我遇到的写的最优美的代码,很喜欢,推荐给大家:) 基于networkcomms2.3.1开源版本( gplv3)协议,写了一些文章, ...

随机推荐

  1. PS如何批量整理图片大下

    https://jingyan.baidu.com/article/cdddd41cc7849853cb00e193.html

  2. 用Martini、websocket实现单机版聊天室

    ChatRoom A stand-alone ChatRoom in Martini Please Star https://github.com/renleimlj/ChatRoom Interfa ...

  3. Python学习日记之字典深复制与浅复制

    Python中通过copy模块有两种复制(深复制与浅复制) copy 浅复制 复制时只会复制父对象,而不会复制对象的内部的子对象. deepcopy 深复制 复制对象及其子对象 因此,复制后对原dic ...

  4. 学习一波redis

    作为一名合格的java程序员,做web开发的,除了java,mysql,免不了用到内存数据库redis. 身为一名菜鸟,是时候来一波redis从入门到放弃了,哦不,从入门到精通.. 一.安装部署red ...

  5. Win7 下 PB (PowerBuilder) Insert Control 崩溃的解决办法

    环境: WIN7 x86  PB8.0, x64系统目录不同,不过也可以试试 Insert -> OLE... -> Insert Control  - 崩溃 如果网上提供的办法解决不了你 ...

  6. 00JAVA EE

    JAVA EE 三层架构 我们的开发架构一般都是基于两种形式,一种是C/S架构,也就是客户端/服务器,另一种是B/S架构,也就是浏览器服务器.在JavaEE开发中,几乎全都是基于B/S架构的开发.那么 ...

  7. 华登区块狗系统APP开发

    华登区块狗系统开发,陈翎:{.l8O..285l..l22O.}华登区块狗软件开发,华登区块狗APP开发,华登区块狗模式开发,华登区块狗现成源码,狗狗集市理财模式开发 华登区块狗是什么?华登区块狗ap ...

  8. vue-cli的项目加入骨架屏

    在原生APP中我们经常可以看到,打开app时候,内容还没出来,app会被别的内容替代,这样很好的提升了用户体验.那么在webApp中,我们如何避免白屏的尴尬情况呢?可以通过 vue-skeleton- ...

  9. 暴力搜索+散列--P1008 三连击

    题目描述 将1,2, ⋯,9共9个数分成3组,分别组成3个三位数,且使这3个三位数构成1:2:3的比例,试求出所有满足条件的3个三位数. 输入输出格式 输入格式: 木有输入 输出格式: 若干行,每行3 ...

  10. 用php生成HTML文件的类

    目的 用PHP生成HTML文档, 支持标签嵌套缩进, 支持标签自定义属性 起因 这个东西确实也是心血来潮写的, 本来打算是输出HTML片段用的, 但后来就干脆写成了一个可以输出完整HTML的功能; 我 ...