XMPP 基于XML的点对点通信协议
XMPP 基于XML的点对点通信协议
IM: 即时通信
XMPP: 基于XML的点对点通信协议
1. ejabber: 这个软件是用来开启本地服务器使用, 本地服务器地址为: 127.0.0.1, 所有的电脑都一样, 开启服务器的软件有很多
2. Audium: 就是一个聊天工具
用局域网聊天
1.在同一个WIFI下
2.在同一个本地服务器创建账号
3.登陆的时候, 设置好服务器地址, 为电脑的IP地址
XAMPP: 集成了阿帕奇 FTP和My SQL的一个软件
类的扩展有三种方式
1.协议: 需要自己制定协议, 设置代理, 遵守协议, 一般用来传值(回调)
2.延展: 为类添加私有方法和私有变量, 一般用于已知类(.h和.m都存在的类)
3.类目: 为类添加方法和属性(只有setter和getter, 没有实例变量), 必须实现内部声明的方法, 一般用于系统类的扩展
XMPP的安装
XMPP安装需要的软件
安装ejabberd
选择ejabberd.app进行安装
用户名和密码填写完要记住,之后会用到
安装成功后,在Finder->应用程序->ejabberd-14.07->bin->start点击start.exec运行,会跳出一个网页
在网页上可以添加账号和密码
将 软件添加到Finder->应用程序中,点击打开app,可以用ejabberd中注册的账号和密码登陆
详细代码:
在工程中添加框架和MI类
storyboard基本布局:
ViewController.m #import "ViewController.h" //引入头文件 #import "XMPPFramework.h" #import "XMPPManager.h" #import "ContactViewController.h" #define JID(USER_NAME) [NSString stringWithFormat:@"%@@bogon", USER_NAME] @interface ViewController ()<XMPPStreamDelegate, UIAlertViewDelegate, XMPPRosterDelegate> @property (weak, nonatomic) IBOutlet UITextField *userNameTF; @property (weak, nonatomic) IBOutlet UITextField *passwordTF; //花名册 @property (nonatomic, strong) XMPPRoster *roster; //花名册数组 @property (nonatomic, strong) NSMutableArray *rosterArr; - (IBAction)loginClick:(id)sender; //创建XMPPStream类的属性 @property (nonatomic, strong) XMPPStream *stream; @end @implementation ViewController //点击页面代理方法 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //结束编辑状态 [self.view endEditing:YES]; } //重写XMPPStream的getter方法 - (XMPPStream *)stream { if (_stream == nil) { self.stream = [[XMPPStream alloc] init]; _stream.hostName = @"127.0.0.1"; _stream.hostPort = 5222; [_stream addDelegate:self delegateQueue:dispatch_get_main_queue()]; //激活花名册 [self.roster activate:_stream]; } return _stream; } //重写XMPPRoster花名册方法 - (XMPPRoster *)roster { if (!_roster) { self.roster = [[XMPPRoster alloc] initWithRosterStorage:[XMPPRosterCoreDataStorage sharedInstance] dispatchQueue:dispatch_get_main_queue()]; //配置参数 [_roster addDelegate:self delegateQueue:dispatch_get_main_queue()]; } return _roster; } - (void)viewDidLoad { [super viewDidLoad]; //记录stream [XMPPManager sharedManager].stream = self.stream; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } //登陆按钮 - (IBAction)loginClick:(id)sender { //登陆 //1.断开连接 [self.stream disconnect]; //2.设置JID:设置字符串宏 self.stream.myJID = [XMPPJID jidWithString: JID(self.userNameTF.text)]; //3.连接服务器 [self.stream connectWithTimeout:-1 error:nil]; } #pragma mark-XMPPStreamDelegate //连接服务器成功 - (void)xmppStreamDidConnect:(XMPPStream *)sender { //连接服务器成功以后登陆 [self.stream authenticateWithPassword:self.passwordTF.text error:nil]; } //连接服务器超时 - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender { NSLog(@"连接超时"); } //登陆成功 - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { //发送登陆成功状态 XMPPPresence *presence = [XMPPPresence presence]; [self.stream sendElement:presence]; //记录当前登陆的JID [[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:@"%@@bogon", self.userNameTF.text] forKey:@"MyJID"]; [[[UIAlertView alloc] initWithTitle:@"登陆成功" message:@"" delegate:self cancelButtonTitle:@"好的" otherButtonTitles: nil] show]; } //登录失败 - (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error { [[[UIAlertView alloc] initWithTitle:@"登陆失败" message:@"" delegate:self cancelButtonTitle:@"好的" otherButtonTitles: nil] show]; } //接收到消息的代理方法 - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { //如果body的长度为空 if (message.body.length == 0) { } else { NSLog(@"message: %@", message); } } #pragma mark - UIAlertViewDelegate - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { NSLog(@"%ld", buttonIndex); if (buttonIndex == 0) { //触发sugue [self performSegueWithIdentifier:@"modelToContact" sender:nil]; } else { //清空密码输入框 self.passwordTF.text = @""; } } #pragma mark-XMPPRosterDelegate - (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender { NSLog(@"开始获取花名册"); self.rosterArr = [@[] mutableCopy]; } - (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item { NSLog(@"获取花名册中"); [self.rosterArr addObject:item]; } - (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender { NSLog(@"结束获取"); NSLog(@"rosterArr: %@", self.rosterArr); } //属性传值 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"modelToContact"]) { ContactViewController *contact = segue.destinationViewController; contact.temArr = self.rosterArr; } } @end
ContactViewController.m #import "ContactViewController.h" #import "DDXMLElement.h" #import "ChatViewController.h" @interface ContactViewController ()<UITableViewDataSource, UITableViewDelegate> - (IBAction)back:(id)sender; @property (weak, nonatomic) IBOutlet UITableView *tableView; @end @implementation ContactViewController - (void)viewDidLoad { [super viewDidLoad]; [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark-UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.temArr.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; DDXMLElement *friendItem = self.temArr[indexPath.row]; //找到 JID 字符串 NSString *jidString = [[friendItem attributeForName:@"jid"] stringValue]; cell.textLabel.text = jidString; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self performSegueWithIdentifier:@"modelToChat" sender:indexPath]; } #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"modelToChat"]) { ChatViewController *chatVC = segue.destinationViewController; NSIndexPath *index = (NSIndexPath *)sender; chatVC.friendJID = [[self.temArr[index.row] attributeForName:@"jid"] stringValue]; } } - (IBAction)back:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } @end
ContactViewController.h #import <UIKit/UIKit.h> @interface ContactViewController : UIViewController @property (nonatomic, strong) NSMutableArray *temArr; @end
ChatViewController.h #import <UIKit/UIKit.h> @interface ChatViewController : UIViewController @property (nonatomic, copy)NSString *friendJID; @end
ChatViewController.m #import "ChatViewController.h" #import "XMPPFramework.h" #import "XMPPManager.h" @interface ChatViewController ()<UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, XMPPStreamDelegate> - (IBAction)back:(id)sender; @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet UITextField *textField; @end @implementation ChatViewController - (void)viewDidLoad { [super viewDidLoad]; [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark-UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 10; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; cell.textLabel.text = @"123"; return cell; } #pragma mark-XMPPStreamDelegate - (void)xmppStreamDidConnect:(XMPPStream *)sender { NSLog(@"连接服务器成功"); } - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender { NSLog(@"连接服务器超时"); } - (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message { NSLog(@"发送成功"); } - (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error { NSLog(@"消息发送失败%@", error); } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ - (IBAction)back:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark-UITextFieldDelegate //点击return按钮事件 - (BOOL)textFieldShouldReturn:(UITextField *)textField { //创建消息体 XMPPMessage *message = [XMPPMessage messageWithType:@"text" to:[XMPPJID jidWithString:self.friendJID]]; //添加消息内容 [message addBody:textField.text]; //发送消息 [[XMPPManager sharedManager].stream sendElement:message]; //收回键盘 [textField resignFirstResponder]; return YES; } @end
XMPPManager.h #import <Foundation/Foundation.h> @interface XMPPManager : NSObject + (XMPPManager *)sharedManager; @property (nonatomic, strong) id stream; @end
XMPPManager.m #import "XMPPManager.h" @implementation XMPPManager + (XMPPManager *)sharedManager { static XMPPManager *manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [self new]; }); return manager; } @end
NSString+JID.h #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface UITextField (JID) @property (nonatomic, copy) NSString *jid; @end
NSString+JID.m #import "NSString+JID.h" @implementation UITextField (JID) - (void)setJid:(UITextField *)jid { } - (NSString *)jid { return [NSString stringWithFormat:@"%@@bogon", self.text]; } @end
RegisterViewController.m #import "RegisterViewController.h" //引入 XMPP 框架的头文件 #import "XMPPFramework.h" @interface RegisterViewController ()<XMPPStreamDelegate> @property (weak, nonatomic) IBOutlet UITextField *userNameTF; @property (weak, nonatomic) IBOutlet UITextField *passwordTF; - (IBAction)registerClick:(id)sender; - (IBAction)goBack:(id)sender; //创建一个 XMPPStream类的属性 @property (nonatomic, strong) XMPPStream *stream; @end @implementation RegisterViewController - (void)viewDidLoad { [super viewDidLoad]; //初始化 XMPPStream self.stream = [[XMPPStream alloc] init]; //设置服务器地址 _stream.hostName = @"127.0.0.1"; //设置服务器的端口号 _stream.hostPort = 5222; //设置XMPPStream代理 [_stream addDelegate:self delegateQueue:dispatch_get_main_queue()]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ - (IBAction)registerClick:(id)sender { //设置 JID //JID是用户在聊天服务器中的唯一标识 //JID 有两部分构成: 用户名@域名 self.stream.myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@bogon", self.userNameTF.text]]; //注册 //1. 连接服务器 //先断开连接 [self.stream disconnect]; //-1 代表无限大, 直到连接上服务器 [self.stream connectWithTimeout:-1 error:nil]; } - (IBAction)goBack:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark-XMPPStreamDelegate //连接服务器超时 - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender { NSLog(@"%s", __FUNCTION__); } //连接服务器成功 - (void)xmppStreamDidConnect:(XMPPStream *)sender { NSLog(@"%s", __FUNCTION__); //2.连接服务器成功以后进行注册 [self.stream registerWithPassword:self.passwordTF.text error:nil]; } //注册成功 - (void)xmppStreamDidRegister:(XMPPStream *)sender { [[[UIAlertView alloc] initWithTitle:@"恭喜你" message:@"注册成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"前往登陆", nil] show]; } //注册失败 - (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error { } @end
The one who wants to wear a crown must bear the weight!