最近工作上接到一个需求,做一个web展示数据的报表,最好能实时更新,不限制所用技术。
第一个问题:web服务器推送给浏览器新数据,一开始我想到的最快的最简单的方法就是 在web页面上js轮询了。因为我们的数据更新频率并不快。 后来觉得这种办法有点太土了。 或许长轮询更有效。 当然长轮询的技术很多了。 java 的dwr,c#的 signalr。c#还可以同过异步请求来自己写长轮询。
遇到的第二个问题,就是数据库如何通知web服务器更新数据,下面便是sql server2008的推送了,通过sql server的触发器,当数据表有变化时(增,删,改)就通过tcp请求服务器,服务器会在启动后开启端口一直监听,随时等待通信请求。当收到请求后,就从数据库读取新数据,推送给浏览器。整体大概就这样。
下面是数据库通知服务器。这是一个 winform的demo ,winfom就相当于我们展示数据的服务器了。
最后demo图:
winform:
程序启动后,开启端口监听,如果有收到通信,则通知 dataview更新数据。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Drawing; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Windows.Forms; using System.Threading; namespace sql_dependency { public partial class Form1 : Form { public Form1() { InitializeComponent(); } System.Data.SqlClient.SqlConnection conn = null; string _connstr = "Data Source = 10.6.154.251; database=Temp;user id=sa;pwd=MOcyou0543_"; System.Data.SqlClient.SqlCommand command = null; private void Form1_Load(object sender, EventArgs e) { conn = new System.Data.SqlClient.SqlConnection(_connstr); command = conn.CreateCommand(); command.CommandText = "select [A],[B],[C] From [Temp].[dbo].[Simple]"; SqlDependency.Start(_connstr);//启动 Thread t = new Thread(new ThreadStart(GetData)); t.Start(); } private void GetData() { SetData(); IPAddress localAddr = IPAddress.Parse("127.0.0.1"); TcpListener tcplistener = new TcpListener(localAddr, 10010); tcplistener.Start(); byte[] btServerReceive = new byte[2048]; string strServerReceive = string.Empty; while (true) { TcpClient tcp = tcplistener.AcceptTcpClient(); Console.WriteLine("Connected!"); NetworkStream ns = tcp.GetStream(); int intReceiveLength = ns.Read(btServerReceive, 0, btServerReceive.Length); strServerReceive = Encoding.ASCII.GetString(btServerReceive, 0, intReceiveLength); SetData(); tcp.Close(); } } private delegate void ChangeDataView(); private void SetData() { if (this.InvokeRequired) { this.Invoke(new ChangeDataView(SetData)); } else { using (SqlDataAdapter adapter = new SqlDataAdapter(command)) //查询数据 { System.Data.DataSet ds = new DataSet(); adapter.Fill(ds, 0, 100, "Simple"); dataGridView1.DataSource = ds.Tables["Simple"]; } } } private void Form1_Closed(object sender, FormClosedEventArgs e) { //清理现场 SqlDependency.Stop(_connstr); conn.Close(); conn.Dispose(); } } }
using System; using System.IO; using System.Net; using System.Net.Sockets; using Microsoft.SqlServer.Server; using System.Net.Sockets; namespace SqlDependency { public class Program { [SqlFunction(IsDeterministic = true, DataAccess = DataAccessKind.Read)] public static String WriteStringToFile(String FileFullPath, String Contend) { FileInfo Fi = new FileInfo(FileFullPath); if (!Fi.Directory.Exists) { Fi.Directory.Create(); } using (StreamWriter rw = File.CreateText(FileFullPath)) { rw.WriteLine(Contend); TcpClient tcpClient = new TcpClient(); try { if (tcpClient == null) { tcpClient = new TcpClient(); tcpClient.ReceiveTimeout = 20000; } if (tcpClient.Connected == false) { System.Net.IPAddress address = System.Net.IPAddress.Parse(Contend); System.Net.IPHostEntry ipInfor = System.Net.Dns.GetHostByAddress(address); string hostName = ipInfor.HostName; IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 10010); tcpClient.Connect(serverEndPoint); rw.Write(hostName); } rw.Write("连接成功,先发送指令"); // Translate the passed message into ASCII and store it as a Byte array. Byte[] data = System.Text.Encoding.ASCII.GetBytes("new data!"); NetworkStream stream = tcpClient.GetStream(); // Send the message to the connected TcpServer. stream.Write(data, 0, data.Length); stream.Close(); } catch (Exception e) { rw.Write(e.Message); } tcpClient.Close(); rw.Flush(); rw.Close(); return ""; } } } }
接下来,便开始配置sql server啦:
首先开启sql server的clr支持:
开启数据库CLR 支持 --exec sp_configure 'clr enabled', 1; --开始数据的验证 alter database dbname set TRUSTWORTHY on; RECONFIGURE接着在sql server 2008中,新建查询窗口。加载刚才编写的dll SqlDependency.dll,并注册方法,然后写触发器,当表数据有变化时,触发函数。:
use Temp;--数据库名 create assembly SqlDependency FROM 'D:\SqlDependency.dll'--程序集名称和地址 WITH PERMISSION_SET = UNSAFE GO --方法名写正确,为程序集中的方法名,注意参数个数 create function WriteStringToFile(@FileFullName as nvarchar(max), @FileContend AS nvarchar(max)) returns nvarchar(max) with returns null on null input external name [SqlDependency].[SqlDependency.Program].[WriteStringToFile] GO --编写触发器,传递参数以及 CREATE TRIGGER [dbo].[UserTableChangedEvent] on [dbo].[Simple] FOR INSERT, DELETE, UPDATE AS BEGIN DECLARE @Contend AS VARCHAR(100) DECLARE @FileName AS VARCHAR(MAX) SET @FileName ='D:\\MSG\\'+CONVERT(varchar(12) , getdate(), 112 )+'\\'+ convert(nvarchar(50), NEWID())+'.TXT' SET @Contend = '127.0.0.1'; Select dbo.WriteStringToFile(@FileName, @Contend) END GO注意,我的应用程序和 数据库在一台服务器上,所以地址都是127.0.0.1.可跟据实际填写正确地址。
再次在sql server中新建一个查询窗口,插入语句,进行测试吧。
如果过程中有问题,需要更新程序,方便地删除之上所创建的几个东东:
drop TRIGGER [dbo].[UserTableChangedEvent] drop function WriteStringToFile drop assembly SqlDependency之后将尝试在web 结合 signal实现实时推送数据给web页面。等待下篇。