XMPP搭建本地服务器、实现即时通讯(二)

前面部分呢,我们实现了一个XMPP本地服务器的搭建 http://www.jianshu.com/p/5ea6b611c5b1 但是我们最终的目的并不是这些,我们是要实现即时通讯的功能,今天我们就在代码里面实现即时通讯的基本功能。

一、项目环境配置

首先我们在工程中导入XMPPFramework



添加本地依赖库:libxml2.tbd和libresolv.tbd



然后Build Settings->Header searches Pathes添加字段:/usr/include/libxml2

Commend+B编译正常的话就OK了

二、建立与服务器的链接

我们在建立与服务器链接之前需要自己创建:登陆页面,注册页面,好友列表页面和聊天页面,这些在此不再累述,想必大家用可视化,分分钟搞定。至于各个页面之间的关联,更是小kiss
我们需要建立一个XMPP的任务管理类对象 而且这个对象只需要一个 我们可以用单例:

.h
#import <Foundation/Foundation.h>
#import "XMPPFramework.h"

@interface XMPPManager : NSObject<XMPPStreamDelegate>

+(XMPPManager *)sharedXMPPManager;//创建管理者对象单例

@property(nonatomic,strong)XMPPStream *stream;//信息管道
@property(nonatomic,strong)XMPPRoster *xmppRoster;//进行添加好友 删除好友 获取还有列表等功能
@property(nonatomic,strong)XMPPMessageArchiving *xmppMessageArchivering;

@property(nonatomic,strong)NSManagedObjectContext *messageManagerContext;//聊天信息托管对象 上下文

//登陆
-(void)loginWithUser:(NSString *)user password:(NSString *)password;

//注册
-(void)registWithUser:(NSString *)user password:(NSString *)password;
@end


.m
#import "XMPPManager.h"
//判断想服务器建立连接是登陆还是注册
typedef enum : NSUInteger {
    ConnectToServerPurposeLogin,//登陆
    ConnectToServerPurposeRegist,//注册
} MyStatus;

@interface XMPPManager ()
@property(nonatomic,strong)NSString *password;//登录密码
@property(nonatomic,strong)NSString *registPassword;//注册密码
@property(nonatomic,assign)MyStatus connectToServerPurse;
@end
@implementation XMPPManager

+(XMPPManager *)sharedXMPPManager{
    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;//端口号标识唯一的服务
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//让当前对象成为stream的代理
        
        
        //进行好友存储
        XMPPRosterCoreDataStorage *storage= [XMPPRosterCoreDataStorage sharedInstance];
        self.xmppRoster = [[XMPPRoster alloc]initWithRosterStorage:storage dispatchQueue:dispatch_get_main_queue()];
        [self.xmppRoster activate:self.stream];//激活
        
        
        //进行聊天信息存储
        XMPPMessageArchivingCoreDataStorage *archiveringStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        self.xmppMessageArchivering = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:archiveringStorage dispatchQueue:dispatch_get_main_queue()];
        [self.xmppMessageArchivering activate:self.stream];//激活
        
        self.messageManagerContext = archiveringStorage.mainThreadManagedObjectContext;
    }
    return self;
}

//与服务器建立链接
-(void)connectToServerWithUser:(NSString *)user{
    //要是正在链接的话那么就先断开连接
    if ([self.stream isConnected]) {
        [self disconnectServer];
    }
    XMPPJID *jid = [XMPPJID jidWithUser:user domain:kDomin resource:kResource];
    self.stream.myJID = jid;
    NSError *error = nil;
    [self.stream connectWithTimeout:30.0f error:&error];
    if (nil != error) {
        NSLog(@"%s__%d__链接出错:%@",__FUNCTION__,__LINE__,error);
    }
}
//与服务器断开链接
-(void)disconnectServer{
    [self.stream disconnect];
}
//登陆
-(void)loginWithUser:(NSString *)user password:(NSString *)password{
    
    self.connectToServerPurse = ConnectToServerPurposeLogin;
    self.password = password;//将传进来的password传给self.password
    [self connectToServerWithUser:user];
}
//注册
-(void)registWithUser:(NSString *)user password:(NSString *)password{
    self.connectToServerPurse = ConnectToServerPurposeRegist;
    self.registPassword = password;
    [self connectToServerWithUser:user];
}



