利用RHSocketKit構建自定義協議通信

利用RHSocketKit構建自定義協議通信

在網絡傳輸中,tcp/ip協議只是網絡的基礎,分別屬於傳輸層和網絡層。我們各種應用之間的通信需要依賴各種各樣的協議,是建立在tcp/ip之上的,比如典型的http協議。

在開發過程中,一般我們制定的協議需要兩個部分,header和content。

1-header:協議頭部,放置一些meta信息。

2-content:應用之間交互的信息主體。


例如:分隔符delimiter-2個字節|數據包類型type-2個字節|長度length(不含包頭長度)-4個字節|數據信息主體content-length個子節

其中delimiter,type,length就是header信息,content就是交互的主體。


我們來利用RHSocketKit構造一個數據包對象。

#import "RHSocketPacketContext.h"

@interface RHSocketCustomRequest : RHSocketPacketRequest
//這裏,我們把content數據放在夫類的object變量中。
//length可以通過object數據直接獲得,不再申明額外的變量。
@property (nonatomic, assign) int16_t fenGeFu;
@property (nonatomic, assign) int16_t dataType;

@end


數據包已經創建好了,那麼我們在RHSocketKit中如何去對這種自定義的協議編碼(Encode)呢?

定義編碼協議RHSocketEncoderProtocol和解碼協議RHSocketDecoderProtocol。


RHSocketCustom0330Encoder:編碼器,將發送包的數據從一種格式轉換成二進制數組。

RHSocketCustom0330Decoder:解碼器,將接收包的二進制數組轉換成可讀的變量數據。


RHSocketCustom0330Encoder文件

#import <Foundation/Foundation.h>
#import "RHSocketCodecProtocol.h"

@interface RHSocketCustom0330Encoder : NSObject <RHSocketEncoderProtocol>

@end

#import "RHSocketCustom0330Encoder.h"
#import "RHSocketException.h"
#import "RHSocketUtils.h"
#import "RHSocketCustomRequest.h"

@implementation RHSocketCustom0330Encoder

- (void)encode:(id<RHUpstreamPacket>)upstreamPacket output:(id<RHSocketEncoderOutputProtocol>)output
{
    id object = [upstreamPacket object];
    if (![object isKindOfClass:[NSData class]]) {
        [RHSocketException raiseWithReason:@"[Encode] object should be NSData ..."];
        return;
    }
    
    NSData *data = object;
    if (data.length == 0) {
        return;
    }//
    
    RHSocketCustomRequest *req = (RHSocketCustomRequest *)upstreamPacket;
    NSUInteger dataLen = data.length;
    //<strong><span style="color:#ff0000;">注意這裏,按照協議的描述,順序把數值轉成二進制填充到對應的位置,然後output輸出</span></strong>
    NSMutableData *sendData = [[NSMutableData alloc] init];
    //分隔符 2個字節
    [sendData appendData:[RHSocketUtils bytesFromUInt16:req.fenGeFu]];
    //數據包類型 2個字節
    [sendData appendData:[RHSocketUtils bytesFromUInt16:req.dataType]];
    //長度(不含包頭長度) 4個字節
    [sendData appendData:[RHSocketUtils bytesFromUInt32:(uint32_t)dataLen]];
    //數據包 dataLen個字節
    [sendData appendData:data];
    
    NSTimeInterval timeout = [upstreamPacket timeout];
    
    RHSocketLog(@"timeout: %f, sendData: %@", timeout, sendData);
    [output didEncode:sendData timeout:timeout];
}

@end

RHSocketCustom0330Decoder文件

#import <Foundation/Foundation.h>
#import "RHSocketCodecProtocol.h"

@interface RHSocketCustom0330Decoder : NSObject <RHSocketDecoderProtocol>

@end


#import "RHSocketCustom0330Decoder.h"
#import "RHSocketException.h"
#import "RHSocketUtils.h"
#import "RHSocketCustomResponse.h"

@interface RHSocketCustom0330Decoder ()
{
    NSUInteger _countOfLengthByte;
}

@end

@implementation RHSocketCustom0330Decoder

