0%

C/S程序是基于客户端和服务器的,在客户机编译新版本后将文件发布在更新服务器上,然后建立一个XML文件,该文件列举最新程序文件的版本号及最后修改日期。如程序文件较多的话可以通过工具自动生成XML文件。当某客户机运行程序后会自动下载这个XML文件,通过与本地机器上的版本号匹配,如本机上的版本号比服务器上的要旧,通知客户机运行更新程序。如用户更新了版本,将最新版本号写入配置文件,这样方便下一次匹配。

通过可行性分析可以使用下面3种方案下载1. 局域网共享文件夹下载 
2. Tcp/ip远程下载 
3. 通过Web方式下载

1.局域网共享文件夹下载

方式1适合局域网络,功能简单,通过File.Copy()方式从服务器直接复制文件。如建立企业级的VPN网络,也可作为局域网络直接复制。共享文件夹下载实现非常简单,我们只需要在服务器上共享一个文件夹并设定访问权限,然后将最新版本存放在这个目录,升级程序直接从这个目录Copy文件即可。

2.Tcp/ip远程下载
方式2是通过基于tcp/ip 的Socket组件编程来实现,这个机制必须要建立服务器用于监听客户的升级请求。简单设计思路是在服务器端启动TcpListener监听客户端的Socket连接,当Client发送连接请求,TcpListener捕获当前请求的Socket,并取到请求命令数据(字符串),然后由命令处理程序分析该字符串,如果字符串头部包含GET_FILE标识则为下载文件。举例说明:客户机向服务器程序发送请求命令:”GET_FILE|D:\PUBLISH\TEST.DLL”。首先TcpListener捕获当前Socket.并接收到命令数据”GET_FILE|D:\PUBLISH\TEST.DLL”,通过分析程序发现”GET_FILE”是特殊命令,表示下载文件请求。然后通过socket.SendFile(file=”D:\PUBLISH\TEST.DLL”)将文件传送给当前Socket。客户端由NetworkStream.Read()方法接收由服务器传来的文件。

**
3.通过Web方式下载。**
方式3是通过.NetFramework提供的WebClient组件下载文件。只需指定DownloadData()方法中参数address(url)。


下面讲解版本更新程序系统框架图:

主窗体<->下载控制器<->XmlLoader关系图

贴图图片

图解:

frmUpgrader窗体定义一个下载控制器及2个TreeView组件。
当点[检查更新]按钮,控制器调用当前下载器的DownloadServerXml()方法从服务器下载XmlServerFiles.xml文件。
下载后将文件交给XmlLoader分析,分析器创建XmlDocument实例。最后将XML分析器作为FileView构造器参数创建FileView实例。

FileView两个重要方法:
LoadTreeViewClient()方法创建客户端文件清单的TreeView。
LoadTreeViewServer()方法创建服务器端文件清单的TreeView。

TreeView的数据来源是两个Xml文件。

DownloadController 下载控制器,它负责建立下载策略实例及控制当前的下载器。

XmlLoader分析器主要功能是分析服务器端及本地的XML文件(XmlServerFiles.xml和XmlClientFiles.xml)。XmlLoader类图列举了所有方法,从图中可以看出,XmlLoader控制XmlDocument对象。通过XmlDocument.SelectSingleNode方法查找某个指定的文件,然后获取文件最后修改日期文件名等信息用于匹配。

IDownloader接口定义了下载器的主要方法,下面会详细讲解3个下载器的实现策略。

FileInfo是文件的实体类,结构相当简单,只包含文件名,物理路径及最后修改时间。


**三种下载器实现:
**

贴图图片

UML图说明:

frmUpgrader: 主窗体

DownloadController:下载控制器,如上图所示,它负责控制实现IDownloader接口的实例。

IDownloader: 下载器接口,下载器需要实现这个接口。

LAN_Downloader: 局域网复制文件下载器。

WebClient_Downloader: 广域网下载器,通过WebClient组件下载文件。

TcpIp_Downloader: Tcp/ip下载器。需要运行Tcp/ip服务器提供下载服务。

主窗体有[检查更新]及[开始更新]两个按钮。分别调用下载控制器的CheckUpdate()及Download()方法。


Tcp/IP下载器图解:
贴图图片

Tcp/IP下载器需要有服务器支持,使用tcp/ip传送文件简单设计思路是:在服务器端启动TcpListener监听客户端的Socket连接,当Client发送连接请求,TcpListener捕获当前请求的Socket,并取到请求命令数据(字符串),然后由命令处理程序分析该字符串,如果字符串头部包含GET_FILE标识则为下载文件。

UpgraderServer 是tcp/ip服务器的核心类。他控制TcpListener对象,TcpListener负责监听客户端的Socket连接。
当有下载文件请求时就调用SendFile()方法将文件传送给当前连接的Socket. 
Stop()方法用来关闭服务器.
SendFile()方法用来发送文件
StartListening()方法用户启动监听程序。

TcpListener是监听程序,它负责监听客户端的Socket连接。如有连接请求触发AccecptSocket事件。该事件返回当前请求的Socket对象。

UpgraderClient是tcp/ip客户端的核心类。他控制TcpClient对象, TcpClient对象负责监听来自服务器的请求。

DownloadFile()方法详解:
要明白客户端是如何接收文件,先要明白NetworkStream对象. NetworkStream是提供用于网络访问的基础数据流。客户机监听来自服务器的数据是通过NetworkStream.Read()方法实现的,当程序执行到ns.Read()方法时就开始监听,请看代码。

byte[] resBytes = new byte[256];  
int resSize;  
do
{
    
   resSize = ns.Read(resBytes, 0, resBytes.Length);

      string msg = Byte2Str(resBytes);
   if (msg.Trim().ToUpper() == “FILE_NOT_FOUND”)
   {
      if (_writeOutput != null) _writeOutput(“找不到文件:” + file);
      break;
   }
   if (resSize == 0) break;

      ms.Write(resBytes, 0, resSize);
   } while (ns.DataAvailable);
   ns.Close();

请注意while (ns.DataAvailable)这段代码,当接受到来自服务器的数据时DataAvailable=True,然后通过NetworkStream.Read方法每次读取256字节,直到读取完所有数据时DataAvailable=false。这时监听工作完成,跳出while循环。最后调用FileStream对象保存文件。

TcpIp_Downloader Tcp/IP下载器方法:

Download():下载XmlServerFiles.xml定义的所有文件。
DownloadFile(FileInfo file):下载单个文件。
DownloadServerXml():下载服务器上的文件清单。
Init() //初始化下载器。


IDownloader下载器接口定义

 
 
 
public interface IDownloader
{
   void Init();  
   void Download();

      FileInfo DownloadFile(FileInfo file); 
   XmlLoader XmlServer { get;} //服务器上的Xml文件
   XmlLoader XmlLocal { get;}//客户机上的Xml文件
   
   int DownloadsCount { get;} //下载成功的文件总数
   int DownloadFaliedCount { get;}//下载失败的文件总数
   
   void DownloadServerXml();  
   void SetProgressBar(ToolStripProgressBar progress);
   void SetTrace(ListBox logList);
}

**下载器类型
**

 
 
 
public enum DownloadType
{
   Intranet = 1,
   TcpIp = 2,
   WebDownload = 3
}

下载控制器,该控件器可以创建3种不同的下载器。

 
 
 
 
public class DownloadController
{
   private IDownloader _downloader = null;
   public IDownloader CurrentDownloader { get { return _downloader; } }

      private TreeView _tvServerFiles;
   private TreeView _tvLocalFiles;
   private ListBox _log;
   private ToolStripProgressBar _progress = null;

      public DownloadController(IDownloader downloader)
   {
      _downloader = downloader;
   }

       
    
    
   public static DownloadController Create(DownloadType type)
   {
      if (DownloadType.Intranet == type)
      return new DownloadController(new LAN_Downloader());
      if (DownloadType.TcpIp == type)
      return new DownloadController(new TcpIp_Downloader());
      if (DownloadType.WebDownload == type)
      return new DownloadController(new WebClient_Downloader());
      return null;
   }

      public void Download()
   {
      _log.Items.Add(“开始下载….”);
      _downloader.SetProgressBar(_progress);
      _downloader.Init();
      _downloader.SetTrace(_log);
      _downloader.Download();
      _log.Items.Add(“下载完成….”);

            _log.Items.Add(“刷新文件列表….”);
       
      new FileView(_downloader.XmlLocal, _progress).LoadTreeViewClient(_tvLocalFiles, null);
      _log.Items.Add(“完成.”);
   }

      public void CheckUpdate()
   {
      _log.Items.Add(“开始检查服务器上有用更新….”);
      _downloader.SetProgressBar(_progress);
      _downloader.Init();
      _downloader.SetTrace(_log);

             
      new FileView(_downloader.XmlServer, _progress).LoadTreeViewServer(_tvServerFiles);
      new FileView(_downloader.XmlLocal, _progress).LoadTreeViewClient(_tvLocalFiles, _downloader.XmlServer);

            if (_downloader.XmlLocal.HasNewVersion)
      _log.Items.Add(“服务器上有最新版本,请更新.”);
      else
      _log.Items.Add(“检查完成,没有发现新版本.”);
   }

      public void BindControls(TreeView tvServerFiles, TreeView tvLocalFiles, ListBox log, ToolStripProgressBar progress)
   {
      _progress = progress;
      _tvLocalFiles = tvLocalFiles;
      _tvServerFiles = tvServerFiles;
      _log = log;
   }
}

**文件对象定义
**

 
 
 
public class FileInfo
{
   private string _name = “”;
   private string _FullPath = “”;
   private DateTime _ModifyTime = DateTime.MinValue;

      public FileInfo() { }

      public FileInfo(string fileName, string fullPath, DateTime lastEditDate)
   {
      this.Name = fileName;
      this.FullPath = fullPath;
      this.ModifyTime = lastEditDate;
   }

       
    
    
   public string Name { get { return _name; } set { _name = value; } }

       
    
    
   public string FullPath { get { return _FullPath; } set { _FullPath = value; } }

       
    
    
   public DateTime ModifyTime { get { return _ModifyTime; } set { _ModifyTime = value; } }

      public override string ToString()
   {
      return this.Name;
   }
}

**XML文件解释器,分析服务器/客户端的xml文件
**

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Collections;
using System.Windows.Forms;

namespace CSFramework.Tech.AutoUpgraderLib
{
    
    
    
   public class XmlLoader
   {
      private XmlDocument _xml;
      public XmlDocument XML { get { return _xml; } }

            private string _xmlFilePath;

            public XmlLoader(string xmlFile)
      {
         _xml = new XmlDocument();
         _xmlFilePath = xmlFile;
         if (System.IO.File.Exists(xmlFile)) _xml.Load(xmlFile);
      }

            private bool _HasNewVersion = false;

             
      public bool HasNewVersion { get { return _HasNewVersion; } set { _HasNewVersion = value; } }

             
       
       
       
