【原創】NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰

前言

本文將演示一個iOS客戶端程序,經過UDP協議與兩個典型的NIO框架服務端,實現跨平臺雙向通訊的完整Demo。服務端將分別用MINA2和Netty4進行實現,而通訊時服務端你只需選其一就好了。同時用MINA2和Netty4分別實現服務端的目的,是由於不少人都在糾結究竟是用MINA仍是Netty來實現高併發的Java網絡通訊服務端,在此乾脆兩個都實現了,就看你怎麼選擇了,夠吊吧。

NIO框架的流行,使得開發大併發、高性能的互聯網服務端成爲可能。這其中最流行的無非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3Netty4Netty5已經被取消開發了詳見此文),本次將使用MINA2和Netty4來實現服務端的代碼。

實際上,MINA2和Netty4的官方代碼裏已經有UDP通訊的Demo代碼,但客戶端並非基於現今流行的移動端(主要是Android和iOS端)來實現,本文將演示用iOS客戶端來實現這種跨平臺的雙向網絡通訊。演示Demo中,已經解決跨平臺通訊時的亂碼、數據字節異常等問題,請繼續往下閱讀。php

學習交流

- 更多即時通信技術資料:http://www.52im.net/forum.php?mod=collection&op=all
html

- 移動端即時通信交流羣:215891622 推薦java

《NIO框架入門》系列文章博客園首發

有關MINA和Netty的入門文章不少,但多數都是複製、粘貼的未經證明的來路不明內容,對於初次接觸的人來講,一個能夠運行且編碼規範的Demo,顯然要比各類「詳解」、「深刻分析」之類的要來的直接和有意義。本系列入門文章正是基於此種考慮而寫,雖無精深內容,但至少但願對初次接觸MINA、Netty的人有所啓發,起到拋磚引玉的做用。

本文是《NIO框架入門》系列文章中的第3篇,目錄以下:git

本文亮點 

  • 客戶端基於iOS移動端平臺實現:
    一般這類跨平臺的網絡通訊例子很難找,本文已解決跨平臺通訊的適配問題,是個可貴的實踐入門示例;
  • 完整可執行源碼、方便學習:
    完整的Demo源碼,適合新手直接運行,便於學習和研究。
  • Demo中的代碼源自做者的開源工程,有實用價值:
    源碼均修改自做者的即時通信開源工程 MobileIMSDK,只是爲了方便學習理解而做了簡化,有必定的實用價值;

本文Demo的場景邏輯

本文要演示的Demo包含兩部分,iOS UDP客戶端和NIO框架實現的服務端(包括MINA2和Netty4實現兩個方案),客戶端每隔5秒向服務端發送消息,而服務端在收到消息後立刻回覆一條消息給客戶端。

如上所述,服務端和客戶端都要實現消息的發送和接收,即實現跨平臺的雙向通訊。若是有心的話,稍加改造,也就很容易實現一個簡陋的聊天程序了。下節將將給出真正的實現代碼。程序員

iOS客戶端準備工做

[Step 1] 去Github上下載最新的CocoaAsyncSocket:

CocoaAsyncSocket源碼地址:https://github.com/52im/CocoaAsyncSocket,以下圖:

補充說明:iOS裏的網絡編程有多種途徑實現(具體請參看此文),本文選擇的是iOS裏很是熱門的 CocoaAsyncSocket 工程,它對iOS原生網絡API作了進一步封裝,使得開發者更易使用。

github

[Step 2] 建好XCode工程,準備開擼:

建好工程後把CocoaAsyncSocket的源碼引用進來就好了,以下圖:

補充說明:如何新建一個XCode工程請自行百度之,按照系統默認的簡單創建一個就行了,本例不須要做額外配置和額外的系統庫引用。編程

iOS客戶端代碼實現

[1] 客戶端主類 ViewController.m:

//  Copyright (C) 2016 即時通信網(52im.net)- 即時通信開發者社區.
//  All rights reserved.
//  Created by JackJiang on 16/06/22.
#import "ViewController.h"
#import "LocalUDPSocketProvider.h"
#import "LocalUDPDataSender.h"
#import "CharsetHelper.h"
#import "UDPUtils.h"
 
@interface ViewController ()
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
     
    // 初始化socket
    [[LocalUDPSocketProvider sharedInstance] initialLocalUDPSocket];
    // 注意:執行延遲的單位是秒哦
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(doSend) userInfo:nil repeats:YES];
    [timer fire];
}
 
