本文将演示一个iOS客户端程序,经过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通讯的完整Demo。服务端将分别用MINA2和Netty4进行实现,而通讯时服务端你只需选其一就好了。同时用MINA2和Netty4分别实现服务端的目的,是由于不少人都在纠结究竟是用MINA仍是Netty来实现高并发的Java网络通讯服务端,在此干脆两个都实现了,就看你怎么选择了,够吊吧。
NIO框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3和Netty4(Netty5已经被取消开发了:详见此文),本次将使用MINA2和Netty4来实现服务端的代码。
实际上,MINA2和Netty4的官方代码里已经有UDP通讯的Demo代码,但客户端并非基于现今流行的移动端(主要是Android和iOS端)来实现,本文将演示用iOS客户端来实现这种跨平台的双向网络通讯。演示Demo中,已经解决跨平台通讯时的乱码、数据字节异常等问题,请继续往下阅读。php
- 更多即时通信技术资料:http://www.52im.net/forum.php?mod=collection&op=all
html
- 移动端即时通信交流群:215891622 推荐java
有关MINA和Netty的入门文章不少,但多数都是复制、粘贴的未经证明的来路不明内容,对于初次接触的人来讲,一个能够运行且编码规范的Demo,显然要比各类“详解”、“深刻分析”之类的要来的直接和有意义。本系列入门文章正是基于此种考虑而写,虽无精深内容,但至少但愿对初次接触MINA、Netty的人有所启发,起到抛砖引玉的做用。
本文是《NIO框架入门》系列文章中的第3篇,目录以下:git
本文要演示的Demo包含两部分,iOS UDP客户端和NIO框架实现的服务端(包括MINA2和Netty4实现两个方案),客户端每隔5秒向服务端发送消息,而服务端在收到消息后立刻回复一条消息给客户端。
如上所述,服务端和客户端都要实现消息的发送和接收,即实现跨平台的双向通讯。若是有心的话,稍加改造,也就很容易实现一个简陋的聊天程序了。下节将将给出真正的实现代码。程序员
CocoaAsyncSocket源码地址:https://github.com/52im/CocoaAsyncSocket,以下图:
补充说明:iOS里的网络编程有多种途径实现(具体请参看此文),本文选择的是iOS里很是热门的 CocoaAsyncSocket 工程,它对iOS原生网络API作了进一步封装,使得开发者更易使用。
github
建好工程后把CocoaAsyncSocket的源码引用进来就好了,以下图:
补充说明:如何新建一个XCode工程请自行百度之,按照系统默认的简单创建一个就行了,本例不须要做额外配置和额外的系统库引用。编程
// 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
// 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工程自行查看哦。
网络
// 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演示》
[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)