      public static XmlLoader CreateEmpty(string xmlFilePath)
      {
         string xml =
         “ \r\n” +
         “ \r\n” +
         “本机最近更新清单 \r\n” +
         “ \r\n” +
         “<LastUpdateTime value=\“\“ /> \r\n” +
         “<Version value=\“\“ /> \r\n” +
         “
\r\n” +
         “ \r\n” +
         “
\r\n” +
         “
\r\n”;

                   
         if (File.Exists(xmlFilePath)) File.Delete(xmlFilePath);

                  string dir = Path.GetDirectoryName(xmlFilePath);
         if (!Directory.Exists(dir)) throw new Exception(“不存在目录:” + dir);

                  StreamWriter sw = File.CreateText(xmlFilePath);
         sw.Write(xml);
         sw.Flush();
         sw.Close();

                  return new XmlLoader(xmlFilePath);
      }

             
      public void SetLastUpdateInfo(string version, DateTime lastUpdateTime)
      {
         XmlNode nodeVersion = _xml.SelectSingleNode(“Upgrader/Application/Version”);
         XmlNode nodeTime = _xml.SelectSingleNode(“Upgrader/Application/LastUpdateTime”);

                  nodeVersion.Attributes[“value”].Value = version;
         nodeTime.Attributes[“value”].Value = lastUpdateTime.ToString();
      }

             
      public string GetVersion()
      {
         XmlNode ver = _xml.SelectSingleNode(“Upgrader/Application/Version”);
         if (ver != null)
         return ver.Attributes[“value”].Value;
         else
         return “”;
      }

             
       
       
       
       
       
      public bool CompareFile(FileInfo file)
      {
         if (file == null) return true; 

                  XmlNode node = this.GetFileNode(file.FullPath);
         if (node == null) return true; 

                  DateTime date;
         if (DateTime.TryParse(node.Attributes[“lastModify”].Value, out date))
         {
            return file.ModifyTime.CompareTo(date) > 0;
         }

                  return false;
      }

             
       
       
      public bool CompareNode(XmlNode node1, XmlNode node2)
      {
         if (node1 == null || node2 == null) return false;

                  DateTime date1 = Common.StrToDate(node1.Attributes[“lastModify”].Value);
         DateTime date2 = Common.StrToDate(node2.Attributes[“lastModify”].Value);
         return date1.CompareTo(date2) > 0;
      }

             
      public XmlNode GetFileNode(string fullPath)
      {
         string xPath = @”Upgrader/Files/File[@fullPath=’’” + fullPath + “‘’]“;
         XmlNode node = _xml.SelectSingleNode(xPath);
         return node;
      }

             
      public FileInfo GetFileInfo(string fullPath)
      {
         XmlNode node = this.GetFileNode(fullPath);
         if (node != null)
         {
            FileInfo fi = new FileInfo();
            fi.FullPath = node.Attributes[“fullPath”].Value;
            fi.ModifyTime = Common.StrToDate(node.Attributes[“lastModify”].Value);
            fi.Name = node.Attributes[“fileName”].Value;
            return fi;
         }
         return null;
      }

             
       
       
       
       
      public void AddOrUpdateHistory(FileInfo serverFile, FileInfo clientFile)
      {
         XmlNode node = GetFileNode(serverFile.FullPath);
         if (node == null)
         {
            XmlNode fileRoot = _xml.SelectSingleNode(“Upgrader/Files”);
            node = _xml.CreateNode(XmlNodeType.Element, “File”, “”);
            fileRoot.AppendChild(node);
         }
         node.RemoveAll(); 
         node.Attributes.Append(CreateAttribute(“fileName”, clientFile.Name));
         node.Attributes.Append(CreateAttribute(“fullPath”, serverFile.FullPath));
         node.Attributes.Append(CreateAttribute(“lastModify”, serverFile.ModifyTime.ToString()));
      }

            private XmlAttribute CreateAttribute(string name, string value)
      {
         XmlAttribute attr = _xml.CreateAttribute(name);
         attr.Value = value;
         return attr;
      }

             
      public void Save()
      {
         _xml.Save(_xmlFilePath);
      }

             
       
       
      public int FilesCount
      {
         get
         {
            XmlNode node = _xml.SelectSingleNode(“Upgrader/Files”);
            return node.ChildNodes.Count;
         }
      }

             
       
      
      public IList GetFiles()
      {
         IList files = new ArrayList();
         XmlNode node = _xml.SelectSingleNode(“Upgrader/Files”);
         foreach (XmlNode n in node.ChildNodes)
         {
            FileInfo sf = new FileInfo();
            sf.FullPath = n.Attributes[“fullPath”].Value;
            sf.ModifyTime = Common.StrToDate(n.Attributes[“lastModify”].Value);
            sf.Name = n.Attributes[“fileName”].Value;
            files.Add(sf);
         }
         return files;
      }
   }
}

refer to : http://www.csframework.com/archive/1/arc-1-20110626-1655.htm

单点登录

    Single Sign On,简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 

单点登录原理

  • 存储信任
  • 验证信任

 

CAS

  Central Authentication Service  是 Yale  大学发起的一个企业级的、开源的项目,旨在为 Web  应用系统提供一种可靠的单点登录解决方法(属于 Web SSO)。CAS  开始于 2001  年, 并在 2004  年 12  月正式成为 JA-SIG  的一个项目。 

CAS执行身份验证

  

  下面是这个身份验证协议中的主要步骤。

  • 用户尝试使用应用程序的 URL 访问应用程序。用户被重定向到 CAS 登录 URL,采用的是 HTTPS 连接,他请求的服务的名称作为参数传递。这时向用户显示一个用户名/密码对话框。
  • 用户输入 ID 和密码,CAS 对他进行身份验证。如果身份验证失败,目标应用程序根本不会知道这个用户曾经试图访问它 —— 用户在 CAS 服务器上就被拦住了。
  • 如果身份验证成功,CAS 就将用户重定向回目标应用程序,并在 URL 中附加一个称为 ticket 的参数。然后,CAS 尝试创建一个称为 ticket-granting cookie 的内存 cookie。这是为了以后进行自动的重新验证;如果存在这个 cookie,就表示这个用户已经成功地登录了,用户就不需要再次输入他的用户名和密码。
  • 应用程序要检查这个 ticket 是否正确,以及是否代表一个有效用户;检查的方法是,打开一个 HTTPS 连接来调用 CAS serviceValidate URL,并作为参数传递 ticket 和服务名称。CAS 检查这个 ticket 是否有效,以及是否与请求的服务相关联。如果检查成功,CAS 就将用户名返回给应用程序。

CAS服务搭建

环境准备:

   

配置服务端 

  • 解压CAS Service,将解压后的文件中modules文件夹中的cas-server-webapp-3.4.8.war文件拷贝的%TOMCAT_HOME%\webapps下,并修改文件名为:cas.war    

  • 配置host

  

    

  配置完成后,启动tomcat ,浏览器输入 https://test.cas.com:8080/cas    

    

  对于提示,必须使用HTTPS,不用理会,如果你有证书也可以配置在tomcat下,不要尝试使用keytool生成,利用keytool生成的证书,客户端在调用时无法保证有效的链接。  

    

  

  输入用户名admin和密码admin登录则会出现

  

  至此,说明服务端配置成功。

CAS客户端搭建

  下载.NET CAS client  https://wiki.jasig.org/display/CASC/.Net+Cas+Client    http://downloads.jasig.org/cas-clients/dotnet/dotnet-client-1.0.2-bin.zip  

  源码下载 https://github.com/Jasig/dotnet-cas-client/tree/master/DotNetCasClient  建议下源码,方便调试与测试  

  客户端服务配置:https://wiki.jasig.org/display/CASC/.Net+Cas+Client  

  创建项目  TestMvcCas MVC4.0架构

  

  安装DotNetCasClient NuGet  http://www.nuget.org/packages/DotNetCasClient

     

  也可以将源码附加到项目中,方便调试

  

  配置web.config

  参见  https://wiki.jasig.org/display/CASC/.Net+Cas+Client   每个节点配置 这上面有详细的介绍

   

配置页面权限验证

运行解决方案,会看到未登陆的用户,会自行跳转到cas登陆界面

登陆成功之后,则会正常跳转,也可以看到站点下存在相应的COOKIE

当前登陆用户采用默认的admin账号,如果需要集成数据库,可以参考下

http://my.oschina.net/indestiny/blog/200768

下一篇争取能实现数据库集成和自定义返回数据

参考资料

  http://blog.csdn.net/cutesource/article/details/5838693

  http://www.coin163.com/java/cas/cas.html

  http://www.ibm.com/developerworks/cn/web/wa-singlesign/

  http://www.cnblogs.com/zhenyulu/archive/2013/01/27/2878935.html

Think You Can Wait - The National

I was drifting, crying (我曾四处漂泊,独自哭泣)

I was looking for an island (也寻找过一个归属)

I was slipping under (可是我却越陷越深)

I’ll pull the devil down with me one way or another (无论如何我都将摧毁心中的恶魔)

I’m out of my mind (我是不是疯了)

think you can wait (觉得你会等我)

I’m way off the line (想想也很离谱)

think you can wait (居然认为你会等我)

We’ve been running a sleepless run (我们都日夜不眠的奔波)

Been away from the baby way too long (已经忘了最初的样子)

We’ve been holding a good night gone (曾今美好的夜晚已经消失)

We’ve been losing our exits one by one (渐渐迷失自己)

I’m out of my mind (我真的是疯了)

think you can wait (觉得你会等我)

I’m way off the line (我想的太离谱了)

think you can wait (居然认为你会等我)

Did I? (是我想的那样吗?)

(all I have is all) (我只有你了)

Think you can wait? (我一直觉得你会等我)

Did I? (是吗)

(all I have is all) (我真的只有你了)

Think you can wait? (真的觉得你会等我)

What I’m thinking is simple (我想的很简单)

I’ll sell apples and ice water at the temple (在神庙里卖些苹果和冰水)

I won’t make trouble (我不会去惹麻烦)

I’ll pull the devil down with me one way or another (只想将心中的恶魔摧毁)

We’ve been running a sleepless run (我们日夜不眠的奔波)

Been away from the baby way too long (是不是已经忘了最初的梦想)

We’ve been holding a good night gone (曾经美好的夜晚已经消失)

We’ve been losing our exits one by one (渐渐迷失自己)

I’ll try (我会努力)

I’ll try (我会继续尝试)

but I couldn’t be better (但我真的快坚持不下去了)

(all I have is all) (我已经倾尽所有了)

I’ll try (我从未放弃)

but I couldn’t be better (但我真的只能这样了)

(all I have is all) (我所拥有的只有这些了)

I’ll try, but I couldn’t be better (我努力着,可是已经力不从心)

(all I have is all) (我,只有你了)

引言:

前面的介绍专题中有朋友向我留言说介绍下关于P2P相关的内容的,首先本人对于C#网络编程也不是什么大牛,因为能力的关系,也只能把自己的一些学习过程和自己的一些学习过程中的理解和大家分享下的,下面就进入正题——P2P(Peer to Peer)编程

一、P2P的介绍

 首先,现在大家熟知的BT、电驴、迅雷、QQ、MSN和PPlive等都是基于P2P方式实现的软件,并且对等联网(Peer to Peer,P2P)将是互联网的发展方向,因此对于P2P技术的了解显得非常的重要,下面就来介绍下P2P架构

在P2P技术之前,我们所有的网络应用都采用C/S或者B/S架构来实现的,然而在之前C/S架构的应用程序中,客户端软件向服务器发出请求,服务器然后对客户端请求做出响应,在这种情况下,如果客户端越多,此时服务器的压力就越大。然而采用P2P技术实现的每台计算机既是客户端,也是服务器,他们的功能都是对等的。对于安装了P2P软件(如迅雷,QQ等)的计算机加入一个共同的P2P网络,网络中的节点之间可以直接进行数据传输和通信。

1.1 P2P架构和C/S架构的比较

C/S架构有下面的缺点(其实上面的简单介绍中也讲到过):

1. 服务器负担过重。当大量用户访问C/S系统的服务器时,服务器常常会出现网络堵塞等现象,这时候,我们可能会通过增加投资提高服务器的硬件性能