- (void)doSend
{
    NSString *toServer = [NSString stringWithFormat:@"Hi,我是iOS客戶端,個人時間戳 %li",[UDPUtils getTimeStampWithMillisecond_l]];
    [[LocalUDPDataSender sharedInstance] send:[CharsetHelper getBytesWithString:toServer]];
}
 
@end

補充說明:本類本是界面主類,但爲了省事,沒有去寫UI代碼,只是做爲本次Demo的主入口類而已,須要查看數據輸出的,請在XCode控制檯看查看log輸出哦。

api

[2] 客戶端Socket管理類 LocalUDPSocketProvider.m:

//  Copyright (C) 2016 即時通信網(52im.net)- 即時通信開發者社區.
//  All rights reserved.
//  Created by JackJiang on 16/06/22.
#import "LocalUDPSocketProvider.h"
#import "GCDAsyncUdpSocket.h"
#import "ConfigEntity.h"
#import "CompletionDefine.h"
 
@interface LocalUDPSocketProvider ()
@property (nonatomic, retain) GCDAsyncUdpSocket *localUDPSocket;
@property (nonatomic, copy) ConnectionCompletion connectionCompletionOnce_;
@end
 
@implementation LocalUDPSocketProvider
 
// 本類的單例對象
static LocalUDPSocketProvider *instance = nil;
 
+ (LocalUDPSocketProvider *)sharedInstance
{
    if (instance == nil)
        instance = [[super allocWithZone:NULL] init];
    return instance;
}
 
- (GCDAsyncUdpSocket *)initialLocalUDPSocket
{
    NSLog(@"【IMCORE】new GCDAsyncUdpSocket中...");
     
    // ** Setup our socket.
    self.localUDPSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
     
    // ** START udp socket
    // 本地綁定端口合法性檢查
    int port = [ConfigEntity getLocalUdpSendAndListeningPort];
    if (port < 0 || port > 65535)
        port = 0;
    NSError *error = nil;
    // 綁定到指定端口(以便收發數據)
    if (![self.localUDPSocket bindToPort:port error:&error])
    {
        NSLog(@"【IMCORE】localUDPSocket建立時出錯,緣由是 bindToPort: %@", error);
        return nil;
    }
    // 開啓收數據處理
    if (![self.localUDPSocket beginReceiving:&error])
    {
        NSLog(@"【IMCORE】localUDPSocket建立時出錯,緣由是 beginReceiving: %@", error);
        return nil;
    }
    NSLog(@"【IMCORE】localUDPSocket建立已成功完成.");
    return self.localUDPSocket;
}
。。。。。。
 
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
      fromAddress:(NSData *)address
withFilterContext:(id)filterContext
{
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (msg)
        NSLog(@"【UDP_SOCKET】【NOTE】>>>>>> 收到服務端的消息: %@", msg);
    else
    {
        NSString *host = nil;
        uint16_t port = 0;
        [GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address];
        NSLog(@"【UDP_SOCKET】RECV: Unknown message from: %@:%hu", host, port);
    }
}
 
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address
{
    NSLog(@"【UDP_SOCKET】成收到的了UDP的connect反饋, isCOnnected?%d", [sock isConnected]);
    // 鏈接結果回調
    if(self.connectionCompletionOnce_ != nil)
        self.connectionCompletionOnce_(YES);
}
 
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error
{
    NSLog(@"【UDP_SOCKET】成收到的了UDP的connect反饋,但鏈接沒有成功, isCOnnected?%d", [sock isConnected]);
    // 鏈接結果回調
    if(self.connectionCompletionOnce_ != nil)
        self.connectionCompletionOnce_(NO);
}
 
@end

補充說明:因代碼較多,文中沒有列出該類的全部代碼,請前往文末下載完整XCode工程自行查看哦。

網絡

[3] 數據發送實現類 LocalUDPDataSender.m:

//  Copyright (C) 2016 即時通信網(52im.net)- 即時通信開發者社區.
//  All rights reserved.
//  Created by JackJiang on 16/06/22.
#import "LocalUDPDataSender.h"
#import "CharsetHelper.h"
#import "GCDAsyncUdpSocket.h"
#import "LocalUDPSocketProvider.h"
#import "ConfigEntity.h"
#import "UDPUtils.h"
#import "CompletionDefine.h"
 
@implementation LocalUDPDataSender
 
// 本類的單例對象
static LocalUDPDataSender *instance = nil;
 
