CocoaAsyncSocket data encoding and decoding for network communication (2), cocoaasyncsocket
Data encoding and decoding for CocoaAsyncSocket Network Communication (2)
In the tcp connection used by CocoaAsyncSocket for network communication (1), we have used CocoaAsyncSocket to encapsulate our socket connection.
This article mainly introduces encoder and decoder to modularize the shared content.
Brief description:
In tcp applications, data is transmitted in the form of two-mechanism bytes.
Generally, the corresponding data structure/data object is constructed for the business protocol, and then converted to binary data and sent to the server during use.
However, we use different tcp Protocols in different application and business scenarios, so the reusability of each socket module is very poor, even if the underlying content is exactly the same, it is also because the coupling is too high during implementation, and thus all needs to be re-developed. To achieve modular reuse, I introduced encoder and decoder following mina and netty.
Interface Framework Design:
The following design interfaces are available for subsequent expansion and custom implementation of your own encoder/decoder.
Data Packets
Basic interface definition of data packets (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
Interface Definition of data packet content (RHSocketPacketContent. h): (added the timeout field, mainly for sent data packets)
#import <Foundation/Foundation.h>#import "RHSocketPacket.h"@protocol RHSocketPacketContent <RHSocketPacket>@property (nonatomic, readonly) NSTimeInterval timeout;@optional- (void)setTimeout:(NSTimeInterval)timeout;@end
Tcp Encoder
Encoderprotocol. 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 Decoder
Decoder interface definition (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 <strong>) output tag :( long) tag; // The returned value is returned, to process data packets and package @ end
Okay, after several adjustments, the programmer's inner mind is countless times entangled, and the interface definition is finally completed. Next let's look at how to combine and use it.
When using the preceding socket connection, you still need to implement the delegate method of delegate,
Copy is required for different apps, and data [encoding], [decoding], and [distribution] are implemented.
And then the corresponding scenario. In fact, [encoding] and [distribution] can be modularized and independent. Let's start from here.
Architecture integration call
First, introduce a service to help us [connect], [encoding], [decoding], and [distribution.
Paste the Code directly.
RHSocketService. h file:
#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 file:
#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
The test code is as follows:
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];
Code call methods and procedures:
1-use the startServiceWithHost method to connect to the server.
2-The client sends data to the server. After successful connection, you can call asyncSendPacket to send data packets. You can also send a knotifsocketpacketrequest notification through notification to send data packets. During data transmission, data is sent to the server through the encoder After encoding.
3-when the server pushes data to the client, a callback of the didReceiveData method is triggered, and a notification is sent to the corresponding distributor After decoding through the decoder (the distributor adjusts the implementation for the service)
The default decoder and encoder are used in the code. You can also customize the maximum delimiter and data frame values for Data Separator Processing. The Code is as follows:
Delimiter Encoder
RHSocketDelimiterEncoder. h file:
# Import <Foundation/Foundation. h> # import "RHSocketEncoderProtocol. h "/*** for data packet delimiter encoder * the maximum value of each frame in the default data packet is 8192 (maxFrameSize = 8192) * The default delimiter of each frame of the data packet is 0xff (delimiter = 0xff) * // @ interface RHSocketDelimiterEncoder: NSObject <RHSocketEncoderProtocol> @ property (nonatomic, assign) NSUInteger maxFrameSize; @ property (nonatomic, assign) uint8_t delimiter; @ end
RHSocketDelimiterEncoder. m file:
#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
Separator Decoder
RHSocketDelimiterDecoder. h file:
# Import <Foundation/Foundation. h> # import "RHSocketDecoderProtocol. h "/*** for data packet delimiter decoder * the maximum value of each frame in the default data packet is 8192 (maxFrameSize = 8192) * The default delimiter of each frame of the data packet is 0xff (delimiter = 0xff) * // @ interface RHSocketDelimiterDecoder: NSObject <RHSocketDecoderProtocol> @ property (nonatomic, assign) NSUInteger maxFrameSize; @ property (nonatomic, assign) uint8_t delimiter; @ end
RHSocketDelimiterDecoder. m file:
#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
Summary:
The server code has not been provided yet, but the socket framework is basically complete. You can implement custom encoder and decoder based on different Protocol extensions.
The test code accesses the Baidu homepage, which is an http protocol and requires an additional encoder and decoder for implementing the http protocol.
Next, we will implement simple encoder and decoder for http.
--------------------
Reprinted please indicate the source, thank you
Email: zhu410289616@163.com
Qq: 410289616
Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.