- (instancetype)init
{
    if (self = [super init]) {
        _countOfLengthByte = 8;
    }
    return self;
}

- (NSInteger)decode:(id<RHDownstreamPacket>)downstreamPacket output:(id<RHSocketDecoderOutputProtocol>)output
{
    id object = [downstreamPacket object];
    if (![object isKindOfClass:[NSData class]]) {
        [RHSocketException raiseWithReason:@"[Decode] object should be NSData ..."];
        return -1;
    }
    
    NSData *downstreamData = object;
    NSUInteger headIndex = 0;
    
    while (downstreamData && downstreamData.length - headIndex >= _countOfLengthByte) {
        NSData *headData = [downstreamData subdataWithRange:NSMakeRange(headIndex, _countOfLengthByte)];
        //去第4個字節開始的4個字節長度
        NSData *lenData = [headData subdataWithRange:NSMakeRange(4, 4)];
        //長度字節數據,可能存在高低位互換,通過數值轉換工具處理
        NSUInteger frameLen = [RHSocketUtils uint32FromBytes:lenData];
        
        //剩餘數據,不是完整的數據包,則break繼續讀取等待
        if (downstreamData.length - headIndex < _countOfLengthByte + frameLen) {
            break;
        }
        //數據包(長度+內容)
        NSData *frameData = [downstreamData subdataWithRange:NSMakeRange(headIndex, _countOfLengthByte + frameLen)];
        
        //去除數據長度後的數據內容
        RHSocketCustomResponse *rsp = [[RHSocketCustomResponse alloc] init];
        rsp.fenGeFu = [RHSocketUtils uint16FromBytes:[headData subdataWithRange:NSMakeRange(0, 2)]];
        rsp.dataType = [RHSocketUtils uint16FromBytes:[headData subdataWithRange:NSMakeRange(2, 2)]];
        rsp.object = [frameData subdataWithRange:NSMakeRange(_countOfLengthByte, frameLen)];
        
        [output didDecode:rsp];
        
        //調整已經解碼數據
        headIndex += frameData.length;
    }//while
    return headIndex;
}

@end

codec組件已經準備就緒,接下來我們增加調用控制。RHSocketKit中已經提供了一個單例的service,已經提取和封裝了可重用代碼。只需要重載編解碼器就可以了。

    //這裏的服務器對應RHSocketServerDemo,連接之前,需要運行RHSocketServerDemo開啓服務端監聽。
    //RHSocketServerDemo服務端只是返回數據,收到的數據是原封不動的,用來模擬發送給客戶端的數據。
    NSString *host = @"127.0.0.1";
    int port = 20162;
    
    //變長編解碼。包體=包頭(包體的長度)+包體數據
    RHSocketCustom0330Encoder *encoder = [[RHSocketCustom0330Encoder alloc] init];
    RHSocketCustom0330Decoder *decoder = [[RHSocketCustom0330Decoder alloc] init];
    
    [RHSocketService sharedInstance].encoder = encoder;
    [RHSocketService sharedInstance].decoder = decoder;
    
    [[RHSocketService sharedInstance] startServiceWithHost:host port:port];

<span style="font-size:18px;">編寫一個服務端對這個通信協議作測試,我這裏使用簡單的echo服務器測試。代碼很簡單,我就不貼了。</span>
<span style="font-size:18px;">可以直接查看源碼<a target=_blank href="https://github.com/zhu410289616/RHSocketKit/tree/master/RHSocketServerDemo">RHSocketServerDemo</a>。</span>

客戶端的過程是,先連接服務器,然後發送數據包。然後服務端返回相同的數據包給客戶端,客戶端做解碼,然後輸出展示。

測試代碼如下:

#import "ViewController.h"
#import "RHSocketService.h"

#import "RHSocketCustom0330Encoder.h"
#import "RHSocketCustom0330Decoder.h"

#import "RHSocketCustomRequest.h"
#import "RHSocketCustomResponse.h"

@interface ViewController ()
{
    UIButton *_serviceTestButton;
}

@end

@implementation ViewController

