【原创】nio框架入门(三):ios与mina2、netty4的跨平台udp双向通信实战 -凯发k8网页登录

我的最新工程mobileimsdk:http://git.oschina.net/jackjiang/mobileimsdk
posts - 336, comments - 13, trackbacks - 0, articles - 0

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

nio框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是mina和netty了,mina目前的主要版本是、而netty的主要版本是和(netty5已经被取消开发了:),本次将使用mina2和netty4来实现服务端的代码。

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

- 更多即时通讯技术资料:

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

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

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

  • 《》
  • 《》
  • 《》(本文
  • 《nio框架入门(四):android与mina2、netty4的跨平台udp双向通信实战》
  • 客户端基于ios移动端平台实现:
    通常这类跨平台的网络通信例子很难找,本文已解决跨平台通信的适配问题,是个难得的实践入门示例;
  • 完整可执行源码、方便学习:
    完整的demo源码,适合新手直接运行,便于学习和研究。
  • demo中的代码源自作者的开源工程,有实用价值:
    源码均修改自作者的即时通讯开源工程 ,只是为了方便学习理解而作了简化,有一定的实用价值;

本文要演示的demo包含两部分,ios udp客户端和nio框架实现的服务端(包括mina2和netty4实现两个方案),客户端每隔5秒向服务端发送消息,而服务端在收到消息后马上回复一条消息给客户端。

如上所述,服务端和客户端都要实现消息的发送和接收,即实现跨平台的双向通信。如果有心的话,稍加改造,也就很容易实现一个简陋的聊天程序了。下节将将给出真正的实现代码。

[step 1] 去github上下载最新的cocoaasyncsocket:

cocoaasyncsocket源码地址:,如下图:

补充说明:ios里的网络编程有多种途径实现(),本文选择的是ios里非常热门的 cocoaasyncsocket 工程,它对ios原生网络api做了进一步封装,使得开发者更易使用。

[step 2] 建好xcode工程,准备开撸:

建好工程后把cocoaasyncsocket的源码引用进来就行了,如下图:

补充说明:如何新建一个xcode工程请自行百度之,按照系统默认的简单建立一个就好了,本例不需要作额外配置和额外的系统库引用。

[1] 客户端主类 viewcontroller.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//  凯发天生赢家一触即发官网 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//  凯发天生赢家一触即发官网 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//  凯发天生赢家一触即发官网 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实现服务端的准备工作请见:《》
- mina2实现服务端的准备工作请见:《》

因两套方案的服务端代码都不复杂,且已经本系列文章的前两篇中详细介绍,本文就不在重复粘贴了。

- netty4实现的服务端请见:《》
- mina2实现的服务端请见:《》

[1] 客户端运行结果:


[2] 服务端运行结果(mina2方案):


[3] 服务端运行结果(netty4方案):

本文中的客户端代码是从 的ios端中复制出来的(只是为了方便理解而做了大幅简化),有兴趣的可以看看 mobileimsdk 、,简化一下可以用作你自已的各种用途。

如果你阅读过本系列的《》和《》,应该能明显地感觉的出来mina2的udp服务端api接口使用要是netty4的繁琐,而且mina2还存在独立客户端(非依赖于mina2客户端)实现时的多余字节和乱码问题。但个人认为mina2的代码风格更符合一般程序员的编码习惯,更好懂一些,而netty4因历经多个大版本的进化,虽起来非常简洁,但实现并不是那么直观。当然,至于mina还是netty,请客观一评估和使用,因为二者并无本质区别。

[1] mina和netty的源码在线学习和查阅:
mina-2.x地址是:
mina-1.x地址是:
netty-4.x地址是:
netty-3.x地址是:

[2] mina和netty的api文档在线查阅:
mina-2.x api文档(在线版):
mina-1.x api文档(在线版):
netty-4.x api文档(在线版):
netty-3.x api文档(在线版):

[3] 更多有关nio编程的资料:
请进入精华资料专辑:

[4] 有关im聊天应用、消息推送技术的资料:
请进入精华资料专辑:

[5] 技术交流和学习:
可直接进入  讨论和学习网络编程、im聊天应用、消息推送应用的开发。

如需完整eclipse源码工程请联系作者,或者进入链接  自行下载。

完整源码工程截图如下:

    

截图说明:左右截图是ios客户端的demo源码、右边截图是服务端(mina2和netty4两个方案)。

(本文同步发布于:)

作者: (点击作者姓名进入github) 
凯发k8网页登录主页:
凯发天生赢家一触即发官网的联系方式:qq: 413980957, 微信: hellojackjiang,email: jb2011@163.com 
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往  找到我)。 



作者: (点击作者姓名进入github)
出处:
交流:欢迎加入即时通讯开发交流群
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往 找到我)。


只有注册用户后才能发表评论。


网站导航:
              
 
jack jiang的 mail: jb2011@163.com, 联系qq: 413980957, 微信: hellojackjiang
网站地图