iOS------XMPP实现一个简单的聊天页面
XMPP协议的优点:
开放-------XMPP协议是自由,开放,公开的,并且易于了解.而且在客户端,服务器,组件,源码库等方面,都已经各自有多种实现.
标准-------互联网工程工作小组(IETF)已经将Jabber的核心XML流协议以XMPP之名,正式列为认可的实时通信及Presence技术。而XMPP的技术规格已被定义在RFC 3920及RFC 3921。任何IM供应商在遵循XMPP协议下,都可与Google Talk实现连接。
证实可用-------第一个Jabber(现在XMPP)技术是Jeremie Miller在1998年开发的,现在已经相当稳定;数以百计的开发者为XMPP技术而努力。今日的互联网上有数以万计的XMPP服务器运作著,并有数以百万计的人们使用XMPP实时传讯软件。
分布式-------XMPP网络的架构和电子邮件十分相像;XMPP核心协议通信方式是先创建一个stream,XMPP以TCP传递XML数据流,没有中央主服务器。任何人都可以运行自己的XMPP服务器,使个人及组织能够掌控他们的实时传讯体验。
安全-------任何XMPP协议的服务器可以独立于公众XMPP网络(例如在企业内部网络中),而使用SASL及TLS等技术的可靠安全性,已自带于核心XMPP技术规格中。
可扩展-------XML命名空间的威力可使任何人在核心协议的基础上建造客制化的功能;为了维持通透性,常见的扩展由XMPPStandards Foundation。
弹性佳-------XMPP除了可用在实时通信的应用程序,还能用在网络管理、内容供稿、协同工具、文件共享、游戏、远程系统监控等。
多样性—用XMPP协议来建造及布署实时应用程序及服务的公司及开放源代码计划分布在各种领域;用XMPP技术开发软件,资源及支持的来源是多样的,使得使你不会陷于被“绑架”的困境。
XMPP的缺点:
数据负载太重:随着通常超过70%的XMPP协议的服务器的数据流量的存在和近60%的被重复转发,XMPP协议目前拥有一个大型架空中存在的数据提供给多个收件人。新的议定书正在研究,以减轻这一问题。
没有二进制数据:XMPP协议的方式被编码为一个单一的长的XML文件,因此无法提供修改二进制数据。因此, 文件传输协议一样使用外部的HTTP。如果不可避免,XMPP协议还提供了带编码的文件传输的所有数据使用的Base64。至于其他二进制数据加密会话(encrypted conversations)或图形图标(graphic icons)以嵌入式使用相同的方法。
XMPP实现简单聊天
想要实现简单的聊天首先要搭建一个服务器,聊天实现的原理就是,一个客户端通过XMPP协议把信息传给服务器,服务器在发消息发给里一个客户端.
下面说一下如何搭建服务器,可以调整我写的博客:
http://www.jianshu.com/p/d47a2fa85009
下面是需要导入的文件:
屏幕快照 2016-03-04 下午8.42.31.png BDC41DD0-28C8-49F3-A200-6C028AB323BB.png
简单的创建一个好友列表和聊天界面(记得给cell重用标识符)
80DDF77F-3DE0-4303-925D-563F00E7192B.png
聊天界面的cell 的样式记得更换
C84984B0-F726-4F88-932D-0E0D133BF449.png
简单的创建一个登陆和注册界面,主入口一样要加上
2F40FE19-6D6C-4017-BD6C-0D192EC888FA.png
至此基本的准备工作已经做好了,记得把登陆注册界面的TextField拖成属性.
下面的代码是写在Appdelegate里面的:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 拿到storyBoard对象
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"LoginAndRegister" bundle:nil];
// 拿到navigation控制器
UINavigationController *naVC = [storyBoard instantiateInitialViewController];
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:naVC animated:YES completion:nil];
return YES;
}
首先我们需要创建一个XMPPManager.h文件,继承自NSObject
#import <Foundation/Foundation.h>
#import "XMPPFramework.h"
@interface XMPPManager : NSObject
@property (nonatomic ,strong) XMPPStream *stream;
@property (nonatomic ,strong) XMPPRoster *roster;
// XMPP聊天消息本地化处理对象
@property (nonatomic ,strong) XMPPMessageArchiving *messageArchiving;
@property (nonatomic ,strong) NSManagedObjectContext *messageContext;
// 单例
+ (XMPPManager *)defaultManager;
// 登陆:用于传值(用户名和密码)
- (void)loginWithUserName:(NSString *)name andPassWord:(NSString *)passWord;
// 注册:用于传值(用户名和密码)
- (void)registerWithUserName:(NSString *)name andPassWord:(NSString *)passWored;
@end
#import "XMPPManager.h"
typedef enum :NSUInteger {
DoLogin,
DoRegister,
}ConnectType;
@interface XMPPManager () <XMPPStreamDelegate, XMPPRosterDelegate>
@property (nonatomic ,copy) NSString *passWord;
@property (nonatomic ,copy) NSString *registerWord;
@property (nonatomic ,assign) ConnectType connectType;
@end
@implementation XMPPManager
+ (XMPPManager *)defaultManager
{
static XMPPManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManager alloc] init];
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.stream = [[XMPPStream alloc] init];
self.stream.hostName = kHostName;
self.stream.hostPort = kHostPort;
// 设置stream的代理
[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
/** 下面的操作其实是在对Roster对象进行初始化*/
// 系统写好的xmpp存储对象
XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
self.roster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
// 激活roster
[self.roster activate:self.stream];
// 给roster对象指定代理
[self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
// 初始化聊天记录管理对象
XMPPMessageArchivingCoreDataStorage *archiving = [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.messageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:archiving dispatchQueue:dispatch_get_main_queue()];
// 激活管理对象
[self.messageArchiving activate:self.stream];
// 给管理对象添加代理
[self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.messageContext = archiving.mainThreadManagedObjectContext;
}
return self;
}
// 与服务器建立链接
- (void)connectToSercerWithUser:(NSString *)user
{
if ([self.stream isConnected]) {
[self disconnectWithSercer];
}
// jid 就是jabberID, 是基于Jabber协议的由用户名生成的唯一ID
self.stream.myJID = [XMPPJID jidWithUser:user domain:kDomin resource:kResource];
NSError *error = nil;
[self.stream connectWithTimeout:30.0 error:&error];
if (error != nil) {
NSLog(@"出现问题");
}
}
// 与服务器断开链接
- (void)disconnectWithSercer
{
[self.stream disconnect];
}
// 登陆
- (void)loginWithUserName:(NSString *)name andPassWord:(NSString *)passWord
{
self.connectType = DoLogin;
self.passWord = passWord;
[self connectToSercerWithUser:name];
}
// 与服务器建立链接成功
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
switch (self.connectType) {
case DoLogin:
{
NSLog(@"chenggong");
// 与服务器进行登录认证
NSError *error = nil;
[self.stream authenticateWithPassword:self.passWord error:&error];
if (error != nil) {
NSLog(@"出现问题");
}
break;
}
case DoRegister:
{
// 与服务器进行登录认证
NSError *error1 = nil;
[self.stream registerWithPassword:self.registerWord error:&error1];
if (error1 != nil) {
NSLog(@"出现问题");
}
break;
}
default:
break;
}
}
// 抛出异常(手动让程序崩溃)
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender
{
NSLog(@"shibai");
@throw [NSException exceptionWithName:@"CQ_Error" reason:@"与服务器建立链接失败, 请查看代码" userInfo:nil];
}
// 注册
- (void)registerWithUserName:(NSString *)name andPassWord:(NSString *)passWored
{
self.connectType = DoRegister;
self.registerWord = passWored;
[self connectToSercerWithUser:name];
}
接着要创建一个LoginViewController并与stroyboard关联
#import "LoginViewController.h"
#import "XMPPManager.h"
@interface LoginViewController () <XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *passWord;
@end
@implementation LoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (IBAction)loginClick:(UIButton *)sender {
NSString *userName = self.userName.text;
NSString *userPWD = self.passWord.text;
// 用用户名和密码进行登陆
[[XMPPManager defaultManager] loginWithUserName:userName andPassWord:userPWD];
}
// 认证成功的时候调用的
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
// 注意: 这段代码的主要作用是让你登陆的账号的状态改为在线状态,如果没有添加,别人给你发的消息服务器默认为离线状态,是不会给你发送的
XMPPPresence *presen = [XMPPPresence presenceWithType:@"available"];
[[XMPPManager defaultManager].stream sendElement:presen];
NSLog(@"111111");
[self dismissViewControllerAnimated:YES completion:nil];
}
// 认证失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
{
NSLog(@"认证失败,请重新输入");
}
下面是注册界面,创建一个RegisterViewController并与storyboard关联:
#import "RegisterViewController.h"
#import "XMPPManager.h"
@interface RegisterViewController () <XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userNameTF;
@property (weak, nonatomic) IBOutlet UITextField *passWordTF;
@end
@implementation RegisterViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (IBAction)registerClick:(UIButton *)sender {
NSString *userName = self.userNameTF.text;
NSString *passWord = self.passWordTF.text;
[[XMPPManager defaultManager] registerWithUserName:userName andPassWord:passWord];
NSLog(@"1");
}
// 当注册成功的时候调用
- (void)xmppStreamDidRegister:(XMPPStream *)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
// 注册失败的时候调用
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
NSLog(@"注册失败");
}
接下来就是好友列表界面了:
#import "MainTableViewController.h"
#import "ChatTableViewController.h"
#import "XMPPManager.h"
@interface MainTableViewController () <XMPPRosterDelegate>
// 用来存储所有的好友信息
@property (nonatomic ,strong) NSMutableArray *allData;
// 通过用户名生成的标识
@property (nonatomic ,strong) XMPPJID *chatToJid;
@end
@implementation MainTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.allData = [NSMutableArray array];
[[XMPPManager defaultManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"mainCell" forIndexPath:indexPath];
XMPPJID *jid = self.allData[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"%@@%@",jid.user,jid.domain];
return cell;
}
#pragma mark rosterDalegate
// 正在获取好友列表
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item
{
NSLog(@"****************");
// 将每一个好友信息存储下来
NSLog(@"%@",item);
NSString *jidString = [[item attributeForName:@"jid"] stringValue];
XMPPJID *jid = [XMPPJID jidWithString:jidString];
// 存储到数组
[self.allData addObject:jid];
// 更新到UI界面
NSIndexPath *path = [NSIndexPath indexPathForRow:self.allData.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationRight];
}
// 开始获取好友列表的时候
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender
{
NSLog(@"---------------****************");
}
// 结束获取好友列表的时候
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender
{
NSLog(@"*******----------------********");
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UITableViewCell *cell = (UITableViewCell *)sender;
// 拿到下一步要跳转的Controller对象
ChatTableViewController *cVC = segue.destinationViewController;
// 判断选中的cell在当前的table中的位置
NSIndexPath *path = [self.tableView indexPathForCell:cell];
cVC.chatToJid = self.allData[path.row];
}
聊天界面:
#import <UIKit/UIKit.h>
#import "XMPPManager.h"
@interface ChatTableViewController : UITableViewController
@property (nonatomic ,strong) XMPPJID *chatToJid;
@end
#import "ChatTableViewController.h"
#import "XMPPManager.h"
@interface ChatTableViewController () <XMPPStreamDelegate>
@property (nonatomic ,strong) NSMutableArray *messageArray;
@property (nonatomic ,strong) NSString *sendMessage;
@property (nonatomic ,strong) NSString *message;
@end
@implementation ChatTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.messageArray = [NSMutableArray array];
[[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
// 更新聊天记录信息
[self reloadMessage];
}
// 展现聊天记录
- (void)reloadMessage
{
NSManagedObjectContext *context = [XMPPManager defaultManager].messageContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
/** 获取所有的聊天记录 */
// 这里面要填的是XMPPARCHiver的coreData实例类型
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
// 对取到的数据进行过滤,传入过滤条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr==%@ AND bareJidStr == %@", [XMPPManager defaultManager].stream.myJID.bare,self.chatToJid.bare];
[fetchRequest setPredicate:predicate];
// Specify how the fetched objects should be sorted
// 设置排序的关键字
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// 完成之后
NSLog(@"I'll be back!");
}
// 清空消息数组里的所有数据
[self.messageArray removeAllObjects];
// 将新的聊天记录添加到数组中
[self.messageArray addObjectsFromArray:fetchedObjects];
[self.tableView reloadData];
if (self.messageArray.count) {
// 判断当前数组的元素个数,代码保护
// 滑动到UITableView的最底部,保证用户看到的是最新的消息
NSIndexPath *indexPath
= [NSIndexPath indexPathForRow:self.messageArray.count - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
- (IBAction)sendAction:(UIBarButtonItem *)sender {
// 创建消息实体
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJid];
[message addBody:@"Hello Word!"];
// 发送消息
[[XMPPManager defaultManager].stream sendElement:message];
[self reloadMessage];
}
// 成功发送消息
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
NSLog(@"%s__%d__|message = %@", __FUNCTION__, __LINE__,message);
[self reloadMessage];
}
// 收到他人发送的消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
NSLog(@"%s__%d__|message = %@", __FUNCTION__, __LINE__,message);
[self reloadMessage];
}
- (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error
{
NSLog(@"发送失败------%@",error);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.messageArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"chatCell" forIndexPath:indexPath];
// 取到对应的信息
XMPPMessageArchiving_Message_CoreDataObject *message = self.messageArray[indexPath.row];
if (message.isOutgoing == YES) {
cell.detailTextLabel.text = message.body;
cell.textLabel.text = @"";
} else {
cell.textLabel.text = message.body;
cell.detailTextLabel.text = @"";
}
return cell;
}
QQ20160304-1.png
QQ20160304-0.png
消息即使到达