XMPP即时通讯协议

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的修改。

猜你喜欢

转载自blog.csdn.net/NcepuKZH/article/details/89293668