2. 系统稳健性和服务器关联密切。指的是——如果服务器出现了问题时,整个系统的运行将会瘫痪(感觉是面向对象中经常强调的原则——低耦合原则)

然而P2P具有下面的特点:

1.对等模式

P2P系统中的客户端能够同时扮演客户端和服务器的角色,使两台计算机之间能够不通过服务器直接进行信息分享(QQ中当好友在线的时候发信息时,相信此时是不需要经过服务器转发的,只有当给离线好友发送消息时,此时应该会先把消息发送到服务器端存储起来,当好友再次登录的时候,会和服务器进行连接,服务器会进行判断是不是给这个用户的信息来决定是否转发,QQ软件的实现属于混合型P2P结构的, 这个会在后面的P2P系统分类中介绍。)

注:括号中都是我个人的一些理解,如果有什么说错的地方请大家及时更正我,这样我会及时的更新以免误导大家,谢谢大家监督。

2. 网络资源的分布式存储

在C/S架构中,所有客户端都直接从服务器下载所有数据资源,这样势必会加重服务器的负担,而P2P则改变了以服务器为中心的状态,使每个节点可以先从服务器上个下载一部分,然后再相互从对方或者其他节点下载其余部分。采用这种方式,当大量客户端同时下载时,就不会形成网络堵塞现象了。

1.2 P2P系统的分类

使用P2P技术的系统分为两类:(1)单纯型P2P——没有专用的服务器。安装了P2P软件的各个计算机可以直接通信

(2)混合型P2P——有专用的服务器,此时的服务器一般叫索引服务器,此服务器与C/S架构下的服务器不同,在C/S架构下所有资源都存储在服务器中,所有传递的信息都要经过服务器,而在混合型P2P系统中的索引服务器仅仅起到协调和扩展的功能,资源不是全部存储在服务器上,而是分布在各个电脑上,安装了P2P软件的电脑开始全部和索引服务器连接,以便告知自己监听的IP地址和端口号,然后再通过索引服务器告诉其他与自己连接的电脑,每台计算机的连接和断开都通过服务器通知网络上有联系的计算机,这样就减轻了每台计算机搜索其他计算机的负担,但是信息的传递还是通过点对点的方式来完成(这里可以以QQ为例,当我们电脑上安装了QQ这类P2P软件时,安装了QQ这类软件的计算机就会加入一个P2P网络,并且登陆的时候都会与索引服务器建立连接,通过连接来告诉服务器自己的IP地址和端口号,当我们找一个好友聊天时,此时自己的计算机和好友的计算机都会与服务器端口连接,但是要互相发送消息,自己的计算机必须知道好友计算机的IP地址和端口号才可以通信,这样的工作正是通过索引服务器来告知对方的–指的是告诉自己的计算机好友的计算机的IP地址和端口号,告诉好友的计算机自己的IP地址和端口号,这样双方就可以不通过服务器直接通信了。)

1.3 主流P2P应用分类

P2P 网络应用大致可以分为三类—— 1. 文件共享类,例如迅雷,BT等软件都是文件共享类的应用

  1. 即时通信类,例如QQ,MSN等软件都是属于即时通信类

  2. 多媒体传输类,例如在线视频直播软件,PPlive等软件

从上面的分类可以看出,现在网络上流行的软件都采用了P2P技术来实现的, 但是它们的实现肯定不是单纯的只采用P2P技术来实现的, 而是采用多种技术来实现的, 在下一专题中将介绍利用TCP,UDP和P2P等技术来实现类似QQ的一个即时通信程序,希望通过此程序可以综合前面专题介绍的内容以及帮助大家对QQ等软件的实现原理有了解。

二、P2P的基本原理

在前面我们对P2P的一些知识进行的简单的介绍, 通过前面的介绍相信大家对P2P的技术有了一定的了解,但是要自己开发一个P2P的应用当然必须了解P2P技术的实现原理的,下面就介绍下P2P实施的基本原理。

安装了P2P软件后,首先双方要进行通信,必须能够发现对方(指的就是知道对方的IP地址和端口号),一旦发现了对方后才可以进行通信,所以P2P应用程序一般分为发现、连接和通信3个阶段。 发现阶段负责动态定位通信方的网络位置;连接阶段负责在双方建立网络连接,通信阶段负责在双方之间传输数据。

2.1 发现阶段

一台计算机要和另外一台计算机通信,必须知道对方的IP地址和监听端口,否则就无法向对方发送消息。在之前的C/S架构中,服务器的IP地址一般固定不变的,并且提供服务的计算机域名也一般不会改变,所以为了方便客户端访问,一些Web服务器在DNS(DNS其实就是域名和IP地址的一个映射)中进行了注册,客户机可以利用域名解析机制将服务器域名解析为IP地址,然后在P2P应用中,各个对等节点(计算机或资源)可以随时加入和随时离开,并且对等节点的IP地址也不是固定的,所以不能采用DNS的机制来获取P2P架构中的对等节点的信息。

目前,在单纯型P2P中,针对如何发现对等节点,各种P2P技术采用的协议和标准都不一样,微软在.net 支持对等名称解析协议(Peer name Resolution Protocol, PNRP),该协议可以发现对等节点的信息,通过无服务器的解析功能将任何资源解析为一组IP地址和端口号,在后面的实现的简单程序用的就是这个协议来完成发现阶段的。

2.2 连接和通信阶段

 完成对等节点的发现后,接下来就可以根据需要,选择TCP、UDP或者其他协议完成数据传输。如果选择TCP,则需要先建立连接,再利用该连接传输数据,关于TCP的内容可以查看我之前的专题;如果选择UDP,则无须建立连接,直接在对等节点之间通信就可以了。

三、.net平台对P2P编程的支持

 之前在发现阶段也介绍了.Net平台对P2P编程的支持的,然后微软帮我们已经封装好了对PNRP协议的实现,这些类在System.NET.PeerToPeer命名空间里(微软现在很多东西都帮我们封装了,这样可以方便我们开发应用程序,感觉微软的做的东西都是这样,把程序的实现都帮我们做好了,我们开发程序的时候只要关注业务逻辑就好了的, 这样做当然有好处也有坏处的, 我觉得好处就是缩短软件的开发周期,让花更多的时间去实现软件真真的业务逻辑方面的东西,不好的地方就是现在的程序员就不能叫程序员,所以园子里面有很多人都称码农,这些我自己的观点了)

3.1 对等名称解析协议(PNRP)

PNRP可以完成对等名称的注册和解析(可以和DNS对比来理解)。

3.1.1 基本概念

第一个介绍的是对等名称的概念,我们将每一个网络资源(包括计算机,P2P应用程序、视频、Mp3或其他文档等资源)抽象为对等节点,对等节点名称当然就是对等节点的名称。对等节点名称简称为对等名,分为安全的和不安全的两种形式,不安全的名称仅由文本字符串组成,任何人都可以注册一个相同的不安全对等名称;安全的由一个公钥/私钥(代表唯一)对支持,所以使用PNRP注册时,不会受到欺骗,对等名称的格式如下:

Authority.Classifier

Authority的值取决于该名称的安全类型。对于不安全的类型,Authority为单字符“0”,而对于安全的对等名称,Authority由40个十六机制字符组成

Classifier是用户定义的用于标志对等节点的字符串,最大长度为150个Unicode字符。例如,对等名称0.PeerNametest1就是一个不安全的对等名称。

第二个概念就是云(Cloud)的概念,安装了相同P2P软件的计算机会加入一个共同的P2P网络中,才能相互识别各自拥有的资源并顺利进行P2P通信。微软PNPR协议将这个P2P网络称为“云”。云是指一组可以通过P2P网络相互识别的对等节点及其上资源的集合。云中的所有对等节点都可以解析注册到该云中的其他任何资源所在的位置(IP+Port),一个对等节点上的某个资源可以同时注册到多个云中。

PNPR目前使用了两种云——本地云和全局云。一个对等名称若注册到链接一本地云,就意味着只有同一本地网络上的其他对等节点可以解析该名称。而注册到全局云上的对等名称则允许IPv6互联网的任何对等节点解析。

注:全局云是基于IPv6协议的,并不支持IPv4,如果不存在IPv6地址,则不会出现全局云,也无法加入全局云。由于现在网上绝大多数应用使用的仍然是IPv4的地址,所以我们通常的P2P编程还用不到全局云,而只能使用默认的本地云。

3.1.2 名称注册

任何资源要被网络上的其他计算机识别到,首先必须注册进P2P网络,名称注册就是将包含对等节点信息的对等名称发布到云中,以便其他对等节点解析。一个资源如果注册到云中后,就可以被云中的其他对等节点解析和访问。

关于名称注册的具体内容,在后面的P2P的程序中也会使用的, 相信大家可以通过代码来进一步理解名称的注册,这里就先介绍到这里的

3.1.3 名称解析

名称解析是指利用对等名称获取到云中资源所在对等节点的IP地址和端口号的过程(和DNS解析原理一样)。PNPR名称解析仅能够注册到云中的其他对等节点资源,而不能发现自身注册的资源

PNPR协议没有使用索引服务器,所以为了完成解析,云中的每个对等节点都存储一些PNRP ID的缓存记录。PNPR缓存中的都含有PNPR ID和应用程序的IP地址和端口号。名称解析的步骤为——首先在本地计算机对等节点的缓存中查找目标资源,如果没有,则在缓存中的临近节点查看,这样循环下去,直到找到目标资源所在的对等节点位置。

3.2 PeerToPeer命名空间

上面主要介绍了PNPR协议的工作过程(相当于是理论部分了),下面就介绍下.net 为我们封装好PNPR的类的使用。这里就简单指明几个常用类的使用,并附上MSDN的链接,大家可以直接点链接进行查看详细内容,因为后面的P2P程序中也有具体的使用,所以这里就不一一列出来了。

 这些类基本上从类名都可以大致知道他们的用途的,所以在这里就没有一一介绍的,只是附上了MSDN的链接。

四、实现P2P应用程序

以上介绍了那么多P2P的相关的知识,主要是为了实现一个自定义的P2P应用程序做准备的,这里就简单实现了资源发现的一个程序。

核心代码:

 对等名称的注册代码:

  1.         private void btnRegister_Click(object sender, EventArgs e)

  2.         {  

  3.             if (tbxResourceName.Text == “”)

  4.             {  

  5.                 MessageBox.Show(“请输入发布的资源名!”, “提示”);

  6.                 return;

  7.             }  

  8.             PeerName resourceName = new PeerName(tbxResourceName.Text, PeerNameType.Unsecured);

  9.             resourceNameReg[seedCount] = new PeerNameRegistration(resourceName, int.Parse(tbxlocalport.Text));

  10.             resourceNameReg[seedCount].Comment =resourceName.ToString();  

  11.             resourceNameReg[seedCount].Data = Encoding.UTF8.GetBytes(string.Format(“{0}”, DateTime.Now.ToString()));

  12.             resourceNameReg[seedCount].Start();  

  13.             seedCount++;  

  14.             comboxSharelist.Items.Add(resourceName.ToString());  

  15.             tbxResourceName.Text = “”;

  16.         }

  