#pragma mark--XMPPStreamDelegate
//我们一旦想服务器发送请求之后又两种结果 成功和失败 我们需要实现XMPP协议的两个方法
//与服务器链接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    
     NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
    
    #pragma mark ------判断与服务器建立连接是登陆还是注册
    switch (self.connectToServerPurse) {
        case ConnectToServerPurposeLogin:
        {
            //建立连接成功之后需要向服务器验证自己的身份
            NSError *error = nil;
            [self.stream authenticateWithPassword:self.password error:&error];
            if (nil != error) {
                NSLog(@"%s__%d__验证出错:%@",__FUNCTION__,__LINE__,error);
            }
            break;
        }
        case ConnectToServerPurposeRegist:
        {
            NSError *err = nil;
            [self.stream registerWithPassword:self.registPassword error:&err];
            if (nil != err) {
                NSLog(@"%s__%d__注册出错:%@",__FUNCTION__,__LINE__,err);
            }
            break;
        }
        default:
            break;
    }
}
//与服务器链接失败
-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
     NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}
@end
登陆

我们在登陆页面点击登陆按钮的时候调用XMPPManager的-(void)loginWithUser:(NSString *)user password:(NSString *)password;进行登陆,但是我们在进行登陆的时候是需要服务器的验证的所有这些都直接在代码理显示了,如下:

#import "LoginViewController.h"
#import "XMPPManager.h"
@interface LoginViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *userPassword;
@end

@implementation LoginViewController
- (IBAction)loginButton:(id)sender {
    //获取用户名和密码
    NSString *userName = self.userName.text;
    NSString *userPassword = self.userPassword.text;
    
    [[XMPPManager sharedXMPPManager]loginWithUser:userName password:userPassword];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    // Do any additional setup after loading the view.
}

#pragma mark--XMPPStreamDelegate  进行身份验证
//验证成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    
    NSLog(@"%s__%d__验证成功",__FUNCTION__,__LINE__);
    
    [[NSUserDefaults standardUserDefaults]setObject:self.userName.text forKey:@"userName"];
    [[NSUserDefaults standardUserDefaults]setObject:self.userPassword.text forKey:@"userPassword"];
    [[NSUserDefaults standardUserDefaults]synchronize];//立即存储
    
    [self dismissViewControllerAnimated:YES completion:nil];
}
//验证失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    NSLog(@"%s__%d__验证出错:%@",__FUNCTION__,__LINE__,error);
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

此时我们也需要在AppDelegate里面作如下处理,当我们再次打开应用程序的时候要是已经验证成功就显示好友列表页面,否则的话就显示登陆注册页面,由于清一色的代码 给大家来张图缓解一下紧张的气氛吧。


#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    if (nil == [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"]) {
        UIStoryboard *story = [UIStoryboard storyboardWithName:@"LoginAndPregist" bundle:nil];
        UINavigationController *nv = [story instantiateInitialViewController];
        [self.window makeKeyAndVisible];
        [self.window.rootViewController presentViewController:nv animated:YES completion:^{
        }];
    }else{
        NSString *userName = [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"];
        NSString *password = [[NSUserDefaults standardUserDefaults]objectForKey:@"userPassword"];
        [[XMPPManager sharedXMPPManager]loginWithUser:userName password:password];
        [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return YES;
}
#pragma mark--XMPPStreamDelegate
//验证成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
    
    //我们验证之后默认的状态是离线的 于是我们想显示在线,需要告诉服务器自己的状态
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
    [[XMPPManager sharedXMPPManager].stream sendElement:presence];
}
//验证失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
}
注册

其实注册和登陆是有很大的相似性的,例如同样需要遵守XMPPStreamDelegate协议,我们点击注册按钮的时候是会执行XMPPManager的-(void)registWithUser:(NSString *)user password:(NSString *)password;进行注册
一样的注册成功还是失败都会有相应的方法来执行,

#import "RegistViewController.h"
#import "XMPPManager.h"
@interface RegistViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *userPassword;

@end

@implementation RegistViewController

- (IBAction)registButton:(id)sender {
    NSString *userName =self.userName.text;
    NSString *userpassword = self.userPassword.text;
    [[XMPPManager sharedXMPPManager]registWithUser:userName password:userpassword];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //设置代理
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark--XMPPStreamDelegate
//注册成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender{
    NSLog(@"%s__%d__注册成功",__FUNCTION__,__LINE__);
    
    //注册成功之后返回到登陆界面
    [self.navigationController popToRootViewControllerAnimated:YES];
}
//注册失败
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
    NSLog(@"%s__%d__注册失败%@",__FUNCTION__,__LINE__,error);
}
@end
获取好友列表

获取还有列表我们需要在XMPPManager里面创建一个XMPPRoster对象
这个对象具有添加好友 删除好友 获取好友列表等功能

@property(nonatomic,strong)XMPPRoster *xmppRoster;

具体的代码部分 我写的非常详细 大家接着看

#import "RosterTableViewController.h"
#import "XMPPManager.h"
#import "ChatTableViewController.h"
@interface RosterTableViewController ()<XMPPRosterDelegate>
@property(nonatomic,strong)NSMutableArray *rosterArray;//保存获取的好友
@end

