前言

本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo。服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了。同时用MINA2和Netty4分别实现服务端的目的,是因为很多人都在纠结到底是用MINA还是Netty来实现高并发的Java网络通信服务端,在此干脆两个都实现了,就看你怎么选择了,够吊吧。

NIO框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3Netty4(Netty5已经被取消开发了:详见此文),本次将使用MINA2和Netty4来实现服务端的代码。

实际上,MINA2和Netty4的官方代码里已经有UDP通信的Demo代码,但客户端并不是基于现今流行的移动端(主要是Android和iOS端)来实现,本文将演示用iOS客户端来实现这种跨平台的双向网络通信。演示Demo中,已经解决跨平台通信时的乱码、数据字节异常等问题,请继续往下阅读。

学习交流

- 更多即时通讯技术资料:http://www.52im.net/forum.php?mod=collection&op=all

- 移动端即时通讯交流群:215891622 推荐

《NIO框架入门》系列文章博客园首发

有关MINA和Netty的入门文章很多,但多数都是复制、粘贴的未经证实的来路不明内容,对于初次接触的人来说,一个可以运行且编码规范的Demo,显然要比各种“详解”、“深入分析”之类的要来的直接和有意义。本系列入门文章正是基于此种考虑而写,虽无精深内容,但至少希望对初次接触MINA、Netty的人有所启发,起到抛砖引玉的作用。

本文是《NIO框架入门》系列文章中的第3篇,目录如下:

本文亮点

  • 客户端基于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做了进一步封装,使得开发者更易使用。

[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输出哦。

[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)

【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战的更多相关文章

  1. 【原创】NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战

    概述 本文演示的是一个Android客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo. 当前由于NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能. ...

  2. 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示

    前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...

  3. 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示

    申明:本文由作者基于日常实践整理,希望对初次接触MINA.Netty的人有所启发.如需与作者交流,见文签名,互相学习. 学习交流 更多学习资料:点此进入 推荐 移动端即时通讯交流: 215891622 ...

  4. 34、Shiro框架入门三,角色管理

    //首先这里是java代码,就是根据shiro-role.ini配置文件中的信息来得到role与用户信息的对应关系//从而来管理rolepublic class TestShiroRoleTest e ...

  5. 32、shiro 框架入门三

    1.AuthenticationStrategy实现 //在所有Realm验证之前调用 AuthenticationInfo beforeAllAttempts( Collection<? ex ...

  6. 使用Spring框架入门三:基于XML配置的AOP的使用

    一.引入Jar包 <!--测试1使用--> <dependency> <groupId>org.springframework</groupId> &l ...

  7. 【原创】新手入门一篇就够:从零开发移动端IM

    一.前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响. 做为IM开发者或即将成为IM开发者的技术人员 ...

  8. Thinkphp入门三—框架模板、变量(47)

    原文:Thinkphp入门三-框架模板.变量(47) [在控制器调用模板] display()   调用当前操作名称的模板 display(‘名字’)  调用指定名字的模板文件 控制器调用模板四种方式 ...

  9. 高性能NIO框架Netty入门篇

    http://cxytiandi.com/blog/detail/17345 Netty介绍 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具 ...

随机推荐

  1. Eclipse JAVA项目的 目录结构 和 导入

    说明:本文所有测试以java工程为例: 1. Eclipse下的java工程目录 eclipse的基本工程目录叫做workspace,每个运行时的eclipse实例只能对应一个workspace,也就 ...

  2. WEB响应布局

    [15/06月,15] em是相对长度单位.相对于当前对象内文本的字体尺寸.如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸.(引自CSS2.0手册) 任意浏览器的默认字体高都是1 ...

  3. XStream学习笔记

    XStream 所需jar包: xstream-1.3.jar xpp3_min-1.1.4c.jar xmlpull-1.1.3.1.jar 目录: 1.注解去除,标签中带有包名的节点 2.注解修改 ...

  4. Hbase随笔

    大数据时代的数据量是超大规模的,传统的关系数据库已经很难存储和管理这些数据了,为了存储海量数据,我们有了HDFS,它可以把成千上万台服务器上的硬盘聚集成一块超级大的硬盘,为了让这些数据产生价值,我们有 ...

  5. 【转载】CentOS6.5_X64下安装配置MongoDB数据库

    [转载]CentOS6.5_X64下安装配置MongoDB数据库 2014-05-16 10:07:09|  分类: 默认分类|举报|字号 订阅      下载LOFTER客户端 本文转载自zhm&l ...

  6. 第一次自己写jquery图片延迟加载插件,不通用,但修改一下还是可以使用到很多页面上的

    不断修改完善中…… /*! * jquery.lazyoading.js *自定义的页面图片延迟加载插件,比网上的jquery.lazyload简单,也更适合自己的网站 *使用方法: 把img 的cl ...

  7. Python学习笔记1——Python基础

    一. 数据类型和变量 整数:十六进制用0x前缀和0-9,a-f表示 浮点数:小数,科学计数法:10用e代替:整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(包括除法),浮点数运算则可 ...

  8. halcon车牌的识别

    read_image (Audi2, 'audi2') fill_interlace (Audi2, ImageFilled, 'odd') dev_set_color('green') thresh ...

  9. 多个div居中显示

    页面中有多个div时我们希望并排居中显示,可以通过在并排显示的div上一层再加一个div,设定宽度,然后让其居中显示达到需要的效果. 关键是要对外层div设定宽度. <!DOCTYPE html ...

  10. 强大好用的"文本"编辑器

    1 editplugs 说明:EditPlus是一款由韩国 Sangil Kim (ES-Computing)出品的小巧但是功能强大的可处理文本.HTML和程序语言的Windows编辑器,你甚至可以通 ...