// 注册资源
private void btnRegister_Click(object sender, EventArgs e)
{
if (tbxResourceName.Text == “”)
{
MessageBox.Show(“请输入发布的资源名!”, “提示”);
return;
}

        // 将资源名注册到云中
        // 具体资源名的结构在博客有介绍
        PeerName resourceName = new PeerName(tbxResourceName.Text, PeerNameType.Unsecured);
        // 用指定的名称和端口号初始化PeerNameRegistration类的实例
        resourceNameReg\[seedCount\] = new PeerNameRegistration(resourceName, int.Parse(tbxlocalport.Text));
        // 设置在云中注册的对等名对象的其他信息的注释
        resourceNameReg\[seedCount\].Comment =resourceName.ToString();
        // 设置PeerNameRegistration对象的应用程序定义的二进制数据
        resourceNameReg\[seedCount\].Data = Encoding.UTF8.GetBytes(string.Format("{0}", DateTime.Now.ToString()));
        // 在云中注册PeerName(对等名)
        resourceNameReg\[seedCount\].Start();
        seedCount++;
        comboxSharelist.Items.Add(resourceName.ToString());
        tbxResourceName.Text = "";
    }

名称解析代码(搜索资源):

  1.         private void btnSearch_Click(object sender, EventArgs e)

  2.         {  

  3.             if (tbxSeed.Text == “”)

  4.             {  

  5.                 MessageBox.Show(“请先输入要寻找的种子资源名”, “提示”);

  6.                 return;

  7.             }  

  8.             lstViewOnlinePeer.Items.Clear();  

  9.             PeerName searchSeed = new PeerName(“0.” + tbxSeed.Text);

  10.             PeerNameResolver myresolver = new PeerNameResolver();

  11.             PeerNameRecordCollection recordCollection = myresolver.Resolve(searchSeed);  

  12.             foreach (PeerNameRecord record in recordCollection)

  13.             {  

  14.                 foreach(IPEndPoint endpoint in record.EndPointCollection)

  15.                 {  

  16.                     if (endpoint.AddressFamily.Equals(AddressFamily.InterNetwork))

  17.                     {  

  18.                         ListViewItem item = new ListViewItem();

  19.                         item.SubItems.Add(endpoint.ToString());  

  20.                         item.SubItems.Add(Encoding.UTF8.GetString(record.Data));  

  21.                         lstViewOnlinePeer.Items.Add(item);  

  22.                     }  

  23.                 }  

  24.             }  

  25.         }

  

// 搜索资源
private void btnSearch_Click(object sender, EventArgs e)
{
if (tbxSeed.Text == “”)
{
MessageBox.Show(“请先输入要寻找的种子资源名”, “提示”);
return;
}

        lstViewOnlinePeer.Items.Clear();
        // 初始化要搜索的资源名
        PeerName searchSeed = new PeerName("0." + tbxSeed.Text);
        // PeerNameResolver类是将节点名解析为PeerNameRecord的值(即将通过资源名来查找资源名所在的地址,包括IP地址和端口号)
        // PeerNameRecord用来定于云中的各个节点
        PeerNameResolver myresolver = new PeerNameResolver();

        // PeerNameRecordCollection表示PeerNameRecord元素的容器
        // Resolve方法是同步的完成解析
        // 使用同步方法可能会出现界面“假死”现象
        // 解决界面假死现象可以采用多线程或异步的方式
        // 关于多线程的知识可以参考本人博客中多线程系列我前面UDP编程中有所使用
        // 在这里就不列出多线程的使用了,朋友可以自己实现,如果有问题可以留言给我一起讨论
        PeerNameRecordCollection recordCollection = myresolver.Resolve(searchSeed);
        foreach (PeerNameRecord record in recordCollection)
        {
            foreach(IPEndPoint endpoint in record.EndPointCollection)
            {
                if (endpoint.AddressFamily.Equals(AddressFamily.InterNetwork))
                {
                    ListViewItem item = new ListViewItem();   
                    item.SubItems.Add(endpoint.ToString());
                    item.SubItems.Add(Encoding.UTF8.GetString(record.Data));
                    lstViewOnlinePeer.Items.Add(item);
                }
            }
        }
    }

运行结果截图:
为了演示资源发现的效果,所以同时开启了本程序的3个进程来模拟网络上对等的3个计算机节点,当在资源名中输入资源后会在分享下列表中显示出本地分享的资源,同时在P2P网络上的其他计算机可以通过资源名称搜索该资源,将得到的资源名称和发布时间显示在ListView控件中,下面是程序的运行结果:

五、总结

到这里P2P编程的介绍就结束了, 本专题只是简单演示了一个资源发现的程序,资源发现是P2P的核心技术,正是因为P2P技术实现了互联网范围的资源发现,才使得它被广泛应用,像我们经常用的下载工具——迅雷,迅雷就是典型采用P2P技术的应用程序,当我们在迅雷页面中输入“爱情公寓3”(相当于本专题中种子文本框填的资源名)然后点击搜索后迅雷会自动启动“狗狗搜索”并显示资源链接列表,当我们点击连接就可以进行下载了。不过迅雷肯定不是使用微软的PNPR,而是迅雷自主研发的与PNPR作用一样的协议——都是完成解析网络资源的地址的作用,当然,迅雷软件中也采用了其他的一些技术,如搜索引擎等。

希望本专题可以帮助大家对P2P技术有所了解,如果有任何的问题都可以通过留言的方式来一起讨论,在下一个专题中将介绍实现一个类似QQ的程序。

源码附上:资源发现程序

1.WebRequest方式

Post:

复制代码