@implementation RosterTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化数组 这个数组里面保存的是获取的好友
    self.rosterArray = [NSMutableArray arrayWithCapacity:10];
    
    [[XMPPManager sharedXMPPManager].xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.rosterArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    XMPPJID *jid = self.rosterArray[indexPath.row];
    cell.textLabel.text = jid.user;
    return cell;
}

#pragma mark--XMPPRosterDelegate
//刚开始获取好友列表
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
     NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}
//正在获取好友列表
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{
     NSLog(@"%s__%d__Item=%@",__FUNCTION__,__LINE__,item);
    NSString *jidStr = [[item attributeForName:@"jid"]stringValue];
    XMPPJID *jid =[XMPPJID jidWithString:jidStr];
    [self.rosterArray addObject:jid];
    
    //将数据添加进数组
    [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.rosterArray.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
}
//已经完成获取好友列表
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
     NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}

#pragma mark--这个方法的作用是传递一个值到我们需要点击进入的聊天页面,同时在聊天页面设置一个属性值来接收
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    UITableViewCell *cell = (UITableViewCell *)sender;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    XMPPJID *jid = self.rosterArray[indexPath.row];
    ChatTableViewController *chat = segue.destinationViewController;
    chat.chatToJid = jid;
}
@end
聊天页面

我们在聊天页面开了一个接口,来接受列表页面传进来的XMPPJID

#import <UIKit/UIKit.h>
#import "XMPPManager.h"
@interface ChatTableViewController : UITableViewController
@property(nonatomic,strong)XMPPJID *chatToJid;//要聊天的好友
@end

首先,在ChatTableViewController.m里面 我们同样的遵守代理协议XMPPStreamDelegate,定义一个可变数组来接收查询的所有聊天信息

@implementation ChatTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.allMessageArray = [NSMutableArray array];//初始化数组
    
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//设置监测对象
    [self reloadMessage]; 
}
//点击右上角添加按钮传递一条信息
- (IBAction)addButton:(id)sender {
    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJid];
    [message addBody:@"Hello"];
    [[XMPPManager sharedXMPPManager].stream sendElement:message];
}

以下是XMPPStreamDelegate对于信息发送的几个方法

#pragma mark--XMPPStreamDelegate
//发送信息成功
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
    NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
    [self reloadMessage];//发送信息是 刷新一次页面
}
//发送信息失败
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error{
    NSLog(@"%s__%d__Error:%@",__FUNCTION__,__LINE__,error);
}
//收到信息
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
    NSLog(@"%s__%d__Message=%@",__FUNCTION__,__LINE__,message);
    [self reloadMessage];//收到信息是刷新一次页面
}

我们在发送消失 或者接收到消失 以及我们进入页面的时候都会显示相应的内容 这其实就设计页面数据加载的内容,因此我们自定义一个方法,用来加载聊天页面的内容,并在相应的方法里进行调用

#pragma mark--加载聊天信息
-(void)reloadMessage{
    
    NSManagedObjectContext *managerObjectContext =[XMPPManager sharedXMPPManager].messageManagerContext;
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:managerObjectContext];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    
    //我们要是不设置相应的检索条件的话 就会吧所有的聊天信息检索出来出来
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ And streamBareJidStr == %@", self.chatToJid.bare,[XMPPManager sharedXMPPManager].stream.myJID.bare];//对方的Jid和自己的Jid
    [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 = [managerObjectContext executeFetchRequest:fetchRequest error:&error];
    if (fetchedObjects == nil) {
        NSLog(@"没有检索出任何内容");
    }else{
        
        //在此判断一下,因为日要是点击一个人,没有聊天记录的话直接return 否则会崩掉的
        if (fetchedObjects.count == 0) {
            return;
        }
        [self.allMessageArray removeAllObjects];
        [self.allMessageArray addObjectsFromArray:fetchedObjects];
        [self.tableView reloadData];
        
        //进行信息加载的时候让tableView 滚动到最底部
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.allMessageArray.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    }
}

至于UITableView data source的几个代理方法就简单的不行不行的了。。。
Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.allMessageArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Message" forIndexPath:indexPath];
    
    XMPPMessageArchiving_Message_CoreDataObject *message = self.allMessageArray[indexPath.row];
    
    //判断聊天信息是发出去的 还是接受进来的,在cell上显示不一样的样式
    if (message.isOutgoing == YES) {
        cell.detailTextLabel.text = message.body;
        cell.textLabel.text = @"";
    }else{
        cell.textLabel.text = message.body;
        cell.detailTextLabel.text = @"";
    }
    return cell;
}

以上就是XMPP实现聊天功能的基本内容,我们在以代码为伍的时候 可以用Openfile和spark等工具帮助我们进行调试~~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容