- (BOOL) send:(NSData *)dataWithBytes
{
    // 得到UDPSocket實例
    GCDAsyncUdpSocket *ds = [[LocalUDPSocketProvider sharedInstance] getLocalUDPSocket];
    // 確保發送數據開始前,已經進行connect的操做:若是Socket沒有「鏈接」上服務端,嘗試「鏈接」一次
    if(ds != nil && ![ds isConnected])
    {
        // 這次數據只在「鏈接」成功後發出,「鏈接」成功則會調用此回調block代碼
        ConnectionCompletion observerBlock = ^(BOOL connectResult) {
            // 成功創建了UDP鏈接後就把包發出去
            if(connectResult)
                [UDPUtils sendImpl:ds withData:dataWithBytes];
        };
        // 調置鏈接回調
        [[LocalUDPSocketProvider sharedInstance] setConnectionObserver:observerBlock];
         
        NSError *connectError = nil;
        BOOL connectResult = [[LocalUDPSocketProvider sharedInstance] tryConnectToHost:&connectError withSocket:ds completion:observerBlock];
        // 若是鏈接意圖沒有成功發出則返回錯誤碼
        return connectResult;
    }
    else
        return [UDPUtils sendImpl:ds withData:dataWithBytes];
}
 
// 獲取本類的單例。使用單例訪問本類的全部資源是惟一的合法途徑。
+ (LocalUDPDataSender *)sharedInstance
{
    if (instance == nil)
        instance = [[super allocWithZone:NULL] init];
    return instance;
}
 
@end

服務端準備工做

本文將分別基於MINA2和Netty4實現兩套服務端(你只須要使用其中之一便可),服務端準備工做已在本系列文章的前兩篇詳細記錄了,具體以下:

- Netty4實現服務端的準備工做請見:NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示
- MINA2實現服務端的準備工做請見:NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示併發

服務端代碼實現

因兩套方案的服務端代碼都不復雜,且已經本系列文章的前兩篇中詳細介紹,本文就不在重複粘貼了。

- Netty4實現的服務端請見:NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示
- MINA2實現的服務端請見:NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示

Demo 運行行截圖

[1] 客戶端運行結果:


[2] 服務端運行結果(MINA2方案):


[3] 服務端運行結果(Netty4方案):

本文小結

本文中的客戶端代碼是從開源即時通信框架MobileIMSDK 的iOS端中複製出來的(只是爲了方便理解而作了大幅簡化),有興趣的能夠看看 MobileIMSDK Android端Server端,簡化一下能夠用做你自已的各類用途。

若是你閱讀過本系列的《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》和《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》,應該能明顯地感受的出來MINA2的UDP服務端API接口使用要是Netty4的繁瑣,並且MINA2還存在獨立客戶端(非依賴於MINA2客戶端)實現時的多餘字節和亂碼問題。但我的認爲MINA2的代碼風格更符合通常程序員的編碼習慣,更好懂一些,而Netty4因歷經多個大版本的進化,雖起來很是簡潔,但實現並非那麼直觀。固然,至於MINA仍是Netty,請客觀一評估和使用,由於兩者並沒有本質區別。

更多學習資源

[1] MINA和Netty的源碼在線學習和查閱:
MINA-2.x地址是:http://docs.52im.net/extend/docs/src/mina2/
MINA-1.x地址是:http://docs.52im.net/extend/docs/src/mina1/
Netty-4.x地址是:http://docs.52im.net/extend/docs/src/netty4/
Netty-3.x地址是:http://docs.52im.net/extend/docs/src/netty3/

[2] MINA和Netty的API文檔在線查閱:
MINA-2.x API文檔(在線版):http://docs.52im.net/extend/docs/api/mina2/
MINA-1.x API文檔(在線版):http://docs.52im.net/extend/docs/api/mina1/
Netty-4.x API文檔(在線版):http://docs.52im.net/extend/docs/api/netty4/
Netty-3.x API文檔(在線版):http://docs.52im.net/extend/docs/api/netty3/

[3] 更多有關NIO編程的資料:
請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&action=view&ctid=9

[4] 有關IM聊天應用、消息推送技術的資料:
請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&op=all

[5] 技術交流和學習:
可直接進入 即時通信開發者社區 討論和學習網絡編程、IM聊天應用、消息推送應用的開發。

完整源碼工程下載

博客園貌似上傳不了附件,如需完整Eclipse源碼工程請聯繫做者,或者進入連接 http://www.52im.net/thread-378-1-1.html 自行下載。

完整源碼工程截圖以下:

    

截圖說明:左右截圖是iOS客戶端的Demo源碼、右邊截圖是服務端(MINA2和Netty4兩個方案)。

(本文同步發佈於:http://www.52im.net/thread-378-1-1.html)

相關文章
相關標籤/搜索