private void button1_Click(object sender, EventArgs e)
{ string ss= HttpPost(“http://localhost:41558/api/Demo/PostXXX“, “{Code:\“test089\“,Name:\“test1\“}”);
} public static string HttpPost(string url, string body)
{ //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
Encoding encoding = Encoding.UTF8;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = “POST”;
request.Accept = “text/html, application/xhtml+xml, */*“;
request.ContentType = “application/json”; byte[] buffer = encoding.GetBytes(body);
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
HttpWebResponse response = (HttpWebResponse)request.GetResponse(); using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{ return reader.ReadToEnd();
}
}

复制代码

Get:

复制代码

private void button1_Click(object sender, EventArgs e)
{ string ss = HttpGet(“http://localhost:41558/api/Demo/GetXXX?Name=北京“);
} public static string HttpGet(string url)
{ //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
Encoding encoding = Encoding.UTF8;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = “GET”;
request.Accept = “text/html, application/xhtml+xml, */*“;
request.ContentType = “application/json”;

        HttpWebResponse response \= (HttpWebResponse)request.GetResponse(); using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
        { return reader.ReadToEnd();
        }
    }

复制代码

2.HttpClient 方式

Post:

复制代码

private async void button2_Click(object sender, EventArgs e)
{
HttpClient client = new HttpClient(); //由HttpClient发出Delete Method
HttpResponseMessage response = await client.DeleteAsync(“http://localhost:41558/api/Demo"+"/1“); if (response.IsSuccessStatusCode)
MessageBox.Show(“成功”);
} private async void button3_Click(object sender, EventArgs e)
{ //创建一个处理序列化的DataContractJsonSerializer
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(People));
MemoryStream ms = new MemoryStream(); //将资料写入MemoryStream
serializer.WriteObject(ms, new People() { Id = 1, Name = “Hello ni” }); //一定要在这设定Position
ms.Position = 0;
HttpContent content = new StreamContent(ms);//将MemoryStream转成HttpContent
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(“application/json”);
HttpClient client = new HttpClient(); //由HttpClient发出Put Method
HttpResponseMessage response = await client.PutAsync(“http://localhost:41558/api/Demo"\+ “/1”, content); if (response.IsSuccessStatusCode)
MessageBox.Show(“成功”);
}

复制代码

Get:

复制代码

using (WebClient client = new WebClient())
{
client.Headers[“Type”] = “GET”;
client.Headers[“Accept”] = “application/json”;
client.Encoding = Encoding.UTF8;
client.DownloadStringCompleted += (senderobj, es) => { var obj = es.Result;
};
client.DownloadStringAsync(“http://localhost:41558/api/Demo“);
}

复制代码

  Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

  Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。

  • Memcached是多线程,而Redis使用单线程。(个人认为Memcached在读写处理速度上由于Redis)
  • Memcached使用预分配的内存池的方式,Redis使用现场申请内存的方式来存储数据,并且可以配置虚拟内存。
  • Redis可以实现持久化(也就是说redis需要经常将内存中的数据同步到硬盘中来保证持久化),主从复制,实现故障恢复。
  • Memcached只是简单的key与value,但是Redis支持数据类型比较多。包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。

Redis支持两种持久化方式:

   (1):snapshotting(快照)也是默认方式.(把数据做一个备份,将数据存储到文件)

   (2)Append-only file(缩写aof)的方式 

   快照是默认的持久化方式,这种方式是将内存中数据以快照的方式写到二进制文件中,默认的文件名称为dump.rdb.可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key键修改就自动做快照.

   aof方式:由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。 

以cmd安装方法

1.下载安装包:https://github.com/dmajkic/redis/downloads

2.安装包下载后根据操作系统选择对应版本文件,里面会有几个dll分别为:

redis-server.exe:服务程序 
redis-check-dump.exe:本地数据库检查
redis-check-aof.exe:更新日志检查
redis-benchmark.exe:性能测试,用以模拟同时由N个客户端发送M个 SETs/GETs 查询.
redis-cli.exe: 服务端开启后,我们的客户端就可以输入各种命令测试了 

首先以管理员身份打开cmd (窗口+R),进入到安装包下载的位置。输入:redis-server.exe redis.conf  开启Redis服务。提示信息没有报错表示启动成功。

此窗口要保持开启状态,如果关闭Redis服务也会相即被关闭。使用客户端测试一下数据。

现在来观察Redis是怎么持久化存储数据到硬盘上。(快照是默认的持久化方式,默认的文件名称为dump.rdb)

可以看到Redis服务端在一段时间后将数据库保存在磁盘上,文件为:dump.rdb。

以weindows服务安装Redis方法:

下载Redis服务安装包:https://github.com/rgl/redis/downloads

下载完成后直接点击.exe下一步下一步OK。安装完后我们会在windows服务中找到Redis Service服务。注意启动服务后在进行相关测试。

在调用Redis服务前需要准备三个DLL。下载地址:【Redis调用驱动】在项目中引用即可。

使用Redis中存储常用的5种数据类型:String,Hash,List,SetSorted set编写实例代码

复制代码

static void Main(string[] args)
{
//在Redis中存储常用的5种数据类型:String,Hash,List,SetSorted set

RedisClient client = new RedisClient("172.21.0.192", 6379);
client.FlushAll();

#region string
client.Add<string>("StringValueTime", "我已设置过期时间噢30秒后会消失", DateTime.Now.AddMilliseconds(30000));
while (true)
{
    if (client.ContainsKey("StringValueTime"))
    {
        Console.WriteLine("String.键:StringValue,值:{0} {1}", client.Get<string>("StringValueTime"), DateTime.Now);
        Thread.Sleep(10000);
    }
    else
    {
        Console.WriteLine("键:StringValue,值:我已过期 {0}", DateTime.Now);
        break;
    }
}

client.Add<string>("StringValue", " String和Memcached操作方法差不多");
Console.WriteLine("数据类型为:String.键:StringValue,值:{0}", client.Get<string>("StringValue"));

Student stud = new Student() { id = "1001", name = "李四" };
client.Add<Student>("StringEntity", stud);
Student Get\_stud = client.Get<Student>("StringEntity");
Console.WriteLine("数据类型为:String.键:StringEntity,值:{0} {1}", Get\_stud.id, Get\_stud.name);
#endregion

#region Hash
client.SetEntryInHash("HashID", "Name", "张三");
client.SetEntryInHash("HashID", "Age", "24");
client.SetEntryInHash("HashID", "Sex", "男");
client.SetEntryInHash("HashID", "Address", "上海市XX号XX室");

List<string> HaskKey = client.GetHashKeys("HashID");
foreach (string key in HaskKey)
{
    Console.WriteLine("HashID--Key:{0}", key);
}

List<string> HaskValue = client.GetHashValues("HashID");
foreach (string value in HaskValue)
{
    Console.WriteLine("HashID--Value:{0}", value);
}

List<string> AllKey = client.GetAllKeys(); //获取所有的key。
foreach (string Key in AllKey)
{
    Console.WriteLine("AllKey--Key:{0}", Key);
}
#endregion

#region List
/\*
 \* list是一个链表结构,主要功能是push,pop,获取一个范围的所有的值等,操作中key理解为链表名字。 
 \* Redis的list类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素,
 \* 这样list既可以作为栈,又可以作为队列。Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,
 \* Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构 
 \*/
client.EnqueueItemOnList("QueueListId", "1.张三");  //入队
client.EnqueueItemOnList("QueueListId", "2.张四");
client.EnqueueItemOnList("QueueListId", "3.王五");
client.EnqueueItemOnList("QueueListId", "4.王麻子");
int q = client.GetListCount("QueueListId");
for (int i = 0; i < q; i++)
{
    Console.WriteLine("QueueListId出队值:{0}", client.DequeueItemFromList("QueueListId"));   //出队(队列先进先出)
}

client.PushItemToList("StackListId", "1.张三");  //入栈
client.PushItemToList("StackListId", "2.张四");
client.PushItemToList("StackListId", "3.王五");
client.PushItemToList("StackListId", "4.王麻子");
int p = client.GetListCount("StackListId");
for (int i = 0; i < p; i++)
{
    Console.WriteLine("StackListId出栈值:{0}", client.PopItemFromList("StackListId"));   //出栈(栈先进后出)
}


#endregion

#region Set无序集合
/\*
 它是string类型的无序集合。set是通过hash table实现的,添加,删除和查找,对集合我们可以取并集,交集,差集
 \*/
client.AddItemToSet("Set1001", "小A");
client.AddItemToSet("Set1001", "小B");
client.AddItemToSet("Set1001", "小C");
client.AddItemToSet("Set1001", "小D");
HashSet<string> hastsetA = client.GetAllItemsFromSet("Set1001");
foreach (string item in hastsetA)
{
    Console.WriteLine("Set无序集合ValueA:{0}", item); //出来的结果是无须的
}

client.AddItemToSet("Set1002", "小K");
client.AddItemToSet("Set1002", "小C");
client.AddItemToSet("Set1002", "小A");
client.AddItemToSet("Set1002", "小J");
HashSet<string> hastsetB = client.GetAllItemsFromSet("Set1002");
foreach (string item in hastsetB)
{
    Console.WriteLine("Set无序集合ValueB:{0}", item); //出来的结果是无须的
}

HashSet<string> hashUnion = client.GetUnionFromSets(new string\[\] { "Set1001", "Set1002" });
foreach (string item in hashUnion)
{
    Console.WriteLine("求Set1001和Set1002的并集:{0}", item); //并集
}

HashSet<string> hashG = client.GetIntersectFromSets(new string\[\] { "Set1001", "Set1002" });
foreach (string item in hashG)
{
    Console.WriteLine("求Set1001和Set1002的交集:{0}", item);  //交集
}

HashSet<string> hashD = client.GetDifferencesFromSet("Set1001", new string\[\] { "Set1002" });  //\[返回存在于第一个集合,但是不存在于其他集合的数据。差集\]
foreach (string item in hashD)
{
    Console.WriteLine("求Set1001和Set1002的差集:{0}", item);  //差集
}

#endregion

#region  SetSorted 有序集合
/\*
 sorted set 是set的一个升级版本,它在set的基础上增加了一个顺序的属性,这一属性在添加修改.元素的时候可以指定,
 \* 每次指定后,zset(表示有序集合)会自动重新按新的值调整顺序。可以理解为有列的表,一列存 value,一列存顺序。操作中key理解为zset的名字.
 \*/
client.AddItemToSortedSet("SetSorted1001", "1.刘仔");
client.AddItemToSortedSet("SetSorted1001", "2.星仔");
client.AddItemToSortedSet("SetSorted1001", "3.猪仔");
List<string> listSetSorted = client.GetAllItemsFromSortedSet("SetSorted1001");
foreach (string item in listSetSorted)
{
    Console.WriteLine("SetSorted有序集合{0}", item);
}
#endregion

}

复制代码

输出结果:

 

  Redis的存储容灾性比较完善,所支持的存储数据类型比较全。比较坑的是版本2.X之下都不支持服务器集群,只能单机。在Redis 3.0中服务器集群功能才亮相。 操作起来总体感觉比较简单容易上手

    为什么编写TaskSchedulerEx类?

    因为.NET默认线程池只有一个线程池,如果某个批量任务一直占着大量线程,甚至耗尽默认线程池,则会严重影响应用程序域中其它任务或批量任务的性能。

     特点:

    1、使用独立线程池,线程池中线程分为核心线程和辅助线程,辅助线程会动态增加和释放,且总线程数不大于参数_maxThreadCount

    2、无缝兼容Task,使用上和Task一样,可以用它来实现异步,参见:C# async await 异步执行方法封装 替代 BackgroundWorker

    3、队列中尚未执行的任务可以取消

    4、通过扩展类TaskHelper实现任务分组

    5、和SmartThreadPool对比,优点是无缝兼容Task类,和Task类使用没有区别,因为它本身就是对Task、TaskScheduler的扩展,所以Task类的ContinueWith、WaitAll等方法它都支持,以及兼容async、await异步编程

    6、代码量相当精简,TaskSchedulerEx类只有260多行代码

    7、池中的线程数量会根据负载自动增减,支持,但没有SmartThreadPool智能,为了性能,使用了比较笨的方式实现,不知道大家有没有既智能,性能又高的方案,我有一个思路,在定时器中计算每个任务执行平均耗时,然后使用公式(线程数 = CPU核心数 * ( 本地计算时间 + 等待时间 ) / 本地计算时间)来计算最佳线程数,然后按最佳线程数来动态创建线程,但这个计算过程可能会牺牲性能

     对比SmartThreadPool:

    TaskSchedulerEx类代码(使用Semaphore实现):

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils
{ ///


/// TaskScheduler扩展 /// 每个实例都是独立线程池 ///

public class TaskSchedulerEx : TaskScheduler, IDisposable
{ #region 外部方法 [DllImport(“kernel32.dll”, EntryPoint = “SetProcessWorkingSetSize”)] public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); #endregion

    #region 变量属性事件
    private ConcurrentQueue<Task> \_tasks = new ConcurrentQueue<Task>(); private int \_coreThreadCount = 0; private int \_maxThreadCount = 0; private int \_auxiliaryThreadTimeOut = 20000; //辅助线程释放时间
    private int \_activeThreadCount = 0; private System.Timers.Timer \_timer; private object \_lockCreateTimer = new object(); private bool \_run = true; private Semaphore \_sem = null; private int \_semMaxCount = int.MaxValue; //可以同时授予的信号量的最大请求数
    private int \_semCount = 0; //可用信号量请求数
    private int \_runCount = 0; //正在执行的和等待执行的任务数量

    /// <summary>
    /// 活跃线程数 /// </summary>
    public int ActiveThreadCount
    { get { return \_activeThreadCount; }
    } /// <summary>
    /// 核心线程数 /// </summary>
    public int CoreThreadCount
    { get { return \_coreThreadCount; }
    } /// <summary>
    /// 最大线程数 /// </summary>
    public int MaxThreadCount
    { get { return \_maxThreadCount; }
    } #endregion

    #region 构造函数
    /// <summary>
    /// TaskScheduler扩展 /// 每个实例都是独立线程池 /// </summary>
    /// <param name="coreThreadCount">核心线程数(大于或等于0,不宜过大)(如果是一次性使用,则设置为0比较合适)</param>
    /// <param name="maxThreadCount">最大线程数</param>
    public TaskSchedulerEx(int coreThreadCount = 10, int maxThreadCount = 20)
    {
        \_sem \= new Semaphore(0, \_semMaxCount);
        \_maxThreadCount \= maxThreadCount;
        CreateCoreThreads(coreThreadCount);
    } #endregion

    #region override GetScheduledTasks
    protected override IEnumerable<Task> GetScheduledTasks()
    { return \_tasks;
    } #endregion

    #region override TryExecuteTaskInline
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    { return false;
    } #endregion

    #region override QueueTask
    protected override void QueueTask(Task task)
    {
        \_tasks.Enqueue(task); while (\_semCount >= \_semMaxCount) //信号量已满,等待

{
Thread.Sleep(1);
}

        \_sem.Release();
        Interlocked.Increment(ref \_semCount);

        Interlocked.Increment(ref \_runCount); if (\_activeThreadCount < \_maxThreadCount && \_activeThreadCount < \_runCount)
        {
            CreateThread();
        }
    } #endregion

    #region 资源释放
    /// <summary>
    /// 资源释放 /// 队列中尚未执行的任务不再执行 /// </summary>
    public void Dispose()
    {
        \_run \= false; if (\_timer != null)
        {
            \_timer.Stop();
            \_timer.Dispose();
            \_timer \= null;
        } while (\_activeThreadCount > 0)
        {
            \_sem.Release();
            Interlocked.Increment(ref \_semCount);
        }
    } #endregion

    #region 创建核心线程池
    /// <summary>
    /// 创建核心线程池 /// </summary>
    private void CreateCoreThreads(int? coreThreadCount = null)
    { if (coreThreadCount != null) \_coreThreadCount = coreThreadCount.Value; for (int i = 0; i < \_coreThreadCount; i++)
        {
            Interlocked.Increment(ref \_activeThreadCount);
            Thread thread \= null;
            thread \= new Thread(new ThreadStart(() => {
                Task task; while (\_run)
                { if (\_tasks.TryDequeue(out task))
                    {
                        TryExecuteTask(task);
                        Interlocked.Decrement(ref \_runCount);
                    } else {
                        \_sem.WaitOne();
                        Interlocked.Decrement(ref \_semCount);
                    }
                }
                Interlocked.Decrement(ref \_activeThreadCount); if (\_activeThreadCount == 0)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                    {
                        SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, \-1, -1);
                    }
                }
            }));
            thread.IsBackground \= true;
            thread.Start();
        }
    } #endregion

    #region 创建辅助线程
    /// <summary>
    /// 创建辅助线程 /// </summary>
    private void CreateThread()
    {
        Interlocked.Increment(ref \_activeThreadCount);
        Thread thread \= null;
        thread \= new Thread(new ThreadStart(() => {
            Task task;
            DateTime dt \= DateTime.Now; while (\_run && DateTime.Now.Subtract(dt).TotalMilliseconds < \_auxiliaryThreadTimeOut)
            { if (\_tasks.TryDequeue(out task))
                {
                    TryExecuteTask(task);
                    Interlocked.Decrement(ref \_runCount);
                    dt \= DateTime.Now;
                } else {
                    \_sem.WaitOne(\_auxiliaryThreadTimeOut);
                    Interlocked.Decrement(ref \_semCount);
                }
            }
            Interlocked.Decrement(ref \_activeThreadCount); if (\_activeThreadCount == \_coreThreadCount)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                {
                    SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, \-1, -1);
                }
            }
        }));
        thread.IsBackground \= true;
        thread.Start();
    } #endregion

    #region 全部取消
    /// <summary>
    /// 全部取消 /// 取消队列中尚未执行的任务 /// </summary>
    public void CancelAll()
    {
        Task tempTask; while (\_tasks.TryDequeue(out tempTask))
        {
            Interlocked.Decrement(ref \_runCount);
        }
    } #endregion }

}

View Code

    TaskSchedulerEx类代码(使用AutoResetEvent实现):

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils
{ ///


/// TaskScheduler扩展 /// 每个实例都是独立线程池 ///

public class TaskSchedulerEx : TaskScheduler, IDisposable
{ #region 外部方法 [DllImport(“kernel32.dll”, EntryPoint = “SetProcessWorkingSetSize”)] public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); #endregion

    #region 变量属性事件
    private ConcurrentQueue<Task> \_tasks = new ConcurrentQueue<Task>(); private int \_coreThreadCount = 0; private int \_maxThreadCount = 0; private int \_auxiliaryThreadTimeOut = 20000; //辅助线程释放时间
    private int \_activeThreadCount = 0; private System.Timers.Timer \_timer; private object \_lockCreateTimer = new object(); private bool \_run = true; private AutoResetEvent \_evt = new AutoResetEvent(false); /// <summary>
    /// 活跃线程数 /// </summary>
    public int ActiveThreadCount
    { get { return \_activeThreadCount; }
    } /// <summary>
    /// 核心线程数 /// </summary>
    public int CoreThreadCount
    { get { return \_coreThreadCount; }
    } /// <summary>
    /// 最大线程数 /// </summary>
    public int MaxThreadCount
    { get { return \_maxThreadCount; }
    } #endregion

    #region 构造函数
    /// <summary>
    /// TaskScheduler扩展 /// 每个实例都是独立线程池 /// </summary>
    /// <param name="coreThreadCount">核心线程数(大于或等于0,不宜过大)(如果是一次性使用,则设置为0比较合适)</param>
    /// <param name="maxThreadCount">最大线程数</param>
    public TaskSchedulerEx(int coreThreadCount = 10, int maxThreadCount = 20)
    {
        \_maxThreadCount \= maxThreadCount;
        CreateCoreThreads(coreThreadCount);
    } #endregion

    #region override GetScheduledTasks
    protected override IEnumerable<Task> GetScheduledTasks()
    { return \_tasks;
    } #endregion

    #region override TryExecuteTaskInline
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    { return false;
    } #endregion

    #region override QueueTask
    protected override void QueueTask(Task task)
    {
        CreateTimer();
        \_tasks.Enqueue(task);
        \_evt.Set();
    } #endregion

    #region 资源释放
    /// <summary>
    /// 资源释放 /// 队列中尚未执行的任务不再执行 /// </summary>
    public void Dispose()
    {
        \_run \= false; if (\_timer != null)
        {
            \_timer.Stop();
            \_timer.Dispose();
            \_timer \= null;
        } while (\_activeThreadCount > 0)
        {
            \_evt.Set();
        }
    } #endregion

    #region 创建核心线程池
    /// <summary>
    /// 创建核心线程池 /// </summary>
    private void CreateCoreThreads(int? coreThreadCount = null)
    { if (coreThreadCount != null) \_coreThreadCount = coreThreadCount.Value; for (int i = 0; i < \_coreThreadCount; i++)
        {
            Interlocked.Increment(ref \_activeThreadCount);
            Thread thread \= null;
            thread \= new Thread(new ThreadStart(() => {
                Task task; while (\_run)
                { if (\_tasks.TryDequeue(out task))
                    {
                        TryExecuteTask(task);
                    } else {
                        \_evt.WaitOne();
                    }
                }
                Interlocked.Decrement(ref \_activeThreadCount); if (\_activeThreadCount == 0)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                    {
                        SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, \-1, -1);
                    }
                }
            }));
            thread.IsBackground \= true;
            thread.Start();
        }
    } #endregion

    #region 创建辅助线程
    /// <summary>
    /// 创建辅助线程 /// </summary>
    private void CreateThread()
    {
        Interlocked.Increment(ref \_activeThreadCount);
        Thread thread \= null;
        thread \= new Thread(new ThreadStart(() => {
            Task task;
            DateTime dt \= DateTime.Now; while (\_run && DateTime.Now.Subtract(dt).TotalMilliseconds < \_auxiliaryThreadTimeOut)
            { if (\_tasks.TryDequeue(out task))
                {
                    TryExecuteTask(task);
                    dt \= DateTime.Now;
                } else {
                    \_evt.WaitOne(\_auxiliaryThreadTimeOut);
                }
            }
            Interlocked.Decrement(ref \_activeThreadCount); if (\_activeThreadCount == \_coreThreadCount)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                {
                    SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, \-1, -1);
                }
            }
        }));
        thread.IsBackground \= true;
        thread.Start();
    } #endregion

    #region 创建定时器
    private void CreateTimer()
    { if (\_timer == null) //\_timer不为空时,跳过,不走lock,提升性能

{ if (_activeThreadCount >= _coreThreadCount && _activeThreadCount < _maxThreadCount) //活跃线程数达到最大线程数时,跳过,不走lock,提升性能
{ lock (_lockCreateTimer)
{ if (_timer == null)
{
_timer = new System.Timers.Timer();
_timer.Interval = _coreThreadCount == 0 ? 1 : 500;
_timer.Elapsed += (s, e) => { if (_activeThreadCount >= _coreThreadCount && _activeThreadCount < _maxThreadCount)
{ if (_tasks.Count > 0)
{ if (_timer.Interval != 20) _timer.Interval = 20;
CreateThread();
} else { if (_timer.Interval != 500) _timer.Interval = 500;
}
} else { if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
}
};
_timer.Start();
}
}
}
}
} #endregion

    #region 全部取消
    /// <summary>
    /// 全部取消 /// 取消队列中尚未执行的任务 /// </summary>
    public void CancelAll()
    {
        Task tempTask; while (\_tasks.TryDequeue(out tempTask)) { }
    } #endregion }

}

View Code

    RunHelper类代码:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils
{ ///


/// 线程工具类 ///

public static class RunHelper
{ #region 变量属性事件

    #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static Task Run(this TaskScheduler scheduler, Action<object\> doWork, object arg = null, Action<Exception> errorAction = null)
    { return Task.Factory.StartNew((obj) => { try {
                doWork(obj);
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误");
            }
        }, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static Task Run(this TaskScheduler scheduler, Action doWork, Action<Exception> errorAction = null)
    { return Task.Factory.StartNew(() => { try {
                doWork();
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误");
            }
        }, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static Task<T> Run<T>(this TaskScheduler scheduler, Func<object, T> doWork, object arg = null, Action<Exception> errorAction = null)
    { return Task.Factory.StartNew<T>((obj) => { try { return doWork(obj);
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误"); return default(T);
            }
        }, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static Task<T> Run<T>(this TaskScheduler scheduler, Func<T> doWork, Action<Exception> errorAction = null)
    { return Task.Factory.StartNew<T>(() => { try { return doWork();
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误"); return default(T);
            }
        }, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static async Task<T> RunAsync<T>(this TaskScheduler scheduler, Func<object, T> doWork, object arg = null, Action<Exception> errorAction = null)
    { return await Task.Factory.StartNew<T>((obj) => { try { return doWork(obj);
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误"); return default(T);
            }
        }, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion

    #region 线程中执行
    /// <summary>
    /// 线程中执行 /// </summary>
    public static async Task<T> RunAsync<T>(this TaskScheduler scheduler, Func<T> doWork, Action<Exception> errorAction = null)
    { return await Task.Factory.StartNew<T>(() => { try { return doWork();
            } catch (Exception ex)
            { if (errorAction != null) errorAction(ex);
                LogUtil.Error(ex, "ThreadUtil.Run错误"); return default(T);
            }
        }, CancellationToken.None, TaskCreationOptions.None, scheduler);
    } #endregion }

}

View Code

    TaskHelper扩展类:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils
{ ///


/// Task帮助类基类 ///

public class TaskHelper
{ #region 变量
///
/// 处理器数 ///

private static int _processorCount = Environment.ProcessorCount; #endregion

    #region UI任务
    private static TaskScheduler \_UITask; /// <summary>
    /// UI任务(2-4个线程) /// </summary>
    public static TaskScheduler UITask
    { get { if (\_UITask == null) \_UITask = new TaskSchedulerEx(2, 4); return \_UITask;
        }
    } #endregion

    #region 菜单任务
    private static TaskScheduler \_MenuTask; /// <summary>
    /// 菜单任务(2-4个线程) /// </summary>
    public static TaskScheduler MenuTask
    { get { if (\_MenuTask == null) \_MenuTask = new TaskSchedulerEx(2, 4); return \_MenuTask;
        }
    } #endregion

    #region 计算任务
    private static TaskScheduler \_CalcTask; /// <summary>
    /// 计算任务(线程数:处理器数\*2) /// </summary>
    public static TaskScheduler CalcTask
    { get { if (\_CalcTask == null) \_CalcTask = new LimitedTaskScheduler(\_processorCount \* 2); return \_CalcTask;
        }
    } #endregion

    #region 网络请求
    private static TaskScheduler \_RequestTask; /// <summary>
    /// 网络请求(8-32个线程) /// </summary>
    public static TaskScheduler RequestTask
    { get { if (\_RequestTask == null) \_RequestTask = new TaskSchedulerEx(8, 32); return \_RequestTask;
        }
    } #endregion

    #region 数据库任务
    private static TaskScheduler \_DBTask; /// <summary>
    /// 数据库任务(8-32个线程) /// </summary>
    public static TaskScheduler DBTask
    { get { if (\_DBTask == null) \_DBTask = new TaskSchedulerEx(8, 32); return \_DBTask;
        }
    } #endregion

    #region IO任务
    private static TaskScheduler \_IOTask; /// <summary>
    /// IO任务(8-32个线程) /// </summary>
    public static TaskScheduler IOTask
    { get { if (\_IOTask == null) \_IOTask = new TaskSchedulerEx(8, 32); return \_IOTask;
        }
    } #endregion

    #region 首页任务
    private static TaskScheduler \_MainPageTask; /// <summary>
    /// 首页任务(8-32个线程) /// </summary>
    public static TaskScheduler MainPageTask
    { get { if (\_MainPageTask == null) \_MainPageTask = new TaskSchedulerEx(8, 32); return \_MainPageTask;
        }
    } #endregion

    #region 图片加载任务
    private static TaskScheduler \_LoadImageTask; /// <summary>
    /// 图片加载任务(8-32个线程) /// </summary>
    public static TaskScheduler LoadImageTask
    { get { if (\_LoadImageTask == null) \_LoadImageTask = new TaskSchedulerEx(8, 32); return \_LoadImageTask;
        }
    } #endregion

    #region 浏览器任务
    private static TaskScheduler \_BrowserTask; /// <summary>
    /// 浏览器任务(2-4个线程) /// </summary>
    public static TaskScheduler BrowserTask
    { get { if (\_BrowserTask == null) \_BrowserTask = new TaskSchedulerEx(2, 4); return \_BrowserTask;
        }
    } #endregion }

}

View Code

    Form1.cs测试代码:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Management; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Utils; namespace test
{ public partial class Form1 : Form
{ private TaskSchedulerEx _taskSchedulerEx = null; private TaskSchedulerEx _taskSchedulerExSmall = null; private TaskSchedulerEx _task = null; public Form1()
{
InitializeComponent();
_taskSchedulerEx = new TaskSchedulerEx(50, 500);
_taskSchedulerExSmall = new TaskSchedulerEx(5, 50);
_task = new TaskSchedulerEx(2, 10);
} private void Form1_Load(object sender, EventArgs e)
{

    } /// <summary>
    /// 模拟大量网络请求任务 /// </summary>
    private void button1\_Click(object sender, EventArgs e)
    {
        DoTask(\_taskSchedulerEx, 200000, 1000, 20);
    } /// <summary>
    /// 模拟CPU密集型任务 /// </summary>
    private void button2\_Click(object sender, EventArgs e)
    {
        DoTask(\_taskSchedulerEx, 100000, 2000, 1);
    } /// <summary>
    /// 模拟大量网络请求任务 /// </summary>
    private void button3\_Click(object sender, EventArgs e)
    {
        DoTask(\_taskSchedulerExSmall, 2000, 100, 20);
    } /// <summary>
    /// 模拟CPU密集型任务 /// </summary>
    private void button4\_Click(object sender, EventArgs e)
    {
        DoTask(\_taskSchedulerExSmall, 2000, 100, 1);
    } /// <summary>
    /// 模拟任务 /// </summary>
    /// <param name="scheduler">scheduler</param>
    /// <param name="taskCount">任务数量</param>
    /// <param name="logCount">每隔多少条数据打一个日志</param>
    /// <param name="delay">模拟延迟或耗时(毫秒)</param>
    private void DoTask(TaskSchedulerEx scheduler, int taskCount, int logCount, int delay)
    {
        \_task.Run(() \=> {
            Log("开始");
            DateTime dt \= DateTime.Now;
            List<Task> taskList = new List<Task>(); for (int i = 1; i <= taskCount; i++)
            {
                Task task \= scheduler.Run((obj) => { var k = (int)obj;
                    Thread.Sleep(delay); //模拟延迟或耗时
                    if (k % logCount == 0)
                    {
                        Log("最大线程数:" + scheduler.MaxThreadCount + " 核心线程数:" + scheduler.CoreThreadCount + " 活跃线程数:" + scheduler.ActiveThreadCount.ToString().PadLeft(4, ' ') + " 处理数/总数:" + k + " / " + taskCount);
                    }
                }, i, (ex) \=> {
                    Log(ex.Message);
                });
                taskList.Add(task);
            }
            Task.WaitAll(taskList.ToArray()); double d = DateTime.Now.Subtract(dt).TotalSeconds;
            Log("完成,耗时:" + d + "秒");
        });
    } private void Form1\_FormClosed(object sender, FormClosedEventArgs e)
    { if (\_taskSchedulerEx != null)
        {
            \_taskSchedulerEx.Dispose(); //释放资源
            \_taskSchedulerEx = null;
        }
    }
}

}

View Code

     测试截图:

一、C# vs SQLite:

C#

SQLite

字段名

类型

库类型

GetFieldType(#)

转换

备注

F_BOOL

bool

BIT NOT NULL

Boolean

 

 

F_BOOL_NULL

bool?

BIT

Boolean

 

 

F_SBYTE

sbyte

INT8 NOT NULL

SByte

sbyte_

 

F_SBYTE_NULL

sbyte?

INT8

SByte

sbyte_

 

F_BYTE

byte

UINT8 NOT NULL

Byte

 

 

F_BYTE_NULL

byte?

UINT8

Byte

 

 

F_SHORT

short

INT16 NOT NULL

Int16

 

 

F_SHORT_NULL

short?

INT16

Int16

 

 

F_USHORT

ushort

UINT16 NOT NULL

UInt16

ushort_

 

F_USHORT_NULL

ushort?

UINT16

UInt16

ushort_

 

F_INT

int

INT32 NOT NULL

Int32

 

 

F_INT_NULL

int?

INT32

Int32

 

 

F_UINT

uint

UINT32 NOT NULL

UInt32

uint_

 

F_UINT_NULL

uint?

UINT32

UInt32

uint_

 

F_LONG

long

INT64 NOT NULL

Int64

 

 

F_LONG_NULL

long?

INT64

Int64

 

 

F_ULONG

ulong

UINT64 NOT NULL

UInt64

ulong_

 

F_ULONG_NULL

ulong?

UINT64

UInt64

ulong_

 

F_FLOAT

float

FLOAT NOT NULL

Double

 

不转兼容

F_FLOAT_NULL

float?

FLOAT

Double

 

不转兼容

F_DOUBLE

double

DOUBLE NOT NULL

Double

 

 

F_DOUBLE_NULL

double?

DOUBLE

Double

 

 

F_DECIMAL

decimal

DECIMAL NOT NULL

Decimal

 

存取不一:<F_DECIMAL>: [-79228162514264300000000000000] != [-79228162514264337593543950335]

F_DECIMAL_NULL

decimal?

DECIMAL

Decimal

 

存取不一:<F_DECIMAL>: [-79228162514264300000000000000] != [-79228162514264337593543950335]

F_CHAR

char

TEXT NOT NULL

String

 

 

F_CHAR_NULL

char?

TEXT

String

 

 

F_STRING

string

TEXT NOT NULL

String

 

 

F_STRING_NULL

string

TEXT

String

 

 

F_DATETIME

DateTime

TEXT NOT NULL

String

 

 

F_DATETIME_NULL

DateTime?

TEXT

String

 

 

 

 

 

 

 

 

二、 C# vs MySQL:

C#

MySQL

字段名

类型

库类型

GetFieldType(#)

转换

备注

F_BOOL

bool

BOOL NOT NULL

Boolean

 

 

F_BOOL_NULL

bool?

BOOL NULL

Boolean

 

 

F_SBYTE

sbyte

TINYINT NOT NULL

SByte

sbyte_

 

F_SBYTE_NULL

sbyte?

TINYINT NULL

SByte

sbyte_

 

F_BYTE

byte

TINYINT UNSIGNED NOT NULL

Byte

 

 

F_BYTE_NULL

byte?

TINYINT UNSIGNED NULL

Byte

 

 

F_SHORT

short

SMALLINT NOT NULL

Int16

 

 

F_SHORT_NULL

short?

SMALLINT NULL

Int16

 

 

F_USHORT

ushort

SMALLINT UNSIGNED NOT NULL

UInt16

ushort_

 

F_USHORT_NULL

ushort?

SMALLINT UNSIGNED NULL

UInt16

ushort_

 

F_INT

int

INT NOT NULL

Int32

 

 

F_INT_NULL

int?

INT NULL

Int32

 

 

F_UINT

uint

INT UNSIGNED NOT NULL

UInt32

uint_

 

F_UINT_NULL

uint?

INT UNSIGNED NULL

UInt32

uint_

 

F_LONG

long

BIGINT NOT NULL

Int64

 

 

F_LONG_NULL

long?

BIGINT NULL

Int64

 

 

F_ULONG

ulong

BIGINT UNSIGNED NOT NULL

UInt64

ulong_

 

F_ULONG_NULL

ulong?

BIGINT UNSIGNED NULL

UInt64

ulong_

 

F_FLOAT

float

FLOAT NOT NULL

Single

 

极值溢出

F_FLOAT_NULL

float?

FLOAT NULL

Single

 

极值溢出

F_DOUBLE

double

DOUBLE NOT NULL

Double

 

 

F_DOUBLE_NULL

double?

DOUBLE NULL

Double

 

 

F_DECIMAL

decimal

DECIMAL NOT NULL

Decimal

 

极值溢出

F_DECIMAL_NULL

decimal?

DECIMAL NULL

Decimal

 

极值溢出

F_CHAR

char

CHARACTER NOT NULL

String

 

中文报错

F_CHAR_NULL

char?

CHARACTER NULL

String

 

中文报错

F_STRING

string

VARCHAR(50) NOT NULL

String

 

 

F_STRING_NULL

string

VARCHAR(50) NULL

String

 

 

F_DATETIME

DateTime

DATETIME NOT NULL

DateTime

 

 

F_DATETIME_NULL

DateTime?

DATETIME NULL

DateTime

 

 

 

 

 

 

 

 

三、 C# vs MSSQL:

C#

MSSQL

字段名

类型

库类型

GetFieldType(#)

转换

备注

F_BOOL

bool

bit NOT NULL

Boolean

 

 

F_BOOL_NULL

bool?

bit NULL

Boolean

 

 

F_SBYTE

sbyte

smallint NOT NULL

Int16

sbyte_short

 

F_SBYTE_NULL

sbyte?

smallint NULL

Int16

sbyte_short

 

F_BYTE

byte

tinyint NOT NUL;

Byte

 

 

F_BYTE_NULL

byte?

tinyint NULL

Byte

 

 

F_SHORT

short

smallint NOT NULL

Int16

 

 

F_SHORT_NULL

short?

smallint NULL

Int16

 

 

F_USHORT

ushort

int NOT NULL

Int32

ushort_int

 

F_USHORT_NULL

ushort?

int NULL

Int32

ushort_int

 

F_INT

int

int NOT NULL

Int32

 

 

F_INT_NULL

int?

int NULL

Int32

 

 

F_UINT

uint

bigint NOT NULL

Int64

uint_long

 

F_UINT_NULL

uint?

bigint NULL

Int64

uint_long

 

F_LONG

long

bigint NOT NULL

Int64

 

 

F_LONG_NULL

long?

bigint NULL

Int64

 

 

F_ULONG

ulong

real NOT NULL

Single

ulong_float

 

F_ULONG_NULL

ulong?

real NULL

Single

ulong_float

 

F_FLOAT

float

real NOT NULL

Single

 

 

F_FLOAT_NULL

float?

real NULL

Single

 

 

F_DOUBLE

double

float NOT NULL

Double

 

 

F_DOUBLE_NULL

double?

float NULL

Double

 

 

F_DECIMAL

decimal

decimal NOT NULL

Decimal

 

极值溢出

F_DECIMAL_NULL

decimal?

decimal NULL

Decimal

 

极值溢出

F_CHAR

char

char(1) NOT NULL

String

 

 

F_CHAR_NULL

char?

char(1) NULL

String

 

 

F_STRING

string

varchar(50) NOT NULL

F_STRING:

 

 

F_STRING_NULL

string

varchar(50) NULL

String

 

 

F_DATETIME

DateTime

datetime NOT NULL

DateTime

 

必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间。

F_DATETIME_NULL

DateTime?

datetime NULL

DateTime

 

 

 

 

 

 

 

 

四、C# vs Oracle:

C#

Oracle

字段名

类型

库类型

GetFieldType(#)

转换

备注

F_BOOL

bool

CHAR(1) NOT NULL

String

bool_string

 

F_BOOL_NULL

bool?

CHAR(1)

String

bool_string

 

F_SBYTE

sbyte

NUMBER(3) NOT NULL

Int16

sbyte_short

 

F_SBYTE_NULL

sbyte?

NUMBER(3)

Int16

sbyte_short

 

F_BYTE

byte

NUMBER(3) NOT NULL

Int16

byte_short

 

F_BYTE_NULL

byte?

NUMBER(3)

Int16

byte_short

 

F_SHORT

short

NUMBER(5) NOT NULL

Int32

short_int

 

F_SHORT_NULL

short?

NUMBER(5)

Int32

short_int

 

F_USHORT

ushort

NUMBER(5) NOT NULL

Int32

ushort_int

 

F_USHORT_NULL

ushort?

NUMBER(5)

Int32

ushort_int

 

F_INT

int

NUMBER(10) NOT NULL

Int64

int_long

 

F_INT_NULL

int?

NUMBER(10)

Int64

int_long

 

F_UINT

uint

NUMBER(10) NOT NULL

Int64

uint_long

 

F_UINT_NULL

uint?

NUMBER(10)

Int64

uint_long

 

F_LONG

long

NUMBER(19) NOT NULL

Decimal

long_decimal

 

F_LONG_NULL

long?

NUMBER(19)

Decimal

long_decimal

 

F_ULONG

ulong

NUMBER(19) NOT NULL

Decimal

ulong_decimal

 

F_ULONG_NULL

ulong?

NUMBER(19)

Decimal

ulong_decimal

 

F_FLOAT

float

BINARY_FLOAT NOT NULL

Single

 

 

F_FLOAT_NULL

float?

BINARY_FLOAT

Single

 

 

F_DOUBLE

double

BINARY_DOUBLE NOT NULL

Double

 

极值溢出

F_DOUBLE_NULL

double?

BINARY_DOUBLE

Double

 

极值溢出

F_DECIMAL

decimal

DECIMAL(33,3) NOT NULL

Decimal

 

 

F_DECIMAL_NULL

decimal?

DECIMAL(33,3)

Decimal

 

 

F_CHAR

char

CHAR(1) NOT NULL

String

 

 

F_CHAR_NULL

char?

CHAR(1)

String

 

 

F_STRING

string

VARCHAR(50) NOT NULL

String

 

不允许空字符

F_STRING_NULL

string

VARCHAR(50)

String

 

不允许空字符

F_DATETIME

DateTime

TIMESTAMP NOT NULL

DateTime

 

大值读取报错

F_DATETIME_NULL

DateTime?

TIMESTAMP

DateTime

 

大值读取报错

 

 

 

 

 

 

附、类型转换:

using System; using System.Collections.Generic; using System.Text; namespace vJine.Core.ORM { public class TypeConverter { public class sbyte_ /*: IConverter*/ { public static sbyte CONV_Q(object V) { return (sbyte)V;
}
} public class ushort_ /*: IConverter*/ { public static ushort CONV_Q(object V) { return (ushort)V;
}
} public class uint_ /*: IConverter*/ { public static uint CONV_Q(object V) { return (uint)V;
}
} public class ulong_ /*: IConverter*/ { public static ulong CONV_Q(object V) { return (ulong)V;
}
} public class bool_string /*: IConverter*/ { public static object CONV_I(object V) { if ((bool)V == true) { return “1”;
} else { return “0”;
}
} public static bool CONV_Q(object V) { if ((string)V == “1”) { return true;
} else { return false;
}
}
} public class sbyte_short /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToInt16(V);
} public static sbyte CONV_Q(object V) { return Convert.ToSByte(V);
}
} public class byte_short /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToInt16(V);
} public static byte CONV_Q(object V) { return Convert.ToByte(V);
}
} public class short_int /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToInt32(V);
} public static short CONV_Q(object V) { return Convert.ToInt16(V);
}
} public class ushort_int /*: IConverter*/ { public static object CONV_I(object V, string Name) { return Convert.ToInt32(V);
} public static ushort CONV_Q(object V, string Name) { return Convert.ToUInt16(V);
}
} public class int_long /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToInt64(V);
} public static int CONV_Q(object V) { return Convert.ToInt32(V);
}
} public class uint_long /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToInt64(V);
} public static uint CONV_Q(object V) { return Convert.ToUInt32(V);
}
} public class long_decimal /*: IConverter*/ { public static object CONV_I(object V) { return decimal.Parse(V.ToString());
} public static long CONV_Q(object V) { return long.Parse(V.ToString());
}
} public class ulong_float /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToSingle(V);
} public static ulong CONV_Q(object V) { return Convert.ToUInt64(V);
}
} public class ulong_decimal /*: IConverter*/ { public static object CONV_I(object V) { return decimal.Parse(V.ToString());
} public static ulong CONV_Q(object V) { return ulong.Parse(V.ToString());
}
} public class float_double /*: IConverter*/ { public static object CONV_I(object V) { return Convert.ToDouble(V);
} public static float CONV_Q(object V) { return Convert.ToSingle(V);
}
} public class char_string /*: IConverter*/ { public static object CONV_I(object V) { if (V is Char) { return V;
} else if (V is string) { string chars = (string)V; if (chars == “”) { return ‘\0’;
} else { return chars[0];
}
} else { throw new OrmException(“Fail To Convert String[{0}] To Char”, V);
}
} public static char CONV_Q(object V) { return ((string)V)[0];
}
} public class DateTime_ /*: IConverter*/ { public static object CONV_I(object V) { return V;
} public static DateTime CONV_Q(object V) { return (DateTime)V;
}
}
}
}

View Code

C# 时间戳与DateTime互转 - 晓晨Master - 博客园

Excerpt

#region 转换时间为unix时间戳 /// /// 转换时间为unix时间戳 /// /// 需要传递UTC时间,避免时区误差,例:DataTime.UTCNow /// public static double ConvertToUnixOfTime(DateTime da…


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<span>#region</span> 转换时间为unix时间戳
<span>///</span> <span>&lt;summary&gt;</span>
<span>///</span><span> 转换时间为unix时间戳
</span><span>///</span> <span>&lt;/summary&gt;</span>
<span>///</span> <span>&lt;param name="date"&gt;</span><span>需要传递UTC时间,避免时区误差,例:DataTime.UTCNow</span><span>&lt;/param&gt;</span>
<span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span>public</span> <span>static</span> <span>double</span><span> ConvertToUnixOfTime(DateTime date)
{
DateTime origin </span>= <span>new</span> DateTime(<span>1970</span>, <span>1</span>, <span>1</span>, <span>0</span>, <span>0</span>, <span>0</span>, <span>0</span><span>);
TimeSpan diff </span>= date -<span> origin;
</span><span>return</span><span> Math.Floor(diff.TotalSeconds);
}
</span><span>#endregion</span>

<span>#region</span> 时间戳转换为时间

<span>public</span> <span>static</span> DateTime StampToDateTime(<span>string</span><span> timeStamp)
{
DateTime dateTimeStart </span>= TimeZone.CurrentTimeZone.ToLocalTime(<span>new</span> DateTime(<span>1970</span>, <span>1</span>, <span>1</span><span>));
</span><span>long</span> lTime = <span>long</span>.Parse(timeStamp + <span>"</span><span>0000000</span><span>"</span><span>);
TimeSpan toNow </span>= <span>new</span><span> TimeSpan(lTime);
</span><span>return</span><span> dateTimeStart.Add(toNow);
}

</span><span>#endregion</span>


 本文将使用一个NuGet公开的组件来实现一些特殊的控件显示,方便大家进行快速的开发系统。

 联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:

1

Install-Package HslCommunication

NuGet安装教程  http://www.cnblogs.com/dathlin/p/7705014.html


组件中包含了一些控件,主要是针对winform机制的,由于WPF的机制完全不一致,所以此处咱提供WPF的版本,只提供了winfrom版本的控件,开发这些控件源自于我之前的项目累积,趁着空闲的间隙重新整理开发完成。

  • 首先便是对winfrom自身按钮的不满,实在是太过于丑了,而且容易被破解(假如你的按钮做了权限验证,权限不足的禁用,实际上这种操作是很危险的,用户完全可以从网上下载一个灰色按钮精灵来破解程序,将原先禁用的隐藏的按钮变得可以点击),所有就重新开发一个更加完善的按钮,而且也不那么丑了。
  • 时间显示的控件就是纯粹为了好玩开发的,想做个更有意思的控件,顺便练练手,技能。
  • 纵向的竖立着的进度条,由于微软没有提供竖立的进度条,而当你需要这种时就很麻烦,一般横向的进度条用微软的即可,但是本组件的进度条也提供了横着的选项。
  • 饼图控件,为了方便的显示一些数据的占用比,而开发的。
  • 仪表盘控件,显示一些仪表的数据,使用仪表显示更加的人性化。
  • 信号灯控件,显示简单的信号灯状态。
  • 曲线控件,显示曲线的情况,多曲线对比,实时数据显示。

要想使用组件的控件,除了使用NuGet来安装组件外,还需要将组件的dll文件(在你的项目的packages里面可以找到,如果你本来就是引用本地的,就直接拖拽本地的即可)拖拽到工具栏:

拖拽完成后效果如下:

ok,现在可以将控件拖到界面上了,接下来就分别讲解几个控件:


直接拖到主界面效果如下:

公开的属性如下:

基本的属性都是有注释的,要想设置显示的文本,设置UIText即可。可以设置文本颜色,按钮是否选中,以及圆角大小,基本常用的就这几项了。双击按钮,进入Click事件也是和button一致的,和button不一样的地方除了界面的区别,还有就是无法被灰色精灵破解,还有按钮自带一个string属性:CustomerInformation    便于扩展一些自定义的数据。


直接拖到主界面效果如下:

主要功能是自己获取系统的时间,然后进行显示,公开了一些允许设置的属性界面:

主要是指针的颜色设置,文本设置。


直接拖到主界面效果如下,演示了纵向和横向的两种进度条,以及一些颜色的设置,相关的属性,随便点点就可以明白:

 

主要设置的属性是背景色,前景色,是否显示文本,边框色,是否启动动画,等等

 特别说明:Max是上限值,如果设置为100,那么Value = 10  就是10%, 而另一个属性 ValueChangeSpeed 则是和动画快慢有关的。


直接拖到主界面效果如下,饼图数据的现实此处要是使用代码来实现:

饼图控件的属性相对比较少,目前就只有一个  IsRenderPercent  是否显示百分比的功能。

然后通过代码来实现设置数据信息:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

private void userButton1_Click(``object sender, EventArgs e)

{

Random random = new Random();

HslCommunication.Controls.UserPieChart[] charts = new HslCommunication.Controls.UserPieChart[4];

charts[0] = userPieChart1;

charts[1] = userPieChart2;

charts[2] = userPieChart3;

charts[3] = userPieChart4;

for (``int j = 0; j < 4; j++)

{

List<``string``> data = new List<``string``>();

List<``int``> ints = new List<``int``>();

for (``int i = 0; i < random.Next(4, 8); i++)

{

data.Add(random.Next(100, 999).ToString());

ints.Add(random.Next(1, 5));

}

charts[j].SetDataSource(

data.ToArray(),

ints.ToArray());

}

}

}

 无非是生成了随机的名字和数据,通过SetDataSource来实现数据本身。效果如下:

此处的颜色也是随机获取的,禁止了白色及接近的白色的自动生成。如果需要指定自己的颜色,需要调用该方法的重载方法,传入另一种类型的数据,允许传入名字,值,及颜色。如果设置了显示占用百分比,如下:


话不多说,先上图,再解释:

仪表盘控件的基础属性如下:

主要支持的特性为:

  • 随意调整大小,仪表的扇形也会随之变化,从半圆到微小的扇形都可以。
  • 支持设置显示区间,起始值,最大值,设置的当前值需要处于两者之间,否则无效。
  • 指针的变动支持简单的动画效果。
  • 支持手动设置刻度的分段数量,比如我要设置0-140的情况,分为14段比较合理,如果仪表盘较大,分为28段也可以。
  • 支持下限报警区间和上限报警区间,在仪表盘控件的外层有额外显示。
  • 支持值处于报警区间时的文本闪烁。
  • 支持手动设置指针的颜色。
  • 支持设置单位或额外的文本显示信息。

话不多说,先上图,再解释:

按钮开关支持2中状态,关和开,可以设置自定义的前景色,背景色,按钮开关变化时将会触发 OnSwitchChanged 事件,可以在属性窗口的事件管理器来设置事件。

主要的设置对象是前景色,背景色,开关状态。


这个控件相对比较简单,除了一个颜色设置外,目前还没有其他什么功能:

属性也很简单。


曲线控件用于方便的显示一些实时数据的趋势,由于曲线控件功能复杂,所以单独开了一篇文章讲解曲线控件:http://www.cnblogs.com/dathlin/p/8341222.html

此处就贴几张效果图:仅供参考

单曲线-像素点模式显示

单曲线-拉伸模式

多曲线-像素点模式

双坐标-多曲线-像素点模式


未完待续…