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.lxweimin.com/p/d47a2fa85009
下面是需要導入的文件:
簡單的創建一個好友列表和聊天界面(記得給cell重用標識符)
聊天界面的cell 的樣式記得更換
簡單的創建一個登陸和注冊界面,主入口一樣要加上
至此基本的準備工作已經做好了,記得把登陸注冊界面的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;
}
消息即使到達