- (void)loadView
{
    [super loadView];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectSocketServiceState:) name:kNotificationSocketServiceState object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectSocketPacketResponse:) name:kNotificationSocketPacketResponse object:nil];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _serviceTestButton = [UIButton buttonWithType:UIButtonTypeCustom];
    _serviceTestButton.frame = CGRectMake(20, 120, 250, 40);
    _serviceTestButton.layer.borderColor = [UIColor blackColor].CGColor;
    _serviceTestButton.layer.borderWidth = 0.5;
    _serviceTestButton.layer.masksToBounds = YES;
    [_serviceTestButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
    [_serviceTestButton setTitle:@"Test Custom Codec" forState:UIControlStateNormal];
    [_serviceTestButton addTarget:self action:@selector(doTestServiceButtonAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:_serviceTestButton];
    
}

- (void)doTestServiceButtonAction
{
    //方便多次觀察,先停止之前的連接
    [[RHSocketService sharedInstance] stopService];
    
    //這裏的服務器對應RHSocketServerDemo,連接之前,需要運行RHSocketServerDemo開啓服務端監聽。
    //RHSocketServerDemo服務端只是返回數據,收到的數據是原封不動的,用來模擬發送給客戶端的數據。
    NSString *host = @"127.0.0.1";
    int port = 20162;
    
    //變長編解碼。包體=包頭(包體的長度)+包體數據
    RHSocketCustom0330Encoder *encoder = [[RHSocketCustom0330Encoder alloc] init];
    RHSocketCustom0330Decoder *decoder = [[RHSocketCustom0330Decoder alloc] init];
    
    [RHSocketService sharedInstance].encoder = encoder;
    [RHSocketService sharedInstance].decoder = decoder;
    
    [[RHSocketService sharedInstance] startServiceWithHost:host port:port];
}

- (void)detectSocketServiceState:(NSNotification *)notif
{
    //NSDictionary *userInfo = @{@"host":host, @"port":@(port), @"isRunning":@(_isRunning)};
    //對應的連接ip和狀態數據。_isRunning爲YES是連接成功。
    //沒有心跳超時後會自動斷開。
    NSLog(@"detectSocketServiceState: %@", notif);
    
    id state = notif.object;
    if (state && [state boolValue]) {
        //連接成功後,發送數據包
        RHSocketCustomRequest *req = [[RHSocketCustomRequest alloc] init];
        req.fenGeFu = 0x24;
        req.dataType = 1234;
        req.object = [@"變長編碼器和解碼器測試數據包1" dataUsingEncoding:NSUTF8StringEncoding];
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketPacketRequest object:req];
        
        req = [[RHSocketCustomRequest alloc] init];
        req.fenGeFu = 0x24;
        req.dataType = 12;
        req.object = [@"變長編碼器和解碼器測試數據包20" dataUsingEncoding:NSUTF8StringEncoding];
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketPacketRequest object:req];
        
        req = [[RHSocketCustomRequest alloc] init];
        req.fenGeFu = 0x24;
        req.dataType = 0;
        req.object = [@"變長編碼器和解碼器測試數據包300" dataUsingEncoding:NSUTF8StringEncoding];
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSocketPacketRequest object:req];
        
    } else {
        //
    }//if
}

- (void)detectSocketPacketResponse:(NSNotification *)notif
{
    NSLog(@"detectSocketPacketResponse: %@", notif);
    
    //這裏結果,記得觀察打印的內容
    NSDictionary *userInfo = notif.userInfo;
    RHSocketCustomResponse *rsp = userInfo[@"RHSocketPacket"];
    NSLog(@"detectSocketPacketResponse data: %@", [rsp object]);
    NSLog(@"detectSocketPacketResponse string: %@", [[NSString alloc] initWithData:[rsp object] encoding:NSUTF8StringEncoding]);
}

源碼RHSocketCustomCodecDemo已經上傳到github中,可以自行下載。

(demo中是通過pod引入RHSocketKit庫的,已經包含了Podfile文件,pod install後就可以正常運行)

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

轉載請註明出處,謝謝

email: [email protected]

qq: 410289616

qq羣:330585393



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