XMPP
一.什么是XMPP
XMPP是一种基于XML的即时通讯协议,前身是jabber.
二.为什么要写XMPP单例
由于XMPPStream是基于myJID建立的长连接,因此在应用程序中应该仅被实例化一次(也就是建立单例)
+ (id)shareInstance
{
static id _s;
if (_s== nil) {
_s = [[[self class]alloc]init];
}
return _s;
}
三.XMPP初始化
//初始化用户
_currentUser = [[UserModel alloc]init];
_allFriendList = [[NSMutableArray alloc]init];
_xmppStream = [[XMPPStream alloc]init];
//设置主机名
[_xmppStream setHostName:@"1000phone.net"];
//设置端口
[_xmppStream setHostPort:5222];
//在这里并没有连接
//设置代理,有数据回来急用delegate接收
// xxx.delegate = self; 是一样的
//这里使用了一个多代理,多对多的方式
[_xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
四.用户模型
@property (nonatomic,copy)NSString *jid;
@property (nonatomic,copy)NSString *password;
@property (nonatomic,copy)NSString *status;
五.XMPP注册
//保存注册回调
void (^saveRegCb)(BOOL ret,NSError *error);
- (void)registerUser:(NSString *)jid withPassword:(NSString *)passWord withCompletion:(void (^)(BOOL, NSError *))cb
{
saveRegCb = [cb copy];
isInRegisting = YES;
//真正的注册
if ([_xmppStream isConnected]) {
[_xmppStream disconnect];
}
_currentUser.jid = jid;
_currentUser.password = passWord;
/*
1.给服务器账号
2.服务器返回我收到了账号
3.再给服务器密码
*/
//把jid字符串转化为XMPPJID对象
XMPPJID *myjid = [XMPPJID jidWithString:jid];
//连接服务器
//给xmppStream流设置一个账号
[_xmppStream setMyJID:myjid];
NSError *error = nil;
//异步方式,只是去连,但还没有成功
//connectWithTimeout,这个函数绝对不能调用两次
BOOL ret = [_xmppStream connectWithTimeout:-1 error:&error];
//异步注册,需要代理
if (ret == NO) {
if (saveRegCb) {
saveRegCb(NO,error);
}
}
}
//已经连接
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
NSLog(@"连接上了 %@",NSStringFromSelector(_cmd));
//输入密码
NSError *error= nil;
//因为不论注册还是登录都会走这个方法,所以用一个变量来区分
if (isInRegisting) {
//一旦注册就会回到xmppStreamDidRegister
[_xmppStream registerWithPassword:_currentUser.password error:&error];
}else
{
//对于已有的用户,这里是授权密码
[_xmppStream authenticateWithPassword:_currentUser.password error:&error];
//一旦授权完成,就会进入密码授权正确
}
//成功和失败
}
//注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender
{
NSLog(@"function %@",NSStringFromSelector(_cmd));
if (saveRegCb) {
saveRegCb(YES,nil);
}
}
//注册失败
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error
{
NSLog(@"function %@",NSStringFromSelector(_cmd));
NSLog(@"error is %@",[error description]);
if (saveRegCb) {
//因为error 是NSXMLElement类型,所以要转化
NSError *myerror = [NSError errorWithDomain:error.description code:-1 userInfo:nil];
saveRegCb(NO,myerror);
}
}
六.XMPP登录
//保存登录回调
void (^saveLoginCb)(BOOL ret,NSError *error);
- (void)loginUser:(NSString *)jid withPassword:(NSString *)passWord withCompletion:(void (^)(BOOL, NSError *))cb
{
saveLoginCb = [cb copy];
isInRegisting = NO;
//登录分为两步
//1.给服务器输入账号,2给服务器输入密码
_currentUser.jid = jid;
_currentUser.password =passWord;
XMPPJID *myjid = [XMPPJID jidWithString:jid];
//有可能是注册完了就登录,所以要断开
if ([_xmppStream isConnected]) {
[_xmppStream disconnect];
}
[_xmppStream setMyJID:myjid];
NSError *error= nil;
BOOL ret= [_xmppStream connectWithTimeout:-1 error:&error];
if (ret == NO) {
saveLoginCb(NO,error);
}
}
//授权成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
NSLog(@"function %@",NSStringFromSelector(_cmd));
//可以上线了
if (saveLoginCb) {
saveLoginCb(YES,nil);
}
//上线,获取所有好友
[self goOnLine];
//取得所有好友信息
//取得当前上线是谁
[self getAllFriends];
}
//授权失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error
{
NSLog(@"function %@",NSStringFromSelector(_cmd));
if (saveLoginCb) {
saveLoginCb(NO,[NSError errorWithDomain:error.description code:-1 userInfo:nil]);
}
}
七.XMPP上线
- (void)goOnLine
{
//presence 曝光
//这个类的作用就是产生一个xml的结构
XMPPPresence *p = [XMPPPresence presence];
//通过给服务器发送XML文件的形式,上线
[_xmppStream sendElement:p];
}
八.XMPP获取好友
void (^saveFriendCb)(NSArray *);
NSMutableArray *_allFriendList;
- (void)getAllFriends
{
//产生一个节点<query xxx>
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
[iq addAttributeWithName:@"type" stringValue:@"get"];
[iq addChild:query];
NSLog(@"all friends %@",iq);
//传给服务器,服务器返回的也是xml
[_xmppStream sendElement:iq];
}
#pragma mark - 获取好友(默认是离线状态)
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
// NSLog(@"iq is %@",iq);
// XML解析好友列表
NSXMLElement * query = iq.childElement;
for (NSXMLElement * oneChild in query.children) {
NSString * jid = [oneChild attributeStringValueForName:@"jid"];
// 相同JID好友防止重复添加 默认情况下都是离线好友
[self addFriendFromReceiveIQ:jid withStatus:@"unavailable"];
}
return YES;
}
#pragma mark - 更新好友列表 好友状态
- (void)addFriendFromReceiveIQ:(NSString *)name withStatus:(NSString *)status
{
for (UserModel * m in _friendArr) {
if ([m.name isEqualToString:name]) {
return;
}
}
UserModel * m = [[UserModel alloc] init];
m.name = name;
m.status = status;
[_friendArr addObject:m];
if (saveFriendCb) {
saveFriendCb(_friendArr);
}
}
//获取状态更新,这个方法会比获取好友更先走
#pragma mark - 订阅事件 上线 离线 发送好友请求 拒绝好友请求...
/*
available -- (默认)用户空闲状态
unavailable -- 用户没空看消息
subscribe -- 请求订阅别人,即请求加对方为好友
subscribed -- 统一被别人订阅,也就是确认被对方加为好友
unsubscribe -- 他取消订阅别人,请求删除某好友
unsubscribed -- 拒绝被别人订阅,即拒绝对放的添加请求
error -- 当前状态packet有错误
*/
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
// maozhi4@10.2.159.91/747f21ab 变成 maozhi4@10.2.159.91
// 从presence中提取出谁发送了 上线 下线请求
// [presence attributeStringValueForName:@"from"]
XMPPJID * fromJid = [XMPPJID jidWithString:[presence attributeStringValueForName:@"from"]];
NSString * jidStr = [NSString stringWithFormat:@"%@@%@",fromJid.user,fromJid.domain];
//XMPPJID * jid = [XMPPJID jidWithString:jidStr];
NSString * status = @"available";
NSString * type = [presence type];
if ([type isEqualToString:@"unavailable"]){
// 离线
status = @"unavailable";
}
else if ([type isEqualToString:@"available"]){
// 上线
status = @"available";
}
[self updateOrAddFriendFromReceivePresence:jidStr withStatus:status];
NSLog(@">>%@ %@",jidStr,status);
}
- (void)updateOrAddFriendFromReceivePresence:(NSString *)name withStatus:(NSString *)status
{
BOOL exist = NO;
// 如果有相同JID的好友 不应该添加至数组
for (UserModel * m in _friendArr) {
if ([m.name isEqualToString:name]) {
exist = YES;
m.status = status;
break;
}
}
// 可以被添加至数组
if (!exist) {
UserModel * m = [[UserModel alloc] init];
m.name = name;
m.status = status;
[_friendArr addObject:m];
}
// 回调好友列表
if (saveFriendCb) {
saveFriendCb(_friendArr);
}
}
- (void)getAllFriends:(void (^)(NSArray *))cb
{
saveFriendCb = [cb copy];
//好友会不断上线下线
if (saveFriendCb) {
saveFriendCb(_allFriendList);
}
}
九.登录页面
- (IBAction)login:(id)sender {
//登录
[[QFXMPPmanager shareInstance]
loginUser:self.nameField.text
withPassword:self.passField.text
withCompletion:^(BOOL ret,NSError *error)
{
if (ret == YES) {
NSLog(@"登录成功");
FriendListViewController *flc = [[FriendListViewController alloc]init];
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:flc];
[self presentViewController:nav animated:YES completion:nil];
}else
{
NSLog(@"登录失败");
}
}];
}
- (IBAction)regist:(id)sender {
//注册
[[QFXMPPmanager shareInstance]
registerUser:self.nameField.text
withPassword:self.passField.text
withCompletion:^(BOOL ret,NSError *error)
{
if (ret == YES) {
NSLog(@"注册成功");
}else
{
NSLog(@"注册失败");
}
}];
}
十.获取好友页面
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"朋友";
[[QFXMPPmanager shareInstance]getAllFriends:^(NSArray *list)
{
[_allFriendList removeAllObjects];
[_allFriendList addObjectsFromArray:list];
[self.tableView reloadData];
}];
}
十一.接收消息页面
[[XMPPmanager shareInstace]refreshMessage:^(ChatModel *model) {
//来一条消息,刷新一条消息
[_chatList addObject:model];
[self.tableView reloadData];
}];
十二.聊天模型
@property (nonatomic,copy)NSString *body;
@property (nonatomic,copy)NSString *from;
@property (nonatomic,copy)NSString *to;
十三.收到的消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
//判断是否是聊天消息
if ([message isChatMessage])
{
//判断消息是否带消息体(body)
if ([message isChatMessageWithBody])
{
NSString *body = [message body];
NSString *from = [[message from]bare];
NSString *to = [[message to]bare];
NSLog(@"body==%@,from=%@,to=%@",body,from,to);
ChatModel *model = [[ChatModel alloc]init];
model.body = body;
model.from = from;
model.to = to;
//刷表
if (saveRefreshMessageBlock) {
saveRefreshMessageBlock(model);
}
}
}
}
十四.发送消息
在聊天的viewController中:
[xmppmanager shareinstace](void)sendMessage:(nssrting *)msg toUser:(userModel *)toUser withCompletion:(void (^) (bool ret))cb
- (void)sendMessage:(nssrting *)msg toUser:(userModel *)toUser withCompletion:(void (^) (bool ret))cb;
{
saveSendMessageBlocks = [cb copy];
xmppjid *toJid = [xmppJid jidwithstring:toUser.name];
xmppMessage *oneMessage = [[xmppmessage alloc]initWithtype:@“chat”to:toJid];
[oneMessage addBody:msg];
[_xmppStream sendElement: oneMessage];
}
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message;
{
if(saveSendMessageBlocks)
{
saveSendMessageBlocks(YES);
}
}
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error;
{
if(saveSendMessageBlocks)
{
saveSendMessageBlocks(NO);
}
}