XMPP即时通讯协议
XMPP(Extensible Messageing and Presence Protocol:可扩展消息与存在协议)是目前主流的四种IM(IM:instant messaging,即时消息)协议之一,其他三种分别为:即时信息和空间协议(IMPP)、空间和即时信息协议(PRIM)、针对即时通讯和空间平衡扩充的进程开始协议SIP(SIMPLE)。
XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。
XMPP标准化的核心结果分为两部分
- 核心的XML流传输协议
- 基于XML流传输的即时通讯扩展应用
XMPP的基本网络结构
*XMPP中定义了三个角色,客户端,服务器,网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。
3. XML流
<stream:stream
to=’example.com’
xmlns=’jabber:client’
xmlns:stream=’http://etherx.jabber.org/streams’
version=’1.0’>
服务器:<?xml version=’1.0’?>
<stream:stream
from=’example.com’
id=’someid’
xmlns=’jabber:client’
xmlns:stream=’http://etherx.jabber.org/streams’
version=’1.0’>
创建基于agsXMPP的实例
agsXMPP致力于创建一个轻量、快速的跨平台类库,用于XMPP协议.本实例是基agsXMPP上开发的,agsXMPP是C#写的支持开源XMPP协议软件,我们可以在agsXMPP上快速构建自已的即时通讯平台,实现服务器客户端交互.
- 异步套接字
- 与工厂模式结合的快速XML解析器
- 自有的轻量级XML Dom,作为所有agsXMPP协议类的基础
为什么不直接所用Microsoft的System.Xml命名空间里的类呢?
决定创建自己的轻量级的Xml Dom,能够飞快地运作,特别是在像PPC’s和Smartphones这样的嵌入式设备上。
XmlTextReader有利于SAX-like的解析。但是Microsoft在.NET1.1的SP1中做了下改变,这使得不能够再使用它来解析网络流,所以需要另外的XML解析器
示例:
套接字接收到一条信息,将比特流推送至解析器。XML解析器探测到隶属于jabber:client命名空间中名字为message的开标签符。在元素创建前,解析器在工厂散列表中做个查找。这样就创建了agsXMPP.protocol.client.Message类的一个实例。如果表中不存在name/namespace的绑定,则会创建agsXMPP.Xml.Element的一个实例
所有的XMPP协议类都派生自agsXMPP.Xml.Element。他们都是在内存中保持XML树的’abstract’元素。所有的属性都是’realtime properties’。在我们要读取消息体,调用消息体属性时,类将会实时查找元素。
服务器端XmppSeverConnection类事件
public XmppServerConnection()
{
streamParser = new StreamParser();
//在流开始时触发,一般是最初的响应流
streamParser.OnStreamStart += new StreamHandler(streamParser_OnStreamStart);
//在流结束时触发,一般是发送</stream:stream>并关闭套接字连接
streamParser.OnStreamEnd += new StreamHandler(streamParser_OnStreamEnd);
//在接收到流结点时触发,这是用得最多的,常用的<message>消息,<Presence>出席消息,
//<IQ>请求应答消息都在这里处理
streamParser.OnStreamElement += new StreamHandler(streamParser_OnStreamElement);
}
#(变量)
#region
private StreamParser streamParser;
private Socket m_Sock;
delegate void dosomethings();
private const int BUFFERSIZE = 1024;
private byte[] buffer = new byte[BUFFERSIZE];
private Form1 frm;
public Jid jid;
public delegate void mydelegate(string str);
#endregion
//此处处理大部份的消息,包括消息路由
private void streamParser_OnStreamElement(object sender, Node e)
{
frm.BeginInvoke(new mydelegate(frm.ShowRecvMessage), new Object[] { e.ToString() });
if (e.GetType() == typeof(Presence))
{
Presence pres = e as Presence;
//处理用户上线消息
if (pres.Show == ShowType.chat && pres.Type == PresenceType.available)
{
pres.From = this.jid;
foreach (XmppServerConnection con in Online.onlineuser)
{
if (con.jid.User != this.jid.User)
{
pres.To = con.jid;
con.Send(pres);
}
}
}
//处理好友离线消息
else if (pres.Type == PresenceType.unavailable)
{
pres.From = this.jid;
Online.onlineuser.Remove(this);
frm.listBox2.Items.Remove(this.jid.User);
frm.listBox1.Items.Add(this.jid.User + "下线了");
foreach (XmppServerConnection con in Online.onlineuser)
{
if (con.jid.User != this.jid.User)
{
pres.To = con.jid;
con.Send(pres);
}
}
}
}
else if (e.GetType() == typeof(agsXMPP.protocol.client.Message))
{
agsXMPP.protocol.client.Message msg = e as agsXMPP.protocol.client.Message;
//点对点聊
if (msg.Type == MessageType.chat)
{
foreach (XmppServerConnection con in Online.onlineuser)
{
if (con.jid.User == msg.To.User)
{
msg.From = jid;
con.Send(msg);
frm.listBox1.Items.Add("向" + con.jid.User + "转发" + msg.To.User + "对" + msg.From.User + "私聊信息\r" + msg.Body);
}
}
}//群聊
else if (msg.Type == MessageType.groupchat)
{
foreach (XmppServerConnection con in Online.onlineuser)
{
con.Send(msg);
frm.listBox1.Items.Add("向" + con.jid.User + "转发" + msg.From.User + "对大家说的信息\r" + msg.Body);
}
}
//发送文件
else if (msg.Type == MessageType.normal)
{
foreach (XmppServerConnection con in Online.onlineuser)
{
if (con.jid.User == msg.To.User)
{
msg.From = jid;
con.Send(msg.ToString());
frm.listBox1.Items.Add("向" + con.jid.User + "转发" + msg.To.User + "对" + msg.From.User + "发送文件\r" + msg.Body);
}
}
}
}
else if (e.GetType() == typeof(IQ)) // 路由presences节
{
ProcessIQ(e as IQ); //处理IQ节
}
else if (e.GetType() == typeof(enum_))
{
}
}
private void ProcessIQ(IQ iq)
{
//用户认证
if (iq.Query.GetType() == typeof(Auth))
{
Auth auth = iq.Query as Auth;
switch (iq.Type)
{
case IqType.get:
iq.SwitchDirection();
iq.Type = IqType.result;
auth.AddChild(new Element("password"));
auth.AddChild(new Element("digest"));
Send(iq);
break;
case IqType.set:
this.jid = new Jid(auth.Username, "localhost", "Resource");
Online.onlineuser.Add(this);
string[] subItem0 ={ auth.Username, m_Sock.RemoteEndPoint.ToString() };
frm.Invoke(new dosomethings(delegate()
{
frm.listBox1.Items.Add(auth.Username + "上线");
frm.listBox2.Items.Add(auth.Username);
}));
iq.SwitchDirection();
iq.Type = IqType.result;
iq.Query = null;
Send(iq);
break;
}
}
else if (iq.Query.GetType() == typeof(Roster))
{
ProcessRosterIQ(iq);
}
}
简单介绍函数流程,服务器端开启监听5222端口
while (running)
{
allDone.Reset();
// Start an asynchronous socket to listen for connections.
Console.WriteLine(“等待连接”);
listener.BeginAccept(new AsyncCallback(AcceptCallback), null);
等待客户端连接
allDone.WaitOne();
}
//如果收到客户端请求就异步调用AcceptCallback初始化套接字连接,并为客户端建立一个通信线程,新建初始//化套接字连接采用异步调用读取套接字信息
public XmppSeverConnection(Socket sock)
{
m_Sock = sock;
m_Sock.BeginReceive(buffer, 0, BUFFERSIZE, 0, new AsyncCallback(ReadCallback), null);
m_Sock.SendTimeout = 100;
}
1、客户端异步向服务器端发送连接请求
2、服务器端收到请求,初始化回应流,并随机生成一相SessionID**
3、等待服务器返回消息后客户端发送用户名(由于在客户端采用了异步调用方式,所以UI界面感觉不到等待)
4、服务器端收到用户名等待用户提供密码
5客户端提供加密后的密码
6、服务器端从数据库验证用户名和密码,并返回结果**
7、如果返回错误,客户端提示并终断连接,否则客户端发送响应数据
8、服务器端返回数据
9、客户端发送状态
10、服务器收到状态,发送IQ节并通知其它用户
XMPP协议调试成功后,结合Openfire服务器,实现移动设备和PC端无线方式的即时通讯(通过路由器组成局域网),同样用于设备间的数据交换。由于后边工作不是我再负责,所以不再进行过多赘述,如有需要可以帮忙询问解答。后来项目中使用Openfire作为服务器,手机客户端app和PC端均作为客户端,最后在同一局域网内实现数据交换的即时通讯链路。
Openfire是免费的、开源的、基于可拓展通讯和表示协议(XMPP)、采用Java编程语言开发的实时协作服务器。 Openfire安装和使用都非常简单,并利用Web进行管理。单台服务器可支持上万并发用户。
测试客户端Spark
客户端使用Spark即可,登录时,先在服务器上多创建几个账号,然后直接登录其中一个,添加联系人即可进行聊天,我只是用来测试服务器的配置和端口的监听,以及后期PC端bug的修改。