一、即时通讯介绍
即时通讯(Instant Messenger,简称IM),是一种基于互联网的即时交流消息的业务,代表有:阿里旺旺、QQ、百度Hi等。
企业内部应用即时通讯的特点和要求
特点是沟通成本低,高效,方便,快捷,具备一定的用户黏度
1.即时通讯,包括文字,图片,语音,视频,文件。
2.群组功能,讨论组。
3.重点考虑商务沟通过程中的特点,比如视频会议功能,PPT演示,无线连接投影仪,再比如重要聊天记录保存,还有方便快捷的转账业务。
4.UI简洁高效,大方美观,特别是无广告。
5.用户联系方式导入,支持手机通讯里,EXCEL,MSN通讯录等等。
6.快捷键,常用功能,如截图,关闭。
7.注重隐私保护,身份验证,各种状态,在线,隐身。
8.设计要稳重,比如个人资料要设置为名片,企业形象等等。
二、Openfire介绍
服务端Openfire
源码下载地址http://www.igniterealtime.org/downloads/source.jsp
客户端Spark
源码下载地址http://fisheye.igniterealtime.org/browse/spark/trunk
主要功能
1、支持多服务器集群;
2、防火强穿透;
3、在线、离线消息;
4、文件,图片实时传输;
5、自动升级;
6、插件功能;
7、群聊;
8、好友添加。
三、企业IM开发规划
1、技术选型:采用DotNet平台 c#语言开发,数据库采用SQL Server 2008 R2 ,服务端 Socket App集成后台管理。
2、客户端升级:采用智能客户端实现升级。
a.创建一个升级程序,连接 webservice 来获取程序版本和升级程序的信息;
b.将升级程序下载到本机的一个目录然后覆盖现有的程序;
c.在配置文件中记录本次升级的信息。
4、架构图:
5、数据协议
采用XMPP格式数据进行传输
XMPP : The Extensible Messaging and Presence Protocol。
中文全称:可扩展通讯和表示协议。
XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。
而XMPP的核心部分就是一个在网络上分片段发送XML的流协议。
XMPP节详解
XML节通过XML流来发送,XMPP定义了三种顶级XML节
<message /> --用户之间的信息发送
<iq /> --服务器请求返回信息
<presence /> --状态
XMPP给这三种节定义了五种通用属性
to 属性指定接收节的JID
from 属性指定发送节的JID
id 并且,在接收应用(通常是一个服务器)中是唯一的。注意:流ID可能是严格安全的,并且因此必须是即不能预测也不能重复的
type 属性指定目的或消息上下文,出席或IQ节的详细信息。
xml:lang
<message />节定义了消息语义,<message />节可被看作“推”机制,一个实体推信息给其它实体,与EMAIL系统中发生的通信类似。所有消息节应该拥有‘to’属性,指定有意的消息接收者;根据接收到那样的一个节,服务器应该路由或传送它到有意的接收者。
message用于“发送后即忘”的传输(发送后不验证消息是否接收成功,功能上并非技术上的),这样的传输主要应用与人类可读的文本、警告、通知等信息。
<iq />节定义了请求语义,<iq />节可被看作一个请求-响应机制,与[HTTP]在某些方面相似。IQ语义让一个实体向其它实体请求或接收其它实体的响应成为可能。请求与响应的数据内容由IQ无素的直接子元素的命名空间声明定义,并且,交互由请求实体通过使用‘id’属性来跟踪。因此,IQ交互遵从结构化数据交换的一个通用模式,此交换例如得到/结果或设置/结果(虽然如果合适的话,对一个请求的响应可能会以错误返回)。
<presence />节定义了出席语义,<presence />节可被看作基本广播或“出版-订阅”机制,多实体收到他们已订阅(在这种情况下,网络可利用信息)实体的信息。总的来说,出版实体应该发送一个不带‘to’属性的出席节,在这种情况下,与此实体相连的服务器应该广播给所有订阅实体。然而,一个出版实体也可能发送一个带有‘to’属性的出席节,此种情况下,服务器应该路由或传送节到有意的接收者。
presence用于向那些订阅实体广播网络可用性。
6、XMPP通信原理
所有从一个client到另一个client的消息和数据都要通过xmpp server。
1.client连接到server;
2.server利用本地目录系统的证书对其认证;
3.client制定目标地址,让server告知目标状态;
4.server查找,连接并进行相互认证;
5.client间进行交互。
8、基于XMPP协议.net实现
四、后台数据库集成
集成主要的表结构
五、Socket开发技术
P2P技术(P2P打洞)
一个异步Socket通信的源码
using System; using System.Net; using System.Net.Sockets; using System.Text; namespace Chatting { public abstract class SocketFunc { //不管是服务端还是客户端, 建立连接后用这个Socket进行通信 public Socket communicateSocket = null; //服务端和客户端建立连接的方式稍有不同, 子类会重载 public abstract void Access(string IP, System.Action AccessAciton); //发送消息的函数 public void Send(string message) { if (communicateSocket.Connected == false) { throw new Exception("还没有建立连接, 不能发送消息"); } Byte[] msg = Encoding.UTF8.GetBytes(message); communicateSocket.BeginSend(msg,0, msg.Length, SocketFlags.None, ar => { }, null); } //接受消息的函数 public void Receive(System.Action<string> ReceiveAction) { //如果消息超过1024个字节, 收到的消息会分为(总字节长度/1024 +1)条显示 Byte[] msg = new byte[1024]; //异步的接受消息 communicateSocket.BeginReceive(msg, 0, msg.Length, SocketFlags.None, ar => { //对方断开连接时, 这里抛出Socket Exception //An existing connection was forcibly closed by the remote host communicateSocket.EndReceive(ar); ReceiveAction(Encoding.UTF8.GetString(msg).Trim('\0',' ')); Receive(ReceiveAction); }, null); } } public class ServerSocket:SocketFunc { //服务端重载Access函数 public override void Access(string IP, System.Action AccessAciton) { Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //本机预使用的IP和端口 IPEndPoint serverIP = new IPEndPoint(IPAddress.Any, 9050); //绑定服务端设置的IP serverSocket.Bind(serverIP); //设置监听个数 serverSocket.Listen(1); //异步接收连接请求 serverSocket.BeginAccept(ar => { base.communicateSocket = serverSocket.EndAccept(ar); AccessAciton(); }, null); } } public class ClientSocket:SocketFunc { //客户端重载Access函数 public override void Access(string IP, System.Action AccessAciton) { base.communicateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); base.communicateSocket.Bind(new IPEndPoint(IPAddress.Any, 9051)); //服务器的IP和端口 IPEndPoint serverIP; try { serverIP = new IPEndPoint(IPAddress.Parse(IP), 9050); } catch { throw new Exception(String.Format("{0}不是一个有效的IP地址!", IP)); } //客户端只用来向指定的服务器发送信息,不需要绑定本机的IP和端口,不需要监听 try { base.communicateSocket.BeginConnect(serverIP, ar => { AccessAciton(); }, null); } catch { throw new Exception(string.Format("尝试连接{0}不成功!", IP)); } } } }
using System; using System.Drawing; using System.Windows.Forms; using System.Net.Sockets; namespace Chatting { public partial class MainForm : Form { public MainForm() { InitializeComponent(); } SocketFunc socket; System.Action<string> ReceiveAction; System.Action AccessAction; private void MainForm_Load(object sender, EventArgs e) { //异步建立连接回调 AccessAction = () => { this.Invoke((MethodInvoker)delegate() { lblFriendIP.Visible = false; txtIP.Visible = false; btnConnect.Visible = false; btnWaitAccess.Visible = false; String friendIP = socket.communicateSocket.RemoteEndPoint.ToString(); lblState.Text = String.Format("连接成功. 对方IP:{0}", friendIP); try { socket.Receive(ReceiveAction); } catch (Exception exp) { MessageBox.Show(exp.Message, "错误"); } }); }; //异步接收消息回调 ReceiveAction = msg => { txtGetMsg.Invoke((MethodInvoker)delegate() { UpdateGetMsgTextBox(false, msg, Color.Red); }); }; } private void btnWaitAccess_Click(object sender, EventArgs e) { this.socket = new ServerSocket(); try { this.socket.Access("", this.AccessAction); } catch (Exception ecp) { MessageBox.Show(ecp.Message, "错误"); } lblState.Text = "等待对方连接..."; } private void btnConnect_Click(object sender, EventArgs e) { this.socket = new ClientSocket(); try { this.socket.Access(txtIP.Text, this.AccessAction); } catch (Exception ecp) { MessageBox.Show(ecp.Message, "错误"); } lblState.Text = "正在连接对方..."; } private void btnSendMsg_Click(object sender, EventArgs e) { string message = txtSendMsg.Text.Trim(); if (string.IsNullOrEmpty(message)) { MessageBox.Show("消息内容不能为空!", "错误"); txtSendMsg.Focus(); return; } try { socket.Send(message); } catch(Exception ecp) { MessageBox.Show(ecp.Message, "错误"); return; } UpdateGetMsgTextBox(true, message, Color.Blue); txtSendMsg.Text = ""; } private void UpdateGetMsgTextBox(bool sendMsg, string message, Color color) { string appendText; if (sendMsg == true) { appendText = "Me: " + System.DateTime.Now.ToString() + Environment.NewLine + message + Environment.NewLine; } else { appendText = "Friend: " + System.DateTime.Now.ToString() + Environment.NewLine + message + Environment.NewLine; } int txtGetMsgLength = txtGetMsg.Text.Length; txtGetMsg.AppendText(appendText); txtGetMsg.Select(txtGetMsgLength, appendText.Length - Environment.NewLine.Length*2 -message.Length); txtGetMsg.SelectionColor = color; txtGetMsg.ScrollToCaret(); } } }
namespace Chatting { partial class MainForm { /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的资源。 /// </summary> /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.txtSendMsg = new System.Windows.Forms.TextBox(); this.lblFriendIP = new System.Windows.Forms.Label(); this.txtIP = new System.Windows.Forms.TextBox(); this.btnConnect = new System.Windows.Forms.Button(); this.btnSendMsg = new System.Windows.Forms.Button(); this.btnWaitAccess = new System.Windows.Forms.Button(); this.txtGetMsg = new System.Windows.Forms.RichTextBox(); this.lblState = new System.Windows.Forms.Label(); this.SuspendLayout(); // // label1 // resources.ApplyResources(this.label1, "label1"); this.label1.BackColor = System.Drawing.Color.Transparent; this.label1.ForeColor = System.Drawing.SystemColors.Window; this.label1.Name = "label1"; // // label2 // resources.ApplyResources(this.label2, "label2"); this.label2.BackColor = System.Drawing.Color.Transparent; this.label2.ForeColor = System.Drawing.SystemColors.Window; this.label2.Name = "label2"; // // txtSendMsg // this.txtSendMsg.BorderStyle = System.Windows.Forms.BorderStyle.None; resources.ApplyResources(this.txtSendMsg, "txtSendMsg"); this.txtSendMsg.Name = "txtSendMsg"; // // lblFriendIP // resources.ApplyResources(this.lblFriendIP, "lblFriendIP"); this.lblFriendIP.ForeColor = System.Drawing.SystemColors.Window; this.lblFriendIP.Name = "lblFriendIP"; // // txtIP // resources.ApplyResources(this.txtIP, "txtIP"); this.txtIP.Name = "txtIP"; // // btnConnect // resources.ApplyResources(this.btnConnect, "btnConnect"); this.btnConnect.Name = "btnConnect"; this.btnConnect.UseVisualStyleBackColor = true; this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click); // // btnSendMsg // resources.ApplyResources(this.btnSendMsg, "btnSendMsg"); this.btnSendMsg.Name = "btnSendMsg"; this.btnSendMsg.UseVisualStyleBackColor = true; this.btnSendMsg.Click += new System.EventHandler(this.btnSendMsg_Click); // // btnWaitAccess // resources.ApplyResources(this.btnWaitAccess, "btnWaitAccess"); this.btnWaitAccess.Name = "btnWaitAccess"; this.btnWaitAccess.UseVisualStyleBackColor = true; this.btnWaitAccess.Click += new System.EventHandler(this.btnWaitAccess_Click); // // txtGetMsg // this.txtGetMsg.BackColor = System.Drawing.SystemColors.Window; this.txtGetMsg.BorderStyle = System.Windows.Forms.BorderStyle.None; resources.ApplyResources(this.txtGetMsg, "txtGetMsg"); this.txtGetMsg.Name = "txtGetMsg"; this.txtGetMsg.ReadOnly = true; // // lblState // resources.ApplyResources(this.lblState, "lblState"); this.lblState.ForeColor = System.Drawing.SystemColors.Window; this.lblState.Name = "lblState"; // // MainForm // this.AcceptButton = this.btnSendMsg; resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.Color.Navy; this.Controls.Add(this.lblState); this.Controls.Add(this.txtGetMsg); this.Controls.Add(this.btnWaitAccess); this.Controls.Add(this.btnSendMsg); this.Controls.Add(this.btnConnect); this.Controls.Add(this.txtIP); this.Controls.Add(this.lblFriendIP); this.Controls.Add(this.txtSendMsg); this.Controls.Add(this.label2); this.Controls.Add(this.label1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.MaximizeBox = false; this.Name = "MainForm"; this.Load += new System.EventHandler(this.MainForm_Load); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox txtSendMsg; private System.Windows.Forms.Label lblFriendIP; private System.Windows.Forms.TextBox txtIP; private System.Windows.Forms.Button btnConnect; private System.Windows.Forms.Button btnSendMsg; private System.Windows.Forms.Button btnWaitAccess; private System.Windows.Forms.RichTextBox txtGetMsg; private System.Windows.Forms.Label lblState; } }
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace Chatting { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } } }