Thrift总结(二)创建RPC服务

前面介绍了thrift 基础的东西,怎么写thrift 语法规范编写脚本,如何生成相关的语言的接口。不清楚的可以看这个《Thrift总结(一)介绍》。做好之前的准备工作以后,下面就开始如何用Thrift写RPC接口。

如何用Thrift写RPC接口

1. 打开之前下载的thrift 源码,thrift-0.10.0\lib\csharp\src ,编译生成Thrift.dll 文件。

 

2. 新建一个空白解决方案命名为HelloThrift。在解决方案根目录下创建一个lib文件夹,将刚刚生成的Thrift.dll文件放入lib文件夹中。在解决方案分中建立两个控制台程序和一个类库,控制台程序分别命名为HelloThrift.Client 和 HelloThrift.Server,类库命名为Thrift.Interface。Client、Server和Interface分别引用lib文件夹中的Thrift.dll文件,将准备工作中生成的HelloService文件导入到Interface类库中。Client和Server分别引用Interface。具体结果如下图所示

 

3. 创建完相关的项目和引用之后,在服务端HelloThrift.Server 创建一个类命名为MyHelloService,实现HelloService.Iface接口,代码如下:

  清单1.MyHelloService

using System;
using HelloThrift.Interface;

namespace HelloThrift.Server
{

    public class MyHelloService : HelloService.Iface
    {
        /// 

        /// 只有一个参数返回值为字符串类型的方法
        ///

 

        /// string类型参数
        /// 返回值为string类型
        public string HelloString(string para)
        {
            System.Threading.Thread.Sleep(1 * 1000);

            Console.WriteLine("客户端调用了HelloString方法");

            return para;
        }

        /// 

        /// 只有一个参数,返回值为int类型的方法
        ///

 

        /// 
        /// 返回值为int类型
        public int HelloInt(int para)
        {
            System.Threading.Thread.Sleep(1 * 1000);

            Console.WriteLine("客户端调用了HelloInt方法");

            return para;
        }

        /// 

        /// 只有一个bool类型参数,返回值为bool类型的方法
        ///

 

        /// 
        /// 返回值为bool类型
        public bool HelloBoolean(bool para)
        {
            System.Threading.Thread.Sleep(1 * 1000);

            Console.WriteLine("客户端调用了HelloBoolean方法");

            return para;
        }


        /// 

        /// 返回执行为空的方法
        ///

 

        public void HelloVoid()
        {
            System.Threading.Thread.Sleep(1 * 1000);

            Console.WriteLine("客户端调用了HelloVoid方法");

            Console.WriteLine("HelloWorld");
        }

        /// 

        /// 无参数,返回值为null的方法
        ///

 

        /// 返回值为null
        public string HelloNull()
        {
            System.Threading.Thread.Sleep(1 * 1000);

            Console.WriteLine("客户端调用了HelloNull方法");

            return null;

        }

    }
}

 

4. 创建服务器端HelloThrift.Server 宿主的实现代码,在Program.cs 中添加如下代码,这样一个thrift 服务器就创建好了。

  清单2.HelloThrift.Server

using System;
using Thrift;
using Thrift.Protocol;
using Thrift.Server;
using Thrift.Transport;
using HelloThrift.Interface;

namespace HelloThrift.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //设置服务端口为8080
                TServerSocket serverTransport = new TServerSocket(9081);

                //设置传输协议工厂
                TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();

                //关联处理器与服务的实现
                TProcessor processor = new HelloService.Processor(new MyHelloService());

                //创建服务端对象
                TServer server = new TThreadPoolServer(processor, serverTransport, new TTransportFactory(), factory);

                Console.WriteLine("服务端正在监听9081端口");

                server.Serve();
            }
            catch (TTransportException ex)//捕获异常信息
            {
                //打印异常信息
                Console.WriteLine(ex.Message);

            }

        }
    }
}

 

5. 创建完服务端之后,下面开始生成一个客户端实现代码,在HelloThrift.Client  的 Program.cs 中添加如下代码:

  清单3.HelloThrift.Client

using System;
using HelloThrift.Interface;
using Thrift.Protocol;
using Thrift.Transport;

namespace HelloThrift.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {

                //设置服务端端口号和地址
                TTransport transport = new TSocket("localhost", 9081);
                transport.Open();

                //设置传输协议为二进制传输协议
                TProtocol protocol = new TBinaryProtocol(transport);

                //创建客户端对象
                HelloService.Client client = new HelloService.Client(protocol);

                //调用服务端的方法
                Console.WriteLine(client.HelloString("HelloThrift"));

                Console.ReadKey();

            }
            catch (TTransportException e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

上面的代码调用了服务端的HelloString方法,服务端也会返回传入的传输值,客户端将服务端返回的数据打印出来。好了完成了代码以后,将先启动Server,在启动Client。客户端调用结果 和 服务端请求显示。

 

说明

1. 关于使用Thrift 构建我们自己的rpc 的方法,这里基本讲完了。其他的方法本文就不再演示了,调用起来都是一样。

2. 后续会简单讨论一下Thrift 框架的通信原理。

3. 源代码下载,HelloThrift.rar

 

Thrift总结(一)介绍

这段时间,一直在整理公司的内部 rpc 服务接口,面临的一个问题就是:由于公司内部的系统由几个不同的语言编写的。C# ,java,node.js 等,如何实现这些内部系统之间的接口统一调用,确实是比较麻烦,本来考虑用webapi 但是感觉内部系统之间用webapi 效率不高。最终,我们还是考虑引入Thrift ,通过Thrift整合各个不同的RPC服务。下面就Thrift 如何使用,做个简单的介绍,本人也是初次接触。

  介绍

Thrift是一款由Fackbook开发的可伸缩、跨语言的服务开发框架,该框架已经开源并且加入的Apache项目。Thrift主要功能是:通过自定义的Interface Definition Language(IDL),可以创建基于RPC的客户端和服务端的服务代码。数据和服务代码的生成是通过Thrift内置的代码生成器来实现的。Thrift 的跨语言性体现在,它可以生成C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml , Delphi等语言的代码,且它们之间可以进行透明的通信。

Thrift 使得各个不同的语言的系统之间可以进行透明高效的通信。但是,Fackbook 的一贯作风就是只管发布,不管维护。所以,Thrift 目前还存在一些为解决的问题。大家在调研的时候,应该考虑清楚。

本文结合网络上的资源对从C#开发人员的角度简单介绍Thrift的使用,并且针对不同的传输协议和服务类型给出相应的C#实例,同时简单介绍Thrift异步客户端的实现。

 

Thrift代码生成器windows版下载地址

http://www.apache.org/dyn/closer.cgi?path=/thrift/0.10.0/thrift-0.10.0.exe

Thrift源码下载地址

http://www.apache.org/dyn/closer.cgi?path=/thrift/0.10.0/thrift-0.10.0.tar.gz

 

  Window 下安装配置

Thrift 不需要安装,只需下载windows版的 Thrift代码生成器即可,下载地址如上连接

注意:下载下来之后,必须把文件名字thrift-0.10.0.exe 改为  thrift.exe, 否则cmd会提示:thrift 不是内部命令的错误。

 

  创建thrift的语法规范编写脚本文件

根据thrift的语法规范编写脚本文件Hello.thrift,代码如下:

namespace csharp HelloThrift.Interface

service HelloService{

    string HelloString(1:string para)

    i32 HelloInt(1:i32 para)

    bool HelloBoolean(1:bool para)

    void HelloVoid()

    string HelloNull()

}

 

  生成Csharp 版的服务定义类

然后打开cmd切换到thrift代码生成工具的存放目录,在命令行中输入如下命令:thrift -gen csharp Hello.thrift

代码生成工具会自动在当前目录下把定义好的接口脚本生成C#代码,生成后的代码目录如下

接口脚本生成C#代码

/**
 * Autogenerated by Thrift Compiler (0.9.3)
 *
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 *  @generated
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Thrift;
using Thrift.Collections;
using System.Runtime.Serialization;
using Thrift.Protocol;
using Thrift.Transport;

namespace HelloThrift.Interface
{
  public partial class HelloService {
    public interface Iface {
      string HelloString(string para);
      #if SILVERLIGHT
      IAsyncResult Begin_HelloString(AsyncCallback callback, object state, string para);
      string End_HelloString(IAsyncResult asyncResult);
      #endif
      int HelloInt(int para);
      #if SILVERLIGHT
      IAsyncResult Begin_HelloInt(AsyncCallback callback, object state, int para);
      int End_HelloInt(IAsyncResult asyncResult);
      #endif
      bool HelloBoolean(bool para);
      #if SILVERLIGHT
      IAsyncResult Begin_HelloBoolean(AsyncCallback callback, object state, bool para);
      bool End_HelloBoolean(IAsyncResult asyncResult);
      #endif
      void HelloVoid();
      #if SILVERLIGHT
      IAsyncResult Begin_HelloVoid(AsyncCallback callback, object state);
      void End_HelloVoid(IAsyncResult asyncResult);
      #endif
      string HelloNull();
      #if SILVERLIGHT
      IAsyncResult Begin_HelloNull(AsyncCallback callback, object state);
      string End_HelloNull(IAsyncResult asyncResult);
      #endif
    }

    public class Client : IDisposable, Iface {
      public Client(TProtocol prot) : this(prot, prot)
      {
      }

      public Client(TProtocol iprot, TProtocol oprot)
      {
        iprot_ = iprot;
        oprot_ = oprot;
      }

      protected TProtocol iprot_;
      protected TProtocol oprot_;
      protected int seqid_;

      public TProtocol InputProtocol
      {
        get { return iprot_; }
      }
      public TProtocol OutputProtocol
      {
        get { return oprot_; }
      }


      #region " IDisposable Support "
      private bool _IsDisposed;

      // IDisposable
      public void Dispose()
      {
        Dispose(true);
      }
      

      protected virtual void Dispose(bool disposing)
      {
        if (!_IsDisposed)
        {
          if (disposing)
          {
            if (iprot_ != null)
            {
              ((IDisposable)iprot_).Dispose();
            }
            if (oprot_ != null)
            {
              ((IDisposable)oprot_).Dispose();
            }
          }
        }
        _IsDisposed = true;
      }
      #endregion


      
      #if SILVERLIGHT
      public IAsyncResult Begin_HelloString(AsyncCallback callback, object state, string para)
      {
        return send_HelloString(callback, state, para);
      }

      public string End_HelloString(IAsyncResult asyncResult)
      {
        oprot_.Transport.EndFlush(asyncResult);
        return recv_HelloString();
      }

      #endif

      public string HelloString(string para)
      {
        #if !SILVERLIGHT
        send_HelloString(para);
        return recv_HelloString();

        #else
        var asyncResult = Begin_HelloString(null, null, para);
        return End_HelloString(asyncResult);

        #endif
      }
      #if SILVERLIGHT
      public IAsyncResult send_HelloString(AsyncCallback callback, object state, string para)
      #else
      public void send_HelloString(string para)
      #endif
      {
        oprot_.WriteMessageBegin(new TMessage("HelloString", TMessageType.Call, seqid_));
        HelloString_args args = new HelloString_args();
        args.Para = para;
        args.Write(oprot_);
        oprot_.WriteMessageEnd();
        #if SILVERLIGHT
        return oprot_.Transport.BeginFlush(callback, state);
        #else
        oprot_.Transport.Flush();
        #endif
      }

      public string recv_HelloString()
      {
        TMessage msg = iprot_.ReadMessageBegin();
        if (msg.Type == TMessageType.Exception) {
          TApplicationException x = TApplicationException.Read(iprot_);
          iprot_.ReadMessageEnd();
          throw x;
        }
        HelloString_result result = new HelloString_result();
        result.Read(iprot_);
        iprot_.ReadMessageEnd();
        if (result.__isset.success) {
          return result.Success;
        }
        throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, "HelloString failed: unknown result");
      }

      
      #if SILVERLIGHT
      public IAsyncResult Begin_HelloInt(AsyncCallback callback, object state, int para)
      {
        return send_HelloInt(callback, state, para);
      }

      public int End_HelloInt(IAsyncResult asyncResult)
      {
        oprot_.Transport.EndFlush(asyncResult);
        return recv_HelloInt();
      }

      #endif

      public int HelloInt(int para)
      {
        #if !SILVERLIGHT
        send_HelloInt(para);
        return recv_HelloInt();

        #else
        var asyncResult = Begin_HelloInt(null, null, para);
        return End_HelloInt(asyncResult);

        #endif
      }
      #if SILVERLIGHT
      public IAsyncResult send_HelloInt(AsyncCallback callback, object state, int para)
      #else
      public void send_HelloInt(int para)
      #endif
      {
        oprot_.WriteMessageBegin(new TMessage("HelloInt", TMessageType.Call, seqid_));
        HelloInt_args args = new HelloInt_args();
        args.Para = para;
        args.Write(oprot_);
        oprot_.WriteMessageEnd();
        #if SILVERLIGHT
        return oprot_.Transport.BeginFlush(callback, state);
        #else
        oprot_.Transport.Flush();
        #endif
      }

      public int recv_HelloInt()
      {
        TMessage msg = iprot_.ReadMessageBegin();
        if (msg.Type == TMessageType.Exception) {
          TApplicationException x = TApplicationException.Read(iprot_);
          iprot_.ReadMessageEnd();
          throw x;
        }
        HelloInt_result result = new HelloInt_result();
        result.Read(iprot_);
        iprot_.ReadMessageEnd();
        if (result.__isset.success) {
          return result.Success;
        }
        throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, "HelloInt failed: unknown result");
      }

      
      #if SILVERLIGHT
      public IAsyncResult Begin_HelloBoolean(AsyncCallback callback, object state, bool para)
      {
        return send_HelloBoolean(callback, state, para);
      }

      public bool End_HelloBoolean(IAsyncResult asyncResult)
      {
        oprot_.Transport.EndFlush(asyncResult);
        return recv_HelloBoolean();
      }

      #endif

      public bool HelloBoolean(bool para)
      {
        #if !SILVERLIGHT
        send_HelloBoolean(para);
        return recv_HelloBoolean();

        #else
        var asyncResult = Begin_HelloBoolean(null, null, para);
        return End_HelloBoolean(asyncResult);

        #endif
      }
      #if SILVERLIGHT
      public IAsyncResult send_HelloBoolean(AsyncCallback callback, object state, bool para)
      #else
      public void send_HelloBoolean(bool para)
      #endif
      {
        oprot_.WriteMessageBegin(new TMessage("HelloBoolean", TMessageType.Call, seqid_));
        HelloBoolean_args args = new HelloBoolean_args();
        args.Para = para;
        args.Write(oprot_);
        oprot_.WriteMessageEnd();
        #if SILVERLIGHT
        return oprot_.Transport.BeginFlush(callback, state);
        #else
        oprot_.Transport.Flush();
        #endif
      }

      public bool recv_HelloBoolean()
      {
        TMessage msg = iprot_.ReadMessageBegin();
        if (msg.Type == TMessageType.Exception) {
          TApplicationException x = TApplicationException.Read(iprot_);
          iprot_.ReadMessageEnd();
          throw x;
        }
        HelloBoolean_result result = new HelloBoolean_result();
        result.Read(iprot_);
        iprot_.ReadMessageEnd();
        if (result.__isset.success) {
          return result.Success;
        }
        throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, "HelloBoolean failed: unknown result");
      }

      
      #if SILVERLIGHT
      public IAsyncResult Begin_HelloVoid(AsyncCallback callback, object state)
      {
        return send_HelloVoid(callback, state);
      }

      public void End_HelloVoid(IAsyncResult asyncResult)
      {
        oprot_.Transport.EndFlush(asyncResult);
        recv_HelloVoid();
      }

      #endif

      public void HelloVoid()
      {
        #if !SILVERLIGHT
        send_HelloVoid();
        recv_HelloVoid();

        #else
        var asyncResult = Begin_HelloVoid(null, null);
        End_HelloVoid(asyncResult);

        #endif
      }
      #if SILVERLIGHT
      public IAsyncResult send_HelloVoid(AsyncCallback callback, object state)
      #else
      public void send_HelloVoid()
      #endif
      {
        oprot_.WriteMessageBegin(new TMessage("HelloVoid", TMessageType.Call, seqid_));
        HelloVoid_args args = new HelloVoid_args();
        args.Write(oprot_);
        oprot_.WriteMessageEnd();
        #if SILVERLIGHT
        return oprot_.Transport.BeginFlush(callback, state);
        #else
        oprot_.Transport.Flush();
        #endif
      }

      public void recv_HelloVoid()
      {
        TMessage msg = iprot_.ReadMessageBegin();
        if (msg.Type == TMessageType.Exception) {
          TApplicationException x = TApplicationException.Read(iprot_);
          iprot_.ReadMessageEnd();
          throw x;
        }
        HelloVoid_result result = new HelloVoid_result();
        result.Read(iprot_);
        iprot_.ReadMessageEnd();
        return;
      }

      
      #if SILVERLIGHT
      public IAsyncResult Begin_HelloNull(AsyncCallback callback, object state)
      {
        return send_HelloNull(callback, state);
      }

      public string End_HelloNull(IAsyncResult asyncResult)
      {
        oprot_.Transport.EndFlush(asyncResult);
        return recv_HelloNull();
      }

      #endif

      public string HelloNull()
      {
        #if !SILVERLIGHT
        send_HelloNull();
        return recv_HelloNull();

        #else
        var asyncResult = Begin_HelloNull(null, null);
        return End_HelloNull(asyncResult);

        #endif
      }
      #if SILVERLIGHT
      public IAsyncResult send_HelloNull(AsyncCallback callback, object state)
      #else
      public void send_HelloNull()
      #endif
      {
        oprot_.WriteMessageBegin(new TMessage("HelloNull", TMessageType.Call, seqid_));
        HelloNull_args args = new HelloNull_args();
        args.Write(oprot_);
        oprot_.WriteMessageEnd();
        #if SILVERLIGHT
        return oprot_.Transport.BeginFlush(callback, state);
        #else
        oprot_.Transport.Flush();
        #endif
      }

      public string recv_HelloNull()
      {
        TMessage msg = iprot_.ReadMessageBegin();
        if (msg.Type == TMessageType.Exception) {
          TApplicationException x = TApplicationException.Read(iprot_);
          iprot_.ReadMessageEnd();
          throw x;
        }
        HelloNull_result result = new HelloNull_result();
        result.Read(iprot_);
        iprot_.ReadMessageEnd();
        if (result.__isset.success) {
          return result.Success;
        }
        throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, "HelloNull failed: unknown result");
      }

    }
    public class Processor : TProcessor {
      public Processor(Iface iface)
      {
        iface_ = iface;
        processMap_["HelloString"] = HelloString_Process;
        processMap_["HelloInt"] = HelloInt_Process;
        processMap_["HelloBoolean"] = HelloBoolean_Process;
        processMap_["HelloVoid"] = HelloVoid_Process;
        processMap_["HelloNull"] = HelloNull_Process;
      }

      protected delegate void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);
      private Iface iface_;
      protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();

      public bool Process(TProtocol iprot, TProtocol oprot)
      {
        try
        {
          TMessage msg = iprot.ReadMessageBegin();
          ProcessFunction fn;
          processMap_.TryGetValue(msg.Name, out fn);
          if (fn == null) {
            TProtocolUtil.Skip(iprot, TType.Struct);
            iprot.ReadMessageEnd();
            TApplicationException x = new TApplicationException (TApplicationException.ExceptionType.UnknownMethod, "Invalid method name: '" + msg.Name + "'");
            oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));
            x.Write(oprot);
            oprot.WriteMessageEnd();
            oprot.Transport.Flush();
            return true;
          }
          fn(msg.SeqID, iprot, oprot);
        }
        catch (IOException)
        {
          return false;
        }
        return true;
      }

      public void HelloString_Process(int seqid, TProtocol iprot, TProtocol oprot)
      {
        HelloString_args args = new HelloString_args();
        args.Read(iprot);
        iprot.ReadMessageEnd();
        HelloString_result result = new HelloString_result();
        result.Success = iface_.HelloString(args.Para);
        oprot.WriteMessageBegin(new TMessage("HelloString", TMessageType.Reply, seqid)); 
        result.Write(oprot);
        oprot.WriteMessageEnd();
        oprot.Transport.Flush();
      }

      public void HelloInt_Process(int seqid, TProtocol iprot, TProtocol oprot)
      {
        HelloInt_args args = new HelloInt_args();
        args.Read(iprot);
        iprot.ReadMessageEnd();
        HelloInt_result result = new HelloInt_result();
        result.Success = iface_.HelloInt(args.Para);
        oprot.WriteMessageBegin(new TMessage("HelloInt", TMessageType.Reply, seqid)); 
        result.Write(oprot);
        oprot.WriteMessageEnd();
        oprot.Transport.Flush();
      }

      public void HelloBoolean_Process(int seqid, TProtocol iprot, TProtocol oprot)
      {
        HelloBoolean_args args = new HelloBoolean_args();
        args.Read(iprot);
        iprot.ReadMessageEnd();
        HelloBoolean_result result = new HelloBoolean_result();
        result.Success = iface_.HelloBoolean(args.Para);
        oprot.WriteMessageBegin(new TMessage("HelloBoolean", TMessageType.Reply, seqid)); 
        result.Write(oprot);
        oprot.WriteMessageEnd();
        oprot.Transport.Flush();
      }

      public void HelloVoid_Process(int seqid, TProtocol iprot, TProtocol oprot)
      {
        HelloVoid_args args = new HelloVoid_args();
        args.Read(iprot);
        iprot.ReadMessageEnd();
        HelloVoid_result result = new HelloVoid_result();
        iface_.HelloVoid();
        oprot.WriteMessageBegin(new TMessage("HelloVoid", TMessageType.Reply, seqid)); 
        result.Write(oprot);
        oprot.WriteMessageEnd();
        oprot.Transport.Flush();
      }

      public void HelloNull_Process(int seqid, TProtocol iprot, TProtocol oprot)
      {
        HelloNull_args args = new HelloNull_args();
        args.Read(iprot);
        iprot.ReadMessageEnd();
        HelloNull_result result = new HelloNull_result();
        result.Success = iface_.HelloNull();
        oprot.WriteMessageBegin(new TMessage("HelloNull", TMessageType.Reply, seqid)); 
        result.Write(oprot);
        oprot.WriteMessageEnd();
        oprot.Transport.Flush();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloString_args : TBase
    {
      private string _para;

      public string Para
      {
        get
        {
          return _para;
        }
        set
        {
          __isset.para = true;
          this._para = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool para;
      }

      public HelloString_args() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 1:
                if (field.Type == TType.String) {
                  Para = iprot.ReadString();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloString_args");
          oprot.WriteStructBegin(struc);
          TField field = new TField();
          if (Para != null && __isset.para) {
            field.Name = "para";
            field.Type = TType.String;
            field.ID = 1;
            oprot.WriteFieldBegin(field);
            oprot.WriteString(Para);
            oprot.WriteFieldEnd();
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloString_args(");
        bool __first = true;
        if (Para != null && __isset.para) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Para: ");
          __sb.Append(Para);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloString_result : TBase
    {
      private string _success;

      public string Success
      {
        get
        {
          return _success;
        }
        set
        {
          __isset.success = true;
          this._success = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool success;
      }

      public HelloString_result() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 0:
                if (field.Type == TType.String) {
                  Success = iprot.ReadString();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloString_result");
          oprot.WriteStructBegin(struc);
          TField field = new TField();

          if (this.__isset.success) {
            if (Success != null) {
              field.Name = "Success";
              field.Type = TType.String;
              field.ID = 0;
              oprot.WriteFieldBegin(field);
              oprot.WriteString(Success);
              oprot.WriteFieldEnd();
            }
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloString_result(");
        bool __first = true;
        if (Success != null && __isset.success) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Success: ");
          __sb.Append(Success);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloInt_args : TBase
    {
      private int _para;

      public int Para
      {
        get
        {
          return _para;
        }
        set
        {
          __isset.para = true;
          this._para = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool para;
      }

      public HelloInt_args() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 1:
                if (field.Type == TType.I32) {
                  Para = iprot.ReadI32();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloInt_args");
          oprot.WriteStructBegin(struc);
          TField field = new TField();
          if (__isset.para) {
            field.Name = "para";
            field.Type = TType.I32;
            field.ID = 1;
            oprot.WriteFieldBegin(field);
            oprot.WriteI32(Para);
            oprot.WriteFieldEnd();
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloInt_args(");
        bool __first = true;
        if (__isset.para) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Para: ");
          __sb.Append(Para);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloInt_result : TBase
    {
      private int _success;

      public int Success
      {
        get
        {
          return _success;
        }
        set
        {
          __isset.success = true;
          this._success = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool success;
      }

      public HelloInt_result() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 0:
                if (field.Type == TType.I32) {
                  Success = iprot.ReadI32();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloInt_result");
          oprot.WriteStructBegin(struc);
          TField field = new TField();

          if (this.__isset.success) {
            field.Name = "Success";
            field.Type = TType.I32;
            field.ID = 0;
            oprot.WriteFieldBegin(field);
            oprot.WriteI32(Success);
            oprot.WriteFieldEnd();
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloInt_result(");
        bool __first = true;
        if (__isset.success) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Success: ");
          __sb.Append(Success);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloBoolean_args : TBase
    {
      private bool _para;

      public bool Para
      {
        get
        {
          return _para;
        }
        set
        {
          __isset.para = true;
          this._para = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool para;
      }

      public HelloBoolean_args() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 1:
                if (field.Type == TType.Bool) {
                  Para = iprot.ReadBool();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloBoolean_args");
          oprot.WriteStructBegin(struc);
          TField field = new TField();
          if (__isset.para) {
            field.Name = "para";
            field.Type = TType.Bool;
            field.ID = 1;
            oprot.WriteFieldBegin(field);
            oprot.WriteBool(Para);
            oprot.WriteFieldEnd();
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloBoolean_args(");
        bool __first = true;
        if (__isset.para) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Para: ");
          __sb.Append(Para);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloBoolean_result : TBase
    {
      private bool _success;

      public bool Success
      {
        get
        {
          return _success;
        }
        set
        {
          __isset.success = true;
          this._success = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool success;
      }

      public HelloBoolean_result() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 0:
                if (field.Type == TType.Bool) {
                  Success = iprot.ReadBool();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloBoolean_result");
          oprot.WriteStructBegin(struc);
          TField field = new TField();

          if (this.__isset.success) {
            field.Name = "Success";
            field.Type = TType.Bool;
            field.ID = 0;
            oprot.WriteFieldBegin(field);
            oprot.WriteBool(Success);
            oprot.WriteFieldEnd();
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloBoolean_result(");
        bool __first = true;
        if (__isset.success) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Success: ");
          __sb.Append(Success);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloVoid_args : TBase
    {

      public HelloVoid_args() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloVoid_args");
          oprot.WriteStructBegin(struc);
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloVoid_args(");
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloVoid_result : TBase
    {

      public HelloVoid_result() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloVoid_result");
          oprot.WriteStructBegin(struc);

          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloVoid_result(");
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloNull_args : TBase
    {

      public HelloNull_args() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloNull_args");
          oprot.WriteStructBegin(struc);
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloNull_args(");
        __sb.Append(")");
        return __sb.ToString();
      }

    }


    #if !SILVERLIGHT
    [Serializable]
    #endif
    public partial class HelloNull_result : TBase
    {
      private string _success;

      public string Success
      {
        get
        {
          return _success;
        }
        set
        {
          __isset.success = true;
          this._success = value;
        }
      }


      public Isset __isset;
      #if !SILVERLIGHT
      [Serializable]
      #endif
      public struct Isset {
        public bool success;
      }

      public HelloNull_result() {
      }

      public void Read (TProtocol iprot)
      {
        iprot.IncrementRecursionDepth();
        try
        {
          TField field;
          iprot.ReadStructBegin();
          while (true)
          {
            field = iprot.ReadFieldBegin();
            if (field.Type == TType.Stop) { 
              break;
            }
            switch (field.ID)
            {
              case 0:
                if (field.Type == TType.String) {
                  Success = iprot.ReadString();
                } else { 
                  TProtocolUtil.Skip(iprot, field.Type);
                }
                break;
              default: 
                TProtocolUtil.Skip(iprot, field.Type);
                break;
            }
            iprot.ReadFieldEnd();
          }
          iprot.ReadStructEnd();
        }
        finally
        {
          iprot.DecrementRecursionDepth();
        }
      }

      public void Write(TProtocol oprot) {
        oprot.IncrementRecursionDepth();
        try
        {
          TStruct struc = new TStruct("HelloNull_result");
          oprot.WriteStructBegin(struc);
          TField field = new TField();

          if (this.__isset.success) {
            if (Success != null) {
              field.Name = "Success";
              field.Type = TType.String;
              field.ID = 0;
              oprot.WriteFieldBegin(field);
              oprot.WriteString(Success);
              oprot.WriteFieldEnd();
            }
          }
          oprot.WriteFieldStop();
          oprot.WriteStructEnd();
        }
        finally
        {
          oprot.DecrementRecursionDepth();
        }
      }

      public override string ToString() {
        StringBuilder __sb = new StringBuilder("HelloNull_result(");
        bool __first = true;
        if (Success != null && __isset.success) {
          if(!__first) { __sb.Append(", "); }
          __first = false;
          __sb.Append("Success: ");
          __sb.Append(Success);
        }
        __sb.Append(")");
        return __sb.ToString();
      }

    }

  }
}

HelloService定义了服务HelloService的五个方法,每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名。包含了在 Hello.thrift 文件中描述的服务 HelloService 的接口定义,即HelloService.Iface 接口,以及服务调用的底层通信细节,包括客户端的调用逻辑HelloService.Client 以及服务器端的处理逻辑HelloService.Processor,用于构建客户端和服务器端的功能。

这样,C#版的接口代码就生成成功了。非常简单,下一篇,会具体的介绍服务器端如何实现调用这些接口和客户端如何调用这些接口。

 

C#总结(三)DataGridView增加全选列

最近的一个winform的项目中,碰到datagridview控件的第一列添加全选的功能,通常这个功能,有两种实现方式:1. 为控件添加DataGridViewCheckBoxColumn来实现,但是需要提供全选反选功能,2. 再加一个checkbox控件跟datagridview组合来实现全选反选功能。但是,感觉这两种实现效果都不是很好。网上查资料,发现一个老外的实现方法,比较简单通用。demo 代码最下面的连接给出。

他的实现方式就是:DataGridViewCheckBoxColumn的父类DataGridViewColumnHeaderCell 里面有个HeaderCell的属性,看下DataGridViewColumnHeaderCell 的继承关系,就可以知道它继承自DataGridViewCell类, 所以只需要重写DataGridViewColumnHeaderCell类的paint方法,用CheckBoxRenderer画一个Checkbox到单元格上。即可实现在datagridview的列头增加一个全选的checkbox 。以下是实现代码:

实现代码

public delegate void CheckBoxClickedHandler(bool state);
    public class DataGridViewCheckBoxHeaderCellEventArgs : EventArgs
    {
        bool _bChecked;
        public DataGridViewCheckBoxHeaderCellEventArgs(bool bChecked)
        {
            _bChecked = bChecked;
        }
        public bool Checked
        {
            get { return _bChecked; }
        }
    }
    class DatagridViewCheckBoxHeaderCell : DataGridViewColumnHeaderCell
    {
        Point checkBoxLocation;
        Size checkBoxSize;
        bool _checked = false;
        Point _cellLocation = new Point();
        System.Windows.Forms.VisualStyles.CheckBoxState _cbState = 
            System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal;
        public event CheckBoxClickedHandler OnCheckBoxClicked;
 
        public DatagridViewCheckBoxHeaderCell()
        {           
        }

        protected override void Paint(System.Drawing.Graphics graphics, 
            System.Drawing.Rectangle clipBounds, 
            System.Drawing.Rectangle cellBounds, 
            int rowIndex, 
            DataGridViewElementStates dataGridViewElementState, 
            object value, 
            object formattedValue, 
            string errorText, 
            DataGridViewCellStyle cellStyle, 
            DataGridViewAdvancedBorderStyle advancedBorderStyle, 
            DataGridViewPaintParts paintParts)
        {
            base.Paint(graphics, clipBounds, cellBounds, rowIndex, 
                dataGridViewElementState, value, 
                formattedValue, errorText, cellStyle, 
                advancedBorderStyle, paintParts);
            Point p = new Point();
            Size s = CheckBoxRenderer.GetGlyphSize(graphics, 
            System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
            p.X = cellBounds.Location.X + 
                (cellBounds.Width / 2) - (s.Width / 2) ;
            p.Y = cellBounds.Location.Y + 
                (cellBounds.Height / 2) - (s.Height / 2);
            _cellLocation = cellBounds.Location;
            checkBoxLocation = p;
            checkBoxSize = s;
            if (_checked)
                _cbState = System.Windows.Forms.VisualStyles.
                    CheckBoxState.CheckedNormal;
            else
                _cbState = System.Windows.Forms.VisualStyles.
                    CheckBoxState.UncheckedNormal;
            CheckBoxRenderer.DrawCheckBox
            (graphics, checkBoxLocation, _cbState);
        }

        protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
        {
            Point p = new Point(e.X + _cellLocation.X, e.Y + _cellLocation.Y);
            if (p.X >= checkBoxLocation.X && p.X <= 
                checkBoxLocation.X + checkBoxSize.Width 
            && p.Y >= checkBoxLocation.Y && p.Y <= 
                checkBoxLocation.Y + checkBoxSize.Height)
            {
                _checked = !_checked;
                if (OnCheckBoxClicked != null)
                {
                    OnCheckBoxClicked(_checked);
                    this.DataGridView.InvalidateCell(this);
                }
                
            } 
            base.OnMouseClick(e);
        }     
    }

调用方式

DataGridViewCheckBoxColumn colCB = new DataGridViewCheckBoxColumn();
DatagridViewCheckBoxHeaderCell cbHeader = new DatagridViewCheckBoxHeaderCell();
colCB.HeaderCell = cbHeader;
datagridview1.Columns.Add(colCB);
cbHeader.OnCheckBoxClicked += 
    new CheckBoxClickedHandler(cbHeader_OnCheckBoxClicked);

  1. 我们只需要定义一个DataGridViewCheckBoxColumn。

2. 然后为每一行的checkbox 定义一个CheckboxClicked 事件。

 

测试程序

创建一个Winform 项目,加个datagridview控件,初始化几行默认数据。注意:datagirdview有编辑状态,如果有一行数据在编辑状态,那这一行被编辑。

解决办法就是在事件的绑定方法里面增加EndEdit()调用。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            InitDtSource();
        }

        private void cbHeader_OnCheckBoxClicked(bool state)
        {
            //这一句很重要结束编辑状态
            dgInfo.EndEdit();
            dgInfo.Rows.OfType().ToList().ForEach(t => t.Cells[0].Value = state);
        }

        private void InitDtSource()
        {
            try
            {
                var _dtSource = new DataTable();
                //1、添加列
                _dtSource.Columns.Add("姓名", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("身份证号", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("时间", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("地点", typeof(string)); //数据类型为 文本

                for (int i = 0; i < 10; i++)
                {
                    DataRow drData = _dtSource.NewRow();
                    drData[0] = "test" + i;
                    drData[1] = "35412549554521263" + i;
                    drData[2] = "2017-05-21 10:55:21";
                    drData[3] = "北京市";
                    _dtSource.Rows.Add(drData);
                }

                dgInfo.DataSource = _dtSource;

                InitColumnInfo();
            }
            catch (Exception ex)
            {

            }
        }

        private void InitColumnInfo()
        {
            int index = 0;

            DataGridViewCheckBoxColumn colCB = new DataGridViewCheckBoxColumn();
            DatagridViewCheckBoxHeaderCell cbHeader = new DatagridViewCheckBoxHeaderCell();
            colCB.HeaderCell = cbHeader;
            colCB.HeaderText = "全选";
            cbHeader.OnCheckBoxClicked += new CheckBoxClickedHandler(cbHeader_OnCheckBoxClicked);
            dgInfo.Columns.Insert(index, colCB);


            index++;
            dgInfo.Columns[index].HeaderText = "姓名";
            dgInfo.Columns[index].Width = 90;

            index++;
            dgInfo.Columns[index].HeaderText = "身份证号";
            dgInfo.Columns[index].Width = 120;

            index++;
            dgInfo.Columns[index].HeaderText = "时间";
            dgInfo.Columns[index].Width = 150;

            index++;
            dgInfo.Columns[index].HeaderText = "地点";
            dgInfo.Columns[index].Width = 100;

            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;//211, 223, 240
            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.Blue;
            dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Blue;
            dgInfo.Columns[index].DefaultCellStyle = dataGridViewCellStyle2;
        }
    }

 

其他

1. 参考地址:https://www.codeproject.com/Articles/20165/CheckBox-Header-Column-For-DataGridView

2. Demo下载

 

C#总结(二)事件Event 介绍总结

  最近在总结一些基础的东西,主要是学起来很难懂,但是在日常又有可能会经常用到的东西。前面介绍了 C# 的 AutoResetEvent的使用介绍, 这次介绍事件(event)。

  事件(event),对于初学者来说,确实比较神秘,难懂。但是在日常编程过程中却经常遇到。事件使用得当,会让你的代码更加整洁,也能少些很多代码。

 

  一、Event事件,是一种封装过的委托。

  它拥有以下三要素:

    1. 事件发行者:达到某些条件时激发事件的对象

    2. 事件订阅者:订阅事件并对事件发生时进行处理的对象

    3. 定义发行者和订阅者关系,一个发行者可能会有多个订阅者。

  事件发行者和事件订阅者通过委托(delegate) 来实现发送方和接收方的事件触发。

  它拥有哪些好处:

    在以往我们编写订阅这类程序中,往往采用等待机制,为了等待某件事情的发生,需要不断地检测事情什么时候发生,而通过事件(event),可以大大简化了这种过程:

    1. 使用事件,可以很方便地确定程序执行顺序。

    2. 当事件驱动程序等待事件时,它不占用很多资源。事件驱动程序与过程式程序最大的不同就在于,程序不再不停地检查输入设备,而是呆着不动,等待消息的到来,每个输入的消息会被排进队列,等待程序处理它。如果没有消息在等待,则程序会把控制交回给操作系统,以运行其他程序。

    3. 事件简化了编程。事件订阅者只是简单地将消息传送给事件发行者,由发行者的事件驱动程序确定事件的处理方法。不必知道程序的内部订阅触发机制,只是需要知道如何传递消息即可。

  二、事件和委托的区别

    1.委托允许直接通过委托去访问相应的处理函数,而事件只能通过公布的回调函数去调用,

    2.事件只能通过“+=”,“-=”方式注册和取消订户处理函数,而委托除此之外还可以使用“=”直接赋值处理函数。

 

  三、事件的声明(Event)

    在类的内部声明事件,首先必须声明该事件的委托类型。例如:

    public delegate void NumManipulationHandler(NumEventArgs e);

    然后,声明事件本身,使用 event 关键字:

    // 基于上面的委托定义事件
    public event NumManipulationHandler ChangeNum;

    上面的代码定义了一个名为 NumManipulationHandler 的委托和一个名为 ChangeNum 的事件,该事件是在某个值生成的时候会调用委托事件。

 

  四、实例

    public class Program
    {
        public static void Main()
        {

            NumEvent even = new NumEvent(0);
            even.ChangeNum += EventAction.Action;

            even.SetValue(7);
            even.SetValue(11);

            System.Console.ReadKey();
        }
    }

    public class NumEvent
    {
        private int value;

        public delegate void NumManipulationHandler(NumEventArgs e);

        public event NumManipulationHandler ChangeNum;

        public virtual void OnChangeNum(NumEventArgs e)
        {
            ChangeNum?.Invoke(e);
        }

        public NumEvent(int n)
        {
            SetValue(n);
        }

        public void SetValue(int n)
        {
            if (value != n)
            {
                NumEventArgs e = new NumEventArgs(n);
                value = n;
                OnChangeNum(e);
            }
        }
    }

    public class EventAction
    {
        public static void Action(NumEventArgs e)
        {
            System.Console.WriteLine("value : " + e.value);
        }
    }

    public class NumEventArgs : EventArgs
    {
        public int value;
        public NumEventArgs(int _value)
        {
            this.value = _value;
        }
    }

 

Quartz.NET总结(六)了解Jobs 和 Triggers

前面讲了一些Quartz.net 的一些东西, http://www.cnblogs.com/zhangweizhong/category/771057.html

但是发现有一些基础的东西,没有说到。Quartz.net 里面一些个基本的元素,比如Jobs,Triggers等。

Quartz API 的接口和类主要有:

  1. IScheduler – scheduler调度的主要接口api
  2. IJob – scheduler调度执行job需要实现的接口api
  3. IJobDetail – 用来定义jobs的实例
  4. ITrigger – 定义需要执行的job的调度构成
  5. JobBuilder – 用于定义/创建job的明细情况,定义job的实例
  6. TriggerBuilder – 用于定义/创建Trigger的实例

一个Scheduler调度的生命周期从开始创建,到结束关闭,都是通过SchedulerFactory 实现的。当我们创建了IScheduler接口后,我们就可以用来add、remove、或者list jobs和triggers,或者是其他与调度相关的工作。

Scheduler 调度的方法有:

  1. WithCalendarIntervalSchedule
  2. WithCronSchedule
  3. WithDailyTimeIntervalSchedule
  4. WithSimpleSchedule

使用DateBuilder可以轻松的构建特定的执行时间点等,如下个小时、十点钟、或者是其他的具体时间方式。

Jobs and Triggers

定义一个job必须实现了IJob接口,如下为IJob接口的方法:

namespace Quartz
{
    public interface IJob
    {
        void Execute(JobExecutionContext context);
    }
}

当 Job的trigger被触发后,scheduler调度的线程 就会执行 Execute 方法,同时 JobExecutionContext 包含运行环境参数等信息也会传递给execute方法。

比如说:job可以创建并存储在独立的scheduler调度中并由Trigger触发,不同的Trigger触发器可以触发相同的job任务。这种松耦合能够保证如果Job的触发规则变了,不用去该job,而只需修改某个Trigger的触发规则。同时,配置在scheduler调度中的job任务任然是可以被其他触发器使用,而不需要我们重新定义新的job任务。

接下来需要我们了解知道的job深入内容主要有IJob接口的Execute()方法和JobDetails。

对于一个job任务来说,我们知道如何制定特定type类型和如何编写实现一个job的代码,对于使用Quartz.NET来说我们需要了解它各种各样的属性参数,可以通过job的JobDetail明细类来进行分析。

JobDetail明细类的实例是通过JobBuilder创建的,JobBuilder允许我们使用连续的接口方式进行实现和编码。

        // define the job and tie it to our HelloJob class
        IJobDetail job = JobBuilder.Create()
          .WithIdentity("myJob", "group1") // name "myJob", group "group1"
          .Build();

        // Trigger the job to run now, and then every 40 seconds
        ITrigger trigger = TriggerBuilder.Create()
          .WithIdentity("myTrigger", "group1")
          .StartNow()
          .WithSimpleSchedule(x => x
            .WithIntervalInSeconds(40)
            .RepeatForever())
          .Build();

        // Tell quartz to schedule the job using our trigger
        sched.scheduleJob(job, trigger);

接下来定义一个job实现类HelloJob:

    public class HelloJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            Console.WriteLine("HelloJob is executing.");
        }
    }

说到这里,有些 job任务是需要设置一些属性或者参数的 ,怎么给运行中的Job赋值呢? JobDataMap 提供了这方面的实现,也就是在 JobDetail对象中,设置job需要的值或参数。

JobDataMap

JobDataMap可以存储容纳任意数量的可序列化对象,以便在job实例化执行的时候使用。JobDataMap是IDictionary接口的实现,并添加了一些方便存储和检索原始类型数据的方法。

下面演示一小段代码来说明将job任务添加到scheduler调度前先将数据添加到JobDataMap:

Setting Values in a JobDataMap

  // define the job and tie it to our DumbJob class
            IJobDetail job = JobBuilder.Create()
               .WithIdentity("myJob", "group1") // name "myJob", group "group1"
                  .UsingJobData("jobSays", "Hello World!")
               .UsingJobData("myFloatValue", 3.141f)
               .Build();

下面演示下job任务执行时候从JobDataMap中取出数据的代码片段:

Getting Values from a JobDataMap

  public class DumbJob : IJob
    {
        public void Execute(JobExecutionContext context)
        {
            JobKey key = context.JobDetail.Key;

            JobDataMap dataMap = context.JobDetail.JobDataMap;

            string jobSays = dataMap.GetString("jobSays");
            float myFloatValue = dataMap.GetFloat("myFloatValue");

            Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
        }
    }

有了JobDataMaps Job在执行的过程中,就可以取出需要的相关参数。

 

 

C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步)

  前几天碰到一个线程的顺序执行的问题,就是一个异步线程往A接口发送一个数据请求。另外一个异步线程往B接口发送一个数据请求,当A和B都执行成功了,再往C接口发送一个请求。说真的,一直做BS项目,对线程了解,还真不多。就知道AutoResetEvent这个东西和线程有关,用于处理线程切换之类,于是决定用AutoResetEvent来处理上面的问题。
 
  于是网上查找相关资料:
 
  原来,AutoResetEvent在.Net多线程编程中,经常用到。当某个线程调用WaitOne方法后,信号处于发送状态,该线程会得到信号, 程序就会继续向下执行,否则就等待。而且 AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,其他调用WaitOne的线程只有继续等待.也就是说,AutoResetEvent一次只唤醒一个线程,其他线程还是堵塞。
 
  简介
   AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。
        false:无信号,子线程的WaitOne方法不会被自动调用
        true:有信号,子线程的WaitOne方法会被自动调用
   Reset ():将事件状态设置为非终止状态,导致线程阻止;如果该操作成功,则返回true;否则,返回false。
   Set ():将事件状态设置为终止状态,允许一个或多个等待线程继续;如果该操作成功,则返回true;否则,返回false。
   WaitOne(): 阻止当前线程,直到收到信号。
   WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。   
     WaitAll():等待全部信号。
  实现

      

 class Program
    {

        static void Main()
        {
            Request req = new Request();

            //这个人去干三件大事  
            Thread GetCarThread = new Thread(new ThreadStart(req.InterfaceA));
            GetCarThread.Start();

            Thread GetHouseThead = new Thread(new ThreadStart(req.InterfaceB));
            GetHouseThead.Start();

            //等待三件事都干成的喜讯通知信息  
            AutoResetEvent.WaitAll(req.autoEvents);

            //这个人就开心了。  
            req.InterfaceC();

            System.Console.ReadKey();
        }
    }

    public class Request
    {
        //建立事件数组  
        public AutoResetEvent[] autoEvents = null;

        public Request()
        {
            autoEvents = new AutoResetEvent[]
            {
                new AutoResetEvent(false),
                new AutoResetEvent(false)
            };
        }

        public void InterfaceA()
        {
            System.Console.WriteLine("请求A接口");

            Thread.Sleep(1000*2);

            autoEvents[0].Set();

            System.Console.WriteLine("A接口完成");
        }

        public void InterfaceB()
        {
            System.Console.WriteLine("请求B接口");

            Thread.Sleep(1000 * 1);

            autoEvents[1].Set();

            System.Console.WriteLine("B接口完成");
        }

        public void InterfaceC()
        {
            System.Console.WriteLine("两个接口都已经请求完,正在处理C");
        }
    }

 

  注意,WaitOne 或是WaitAll 最好都加上超时时间。否则没有收到信号,线程一直会阻塞。
 
  后话
  这个只是上面的场景的一个简化,主要是用来解决刚刚我说的那个场景的问题。
  以上是自己对 AutoResetEvent 的使用总结。不足之处请各位指点一二。
 

Redis总结(五)缓存雪崩和缓存穿透等问题

  前面讲过一些redis 缓存的使用和数据持久化。感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhong/category/771056.html 。今天总结总结缓存使用过程中遇到的一些常见的问题。比如缓存雪崩,缓存穿透,缓存预热等等。

缓存雪崩

  缓存雪崩是由于原有缓存失效(过期),新缓存未到期间。所有请求都去查询数据库,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

  1. 碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。

        public object GetProductListNew()
        {
            const int cacheTime = 30;
            const string cacheKey = "product_list";
            const string lockKey = cacheKey;
            
            var cacheValue = CacheHelper.Get(cacheKey);
            if (cacheValue != null)
            {
                return cacheValue;
            }
            else
            {
                lock (lockKey)
                {
                    cacheValue = CacheHelper.Get(cacheKey);
                    if (cacheValue != null)
                    {
                        return cacheValue;
                    }
                    else
                    {
                        cacheValue = GetProductListFromDB(); //这里一般是 sql查询数据。              
                        CacheHelper.Add(cacheKey, cacheValue, cacheTime);
                    }                    
                }
                return cacheValue;
            }
        }    
      

 

  2. 加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

  还有一个解决办法解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

     public object GetProductListNew()
        {
            const int cacheTime = 30;
            const string cacheKey = "product_list";
            //缓存标记。
            const string cacheSign = cacheKey + "_sign";
            
            var sign = CacheHelper.Get(cacheSign);
            //获取缓存值
            var cacheValue = CacheHelper.Get(cacheKey);
            if (sign != null)
            {
                return cacheValue; //未过期,直接返回。
            }
            else
            {
                CacheHelper.Add(cacheSign, "1", cacheTime);
                ThreadPool.QueueUserWorkItem((arg) =>
                {
                    cacheValue = GetProductListFromDB(); //这里一般是 sql查询数据。
                    CacheHelper.Add(cacheKey, cacheValue, cacheTime*2); //日期设缓存时间的2倍,用于脏读。                
                });
                
                return cacheValue;
            }
        } 

  缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存。

  缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

  这样做后,就可以一定程度上提高系统吞吐量。

缓存穿透

  缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

  解决的办法就是:如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

        public object GetProductListNew()
        {
            const int cacheTime = 30;
            const string cacheKey = "product_list";

            var cacheValue = CacheHelper.Get(cacheKey);
            if (cacheValue != null)
                return cacheValue;
                
            cacheValue = CacheHelper.Get(cacheKey);
            if (cacheValue != null)
            {
                return cacheValue;
            }
            else
            {
                cacheValue = GetProductListFromDB(); //数据库查询不到,为空。
                
                if (cacheValue == null)
                {
                    cacheValue = string.Empty; //如果发现为空,设置个默认值,也缓存起来。                
                }
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
                
                return cacheValue;
            }
        }    

  把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

缓存预热

  缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样避免,用户请求的时候,再去加载相关的数据。

  解决思路:

    1,直接写个缓存刷新页面,上线时手工操作下。

    2,数据量不大,可以在WEB系统启动的时候加载。

    3,定时刷新缓存,

缓存更新

  缓存淘汰的策略有两种:

    (1) 定时去清理过期的缓存。

    (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 

  两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂,具体用哪种方案,大家可以根据自己的应用场景来权衡。1. 预估失效时间 2. 版本号(必须单调递增,时间戳是最好的选择)3. 提供手动清理缓存的接口。

  我前面有篇文章,是介绍缓存系统的缓存更新的。感兴趣的朋友可以看看:http://www.cnblogs.com/zhangweizhong/p/5884761.html

总结

  这些都是实际项目中,可能碰到的一些问题。实际上还有很多很多各种各样的问题。缓存层框架的封装往往要复杂的多。应用场景不同,方法和解决方案也不同。具体要根据实际情况来取舍。  

 

Web API系列(三)统一异常处理

  前面讲了webapi的安全验证和参数安全,不清楚的朋友,可以看看前面的文章,《Web API系列(二)接口安全和参数校验》,本文主要介绍Web API异常结果的处理。作为内部或者是对外提供的统一webapi 接口,统一的异常处理,把正确的信息返回给调用者很重要。这样可以让接口开发人员,了解具体的原因所在,这样可以得到有效的错误处理。

  需要注意的是,webapi异常的状态码,尽量不要和业务状态码混淆。可以分为两个不同的字段,或者是状态码的规则不同。相关返回数据的格式,可以参考,前面的文章。

1、常规程序异常处理

  常规的程序异常,指的是webapi 接口程序在执行的时候出现的各种异常情况,可以使用异常筛选器捕获所有异常。

  1. API自定义错误过滤器属性:ApiExceptionAttribute

    /// 

    /// API自定义错误过滤器属性
    ///

 

    public class ApiExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        /// 

        /// 统一对调用异常信息进行处理,返回自定义的异常信息
        ///

 

        /// HTTP上下文对象
        public override void OnException(HttpActionExecutedContext context)
        {
            //自定义异常的处理
            if (context.Exception is NotImplementedException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotImplemented) 
                {
                    //封装处理异常信息,返回指定JSON对象
                    Content = new StringContent(JsonHelper.ToJson(new ErrorModel((int)HttpStatusCode.NotImplemented, 0, ex.Message)), Encoding.UTF8, "application/json"),
                    ReasonPhrase = "NotImplementedException"
                });
            }
            else if (context.Exception is TimeoutException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.RequestTimeout)
                {
                    //封装处理异常信息,返回指定JSON对象
                    Content = new StringContent(JsonHelper.ToJson(new ErrorModel((int)HttpStatusCode.RequestTimeout, 0, ex.Message)), Encoding.UTF8, "application/json"),
                    ReasonPhrase = "TimeoutException"
                });
            }
            //.....这里可以根据项目需要返回到客户端特定的状态码。如果找不到相应的异常,统一返回服务端错误500
            else
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    //封装处理异常信息,返回指定JSON对象
                    Content = new StringContent(JsonHelper.ToJson(new ErrorModel((int)HttpStatusCode.InternalServerError, 0, ex.Message)), Encoding.UTF8, "application/json"),
                    ReasonPhrase = "InternalServerErrorException"
                });
            }

            //base.OnException(context);

            //记录关键的异常信息
            //Debug.WriteLine(context.Exception);
        }
    }

 

  2. 定义好了错误过滤器,根据实际情况,在不同级别使用统一的异常处理机制。比如,接口action级别,控制器Controller级别或者是全局。

  我们目前的使用的是全局进行异常过滤。在ApiBase 增加异常过滤。

    [ApiAuth]
    [ApiExceptionHandling]
    public class ApiBase : ApiController

2、地址接口异常处理

  对于常规的异常,我们通过上面的处理方式,就可以很好进行拦截并处理了,如果接口异常是全局性的,如访问地不正确,或者调用的接口就不是有效的地址,这样的话,返回的信息就不会被上面的拦截器进行处理了。

如我们给一个无效的API调用路径,在浏览器中获得下面错误结果。

  由于上面结果就无法被我们的常规异常拦截器所捕获,因此不会输出经过封装好的异常信息。

  所以如果需要拦截,我们需要增加自己的消息代理处理,用来捕获这些特殊的异常信息。

 /// 

    /// API自定义错误消息处理委托类。
    /// 用于处理访问不到对应API地址的情况,对错误进行自定义操作。
    /// 

 


    public class CustomErrorMessageDelegatingHandler : DelegatingHandler
    {
        protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith((responseToCompleteTask) =>
            {
                HttpResponseMessage response = responseToCompleteTask.Result;
                HttpError error = null;
                if (response.TryGetContentValue(out error))
                {
                    //添加自定义错误处理
                    //error.Message = "Your Customized Error Message";
                }

                if (error != null)
                {
                    //获取抛出自定义异常,有拦截器统一解析
                    throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
                    {
                        //封装处理异常信息,返回指定JSON对象
                        Content = new StringContent(JsonHelper.ToJson(new ErrorModel(404, 0, error.Message)), Encoding.UTF8, "application/json"),
                        ReasonPhrase = "Exception"
                    });
                }
                else
                {
                    return response;
                }
            });
        }
    }

  同时,在WebApiConfig中,注册上相关处理

 public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
    
            ..............

            config.MessageHandlers.Add(new CustomErrorMessageDelegatingHandler());

  有了以上这两种异常处理,我们就可以统一我们的调用规则,并进行异常记录和显示了,非常方便。

3、总结

  首先,以上这两种异常处理,我们就可以统一我们的调用规则,但是对于WebApi里面异常的处理机制,可能还不够深入,但对于一般项目的异常处理基本够用。其他朋友,如果还有什么更好的方案,还望不吝赐教,感谢感谢!

  其次,我们目前使用的异常处理,参考于http://www.cnblogs.com/wuhuacong/p/4843422.html。

 

Web API系列(二)接口安全和参数校验

  以前简单介绍过web api 的设计,但是还是有很多朋友问我,如何合理的设计和实现web api。比如,接口安全,异常处理,统一数据返回等问题。所以有必要系统的总结总结 web api 的设计和实现。由于前面已经介绍过web api 的参数和返回格式的设计,《Web API系列(一)设计经验与总结》这次,就来讲讲接口安全。

 

  由于Web API是基于互联网的应用,因此安全性要远比在本地访问数据库的要严格的多,一般通用的做法,是采用几步来保证接口和数据安全:

  1.首先一个是基于CA证书的HTTPS进行数据传输,防止数据被窃听;

  2.然后是采用参数加密签名方式传递,对传递的参数,增加一个加密签名,在服务器端验证签名内容,防止被篡改;

  3.最后是对一般的接口访问,都需要使用用户身份的token进行校验,只要检查通过才允许访问数据。

 

  Web API接口的访问方式,大概可以分为几类:

  1)使用用户名密码。这种方式比较简单,可以有效识别用户的身份(如包括用户信息、密码、或者相关的接口权限等等)。验证成功后,返回相关的数据。

  2)使用安全签名。这种方式提交的数据,URL连接的签名参数是经过安全一定规则的加密的,服务器收到数据后也经过同样规则的安全加密,确认数据没有被中途篡改后,再进行数据修改处理。因此我们可以为不同客户端,如Web/APP/Winfrom等不同接入方式指定不同的加密秘钥,但是秘钥是双方约定的,并不在网络连接上传输,连接传输的一般是这个接入的AppID,服务器通过这个AppID来进行签名参数的加密对比。目前微信后台的回调处理机制,应该就是这么处理的。

  3)公开的接口调用,不需要传入用户令牌、或者对参数进行加密签名的,这种接口一般较少,只是提供一些很常规的数据显示而已。

   

 

  web api 安全校验

  使用用户名密码的实现方式比较简单,这里就不说明如何实现了。就讲一讲安全签名的实现。由于Web API的调用,都是一种无状态的调用方式,所有的接口请求,都要带安全签名。

  

 

  web api核心安全校验代码片断:

 public class QueryData
    {
        public QueryData()
        {

        }

        public QueryData(IEnumerable<KeyValuePair<string, string>> paramList)
        {
            // TODO: Complete member initialization
            try
            {
                if (paramList == null)
                {
                    throw new Exception("请求参数为空!");
                }

                foreach (var param in paramList)
                {
                    m_values[param.Key] = param.Value; //
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
        private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();

        /**
        * 设置某个字段的值
        * @param key 字段名
         * @param value 字段值
        */
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }

        /**
        * 根据字段名获取某个字段的值
        * @param key 字段名
         * @return key对应的字段值
        */
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }

        /**
         * 判断某个字段是否已设置
         * @param key 字段名
         * @return 若字段key已被设置,则返回true,否则返回false
         */
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }

        public string ToUrl()
        {
            string buff = "";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                if (pair.Value == null)
                {
                    throw new Exception("内部含有值为null的字段!");
                }

                if (pair.Key != "sign" && pair.Value.ToString() != "")
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }

        public string MakeSign(string appKey = "test")
        {
            //转url格式
            string str = ToUrl();
            //在string后加入API KEY
            str += "&key=" + appKey;
            //MD5加密
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
                sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }
      

        public bool CheckSign()
        {
            //如果没有设置签名,则跳过检测
            if (!IsSet("sign"))
            {
                throw new Exception("签名存在但不合法!");
            }
            //如果设置了签名但是签名为空,则抛异常
            else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
            {
                throw new Exception("签名存在但不合法!");
            }

            //获取接收到的签名
            string return_sign = GetValue("sign").ToString();

            //在本地计算新的签名
            string cal_sign = MakeSign();

            if (cal_sign == return_sign)
            {
                return true;
            }
            return false;
        }
    }

代码供大家参考和学习,正式的项目可以根据自己公司的需要去设计,后续也会开源相关的完整项目源代码。

 

RabbitMQ学习系列(六): RabbitMQ 高可用集群

前面讲过一些RabbitMQ的安装和用法,也说了说RabbitMQ在一般的业务场景下如何使用。不知道的可以看我前面的博客,http://www.cnblogs.com/zhangweizhong/category/855479.html

本来一直想写一个介绍RabbitMQ高可用的集群的文章。不过,后来发现园子里,有个已经RabbitMQ大牛写了,关于高可用集群的文章了。特别巧合的是,还是以前公司的同事。所以,这里就不啰嗦。直接引用过来吧。原文地址:http://www.cnblogs.com/flat_peach/archive/2013/04/07/3004008.html

RabbitMQ是用erlang开发的,集群非常方便,因为erlang天生就是一门分布式语言,但其本身并不支持负载均衡。

Rabbit模式大概分为以下三种:单一模式、普通模式、镜像模式

单一模式:最简单的情况,非集群模式。

没什么好说的。

普通模式:默认的集群模式。

对于Queue来说,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构。

当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。

所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。

该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。

如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,然后就没有然后了……

镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案

该模式解决了上述问题,其实质和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。

该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。

所以在对可靠性要求较高的场合中适用(后面会详细介绍这种模式,目前我们搭建的环境属于该模式)

 

了解集群中的基本概念:

RabbitMQ的集群节点包括内存节点、磁盘节点。顾名思义内存节点就是将所有数据放在内存,磁盘节点将数据放在磁盘。不过,如前文所述,如果在投递消息时,打开了消息的持久化,那么即使是内存节点,数据还是安全的放在磁盘。

一个rabbitmq集 群中可以共享 user,vhost,queue,exchange等,所有的数据和状态都是必须在所有节点上复制的,一个例外是,那些当前只属于创建它的节点的消息队列,尽管它们可见且可被所有节点读取。rabbitmq节点可以动态的加入到集群中,一个节点它可以加入到集群中,也可以从集群环集群会进行一个基本的负载均衡。

集群中有两种节点:


1 内存节点:只保存状态到内存(一个例外的情况是:持久的queue的持久内容将被保存到disk)


2 磁盘节点:保存状态到内存和磁盘。


内存节点虽然不写入磁盘,但是它执行比磁盘节点要好。集群中,只需要一个磁盘节点来保存状态 就足够了


如果集群中只有内存节点,那么不能停止它们,否则所有的状态,消息等都会丢失。

 思路:

那么具体如何实现RabbitMQ高可用,我们先搭建一个普通集群模式,在这个模式基础上再配置镜像模式实现高可用,Rabbit集群前增加一个反向代理,生产者、消费者通过反向代理访问RabbitMQ集群。

架构图如下:图片来自http://www.nsbeta.info

 
          上述图里是3个RabbitMQ运行在同一主机上,分别用不同的服务端口。当然我们的生产实际里,多个RabbitMQ肯定是运行在不同的物理服务器上,否则就失去了高可用的意义。

 

 

 集群模式配置

设计架构可以如下:在一个集群里,有4台机器,其中1台使用磁盘模式,另2台使用内存模式。2台内存模式的节点,无疑速度更快,因此客户端(consumer、producer)连接访问它们。而磁盘模式的节点,由于磁盘IO相对较慢,因此仅作数据备份使用,另外一台作为反向代理。

四台服务器hostname分别为:queue 、panyuntao1、panyuntao2、panyuntao3(ip:172.16.3.110)

配置RabbitMQ集群非常简单,只需要几个命令,配置步骤如下:

step1:queue、panyuntao1、panyuntao2做为RabbitMQ集群节点,分别安装RabbitMq-Server ,安装后分别启动RabbitMq-server

         启动命令  # Rabbit-Server start ,安装过程及启动命令参见:http://www.cnblogs.com/flat_peach/archive/2013/03/04/2943574.html

step2:在安装好的三台节点服务器中,分别修改/etc/hosts文件,指定queue、panyuntao1、panyuntao2的hosts,如:

172.16.3.32 queue

172.16.3.107 panyuntao1

172.16.3.108 panyuntao2

还有hostname文件也要正确,分别是queue、panyuntao1、panyuntao2,如果修改hostname建议安装rabbitmq前修改。

请注意RabbitMQ集群节点必须在同一个网段里,如果是跨广域网效果就差。

step3:设置每个节点Cookie

Rabbitmq的集群是依赖于erlang的集群来工作的,所以必须先构建起erlang的集群环境。Erlang的集群中各节点是通过一个magic cookie来实现的,这个cookie存放在 /var/lib/rabbitmq/.erlang.cookie 中,文件是400的权限。所以必须保证各节点cookie保持一致,否则节点之间就无法通信。
-r——–. 1 rabbitmq rabbitmq 20 3月 5 00:00 /var/lib/rabbitmq/.erlang.cookie
将其中一台节点上的.erlang.cookie值复制下来保存到其他节点上。或者使用scp的方法也可,但是要注意文件的权限和属主属组。
我们这里将queue中的cookie 复制到 panyuntao1、panyuntao2中,先修改下panyuntao1、panyuntao2中的.erlang.cookie权限
#chmod 777  /var/lib/rabbitmq/.erlang.cookie 
将queue的/var/lib/rabbitmq/.erlang.cookie这个文件,拷贝到panyuntao1、panyuntao2的同一位置(反过来亦可),该文件是集群节点进行通信的验证密钥,所有节点必须一致。拷完后重启下RabbitMQ。
复制好后别忘记还原.erlang.cookie的权限,否则可能会遇到错误
#chmod 400 /var/lib/rabbitmq/.erlang.cookie 
设置好cookie后先将三个节点的rabbitmq重启
# rabbitmqctl stop
# rabbitmq-server start
 
 
 step4:停止所有节点RabbitMq服务,然后使用detached参数独立运行,这步很关键,尤其增加节点停止节点后再次启动遇到无法启动都可以参照这个顺序
     queue# rabbitmqctl stop
     panyuntao1# rabbitmqctl stop
     panyuntao2# rabbitmqctl stop
 
     queue# rabbitmq-server -detached
     panyuntao1# rabbitmq-server -detached
     panyuntao2# rabbitmq-server -detached
 
     分别查看下每个节点
 
     queue# rabbitmqctl cluster_status
          Cluster status of node rabbit@queue …

[{nodes,[{disc,[rabbit@queue]}]},

{running_nodes,[rabbit@queue]},


{partitions,[]}]


…done.

 
     panyuntao1# rabbitmqctl cluster_status

          Cluster status of node rabbit@panyuntao1…

[{nodes,[{disc,[rabbit@panyuntao1]}]},

{running_nodes,[rabbit@panyuntao1]},

{partitions,[]}]

…done.

 

panyuntao2# rabbitmqctl cluster_status

          Cluster status of node rabbit@panyuntao2

[{nodes,[{disc,[rabbit@panyuntao2]}]},

{running_nodes,[rabbit@panyuntao2]},

{partitions,[]}]

…done.

 
 
step4:将panyuntao1、panyuntao2作为内存节点与queue连接起来,在panyuntao1上,执行如下命令:
          panyuntao1# rabbitmqctl stop_app

panyuntao1# rabbitmqctl join_cluster –ram rabbit@queue   

panyuntao1# rabbitmqctl start_app

 

panyuntao2# rabbitmqctl stop_app
panyuntao2# rabbitmqctl join_cluster –ram rabbit@queue   (上方已经将panyuntao1与queue连接,也可以直接将panyuntao2与panyuntao1连接,同样而已加入集群中)
 
panyuntao2# rabbitmqctl start_app
上述命令先停掉rabbitmq应用,然后调用cluster命令,将panyuntao1连接到,使两者成为一个集群,最后重启rabbitmq应用。在这个cluster命令下,panyuntao1、panyuntao2是内存节点,queue是磁盘节点(RabbitMQ启动后,默认是磁盘节点)。
queue 如果要使panyuntao1或panyuntao2在集群里也是磁盘节点,join_cluster 命令去掉–ram参数即可
      #rabbitmqctl join_cluster rabbit@queue   

只要在节点列表里包含了自己,它就成为一个磁盘节点。在RabbitMQ集群里,必须至少有一个磁盘节点存在。

step5:在queue、panyuntao1、panyuntao2上,运行cluster_status命令查看集群状态:

[root@queue ~]# rabbitmqctl cluster_status

Cluster status of node rabbit@queue …


[{nodes,[{disc,[rabbit@queue]},{ram,[rabbit@panyuntao2,rabbit@panyuntao1]}]},


{running_nodes,[rabbit@panyuntao2,rabbit@panyuntao1,rabbit@queue]},


{partitions,[]}]


…done.
[root@panyuntao1 rabbitmq]# rabbitmqctl cluster_status

Cluster status of node rabbit@panyuntao1 …


[{nodes,[{disc,[rabbit@queue]},{ram,[rabbit@panyuntao2,rabbit@panyuntao1]}]},


{running_nodes,[rabbit@panyuntao2,rabbit@queue,rabbit@panyuntao1]},


{partitions,[]}]


…done.
 
[root@panyuntao2 rabbitmq]# rabbitmqctl cluster_status

Cluster status of node rabbit@panyuntao2 …


[{nodes,[{disc,[rabbit@queue]},{ram,[rabbit@panyuntao2,rabbit@panyuntao1]}]},


{running_nodes,[rabbit@panyuntao1,rabbit@queue,rabbit@panyuntao2]},


{partitions,[]}]


…done.
 
这时我们可以看到每个节点的集群信息,分别有两个内存节点一个磁盘节点
 
 
 
 
step6:往任意一台集群节点里写入消息队列,会复制到另一个节点上,我们看到两个节点的消息队列数一致:(如何发送消息参见:http://www.cnblogs.com/flat_peach/archive/2013/03/04/2943574.html
 

root@panyuntao2 :~# rabbitmqctl list_queues -p hrsystem

Listing queues …

test_queue 10000


…done.

root@panyuntao1 :~# rabbitmqctl list_queues -p hrsystem
Listing queues …

test_queue 10000


…done.
 
root@queue:~# rabbitmqctl list_queues -p hrsystem
Listing queues …

test_queue 10000


…done.
 
-p参数为vhost名称
 
          这样RabbitMQ集群就正常工作了,
 
       这种模式更适合非持久化队列,只有该队列是非持久的,客户端才能重新连接到集群里的其他节点,并重新创建队列。假如该队列是持久化的,那么唯一办法是将故障节点恢复起来。  
      为什么RabbitMQ不将队列复制到集群里每个节点呢?这与它的集群的设计本意相冲突,集群的设计目的就是增加更多节点时,能线性的增加性能(CPU、内存)和容量(内存、磁盘)。理由如下:

1. storage space: If every cluster node had a full copy of every queue, adding nodes wouldn’t give you more storage capacity. For example, if one node could store 1GB of messages, adding two more nodes would simply give you two more copies of the same 1GB of messages.

2. performance: Publishing messages would require replicating those messages to every cluster node. For durable messages that would require triggering disk activity on all nodes for every message. Your network and disk load would increase every time you added a node, keeping the performance of the cluster the same (or possibly worse).

当然RabbitMQ新版本集群也支持队列复制(有个选项可以配置)。比如在有五个节点的集群里,可以指定某个队列的内容在2个节点上进行存储,从而在性能与高可用性之间取得一个平衡。

  镜像模式配置
上面配置RabbitMQ默认集群模式,但并不保证队列的高可用性,尽管交换机、绑定这些可以复制到集群里的任何一个节点,但是队列内容不会复制,虽然该模式解决一部分节点压力,但队列节点宕机直接导致该队列无法使用,只能等待重启,所以要想在队列节点宕机或故障也能正常使用,就要复制队列内容到集群里的每个节点,需要创建镜像队列。
我们看看如何镜像模式来解决复制的问题,从而提高可用性 
 
         
 
     step1:增加负载均衡器
 

关于负载均衡器,商业的比如F5的BIG-IP,Radware的AppDirector,是硬件架构的产品,可以实现很高的处理能力。但这些产品昂贵的价格会让人止步,所以我们还有软件负载均衡方案。互联网公司常用的软件LB一般有LVS、HAProxy、Nginx等。LVS是一个内核层的产品,主要在第四层负责数据包转发,使用较复杂。HAProxy和Nginx是应用层的产品,但Nginx主要用于处理HTTP,所以这里选择HAProxy作为RabbitMQ前端的LB。

HAProxy的安装使用非常简单,在Centos下直接yum install haproxy,然后更改/etc/haproxy/haproxy.cfg 文件即可,文件内容大概如下:

   #———————————————————————

 

defaults

mode                    http


log                     global


option                  httplog


option                  dontlognull


option http-server-close


option forwardfor       except 127.0.0.0/8


option                  redispatch


retries                 3


timeout http-request    10s


timeout queue           1m


timeout connect         10s


timeout client          1m


timeout server          1m


timeout http-keep-alive 10s


timeout check           10s


maxconn                 3000

 
    listen rabbitmq_cluster 0.0.0.0:5672
    mode tcp
    balance roundrobin
    server   rqslave1 172.16.3.107:5672 check inter 2000 rise 2 fall 3   
    server   rqslave2 172.16.3.108:5672 check inter 2000 rise 2 fall 3 
#  server   rqmaster 172.16.3.32:5672 check inter 2000 rise 2 fall 3  
#———————————————————————
 
负载均衡器会监听5672端口,轮询我们的两个内存节点172.16.3.107、172.16.3.108的5672端口,172.16.3.32为磁盘节点,只做备份不提供给生产者、消费者使用,当然如果我们服务器资源充足情况也可以配置多个磁盘节点
,这样磁盘节点除了故障也不会影响,除非同时出故障。
 
step2:配置策略
 
使用Rabbit镜像功能,需要基于rabbitmq策略来实现,政策是用来控制和修改群集范围的某个vhost队列行为和Exchange行为
 
在cluster中任意节点启用策略,策略会自动同步到集群节点

# rabbitmqctl set_policy -p hrsystem ha-allqueue"^" '{"ha-mode":"all"}'

这行命令在vhost名称为hrsystem创建了一个策略,策略名称为ha-allqueue,策略模式为 all 即复制到所有节点,包含新增节点,
策略正则表达式为 “^” 表示所有匹配所有队列名称。
例如rabbitmqctl set_policy -p hrsystem ha-allqueue "^message" '{"ha-mode":"all"}'
注意:"^message” 这个规则要根据自己修改,这个是指同步”message”开头的队列名称,我们配置时使用的应用于所有队列,所以表达式为”^”
官方set_policy说明参见
set_policy [-p vhostpath] {name} {pattern} {definition} [priority]
 
ha-mode:
 
ha-mode ha-params Result
all (absent) Queue is mirrored across all nodes in the cluster. When a new node is added to the cluster, the queue will be mirrored to that node.
exactly count Queue is mirrored to count nodes in the cluster. If there are less than count nodes in the cluster, the queue is mirrored to all nodes. If there are more than countnodes in the cluster, and a node containing a mirror goes down, then a new mirror will not be created on another node. (This is to prevent queues migrating across a cluster as it is brought down.)
nodes node names Queue is mirrored to the nodes listed in node names. If any of those node names are not a part of the cluster, this does not constitute an error. If none of the nodes in the list are online at the time when the queue is declared then the queue will be created on the node that the declaring client is connected to.
 
step3:
创建队列时需要指定ha 参数,如果不指定x-ha-prolicy 的话将无法复制
 
下面C#代码片段
 using ( var bus = RabbitHutch.CreateBus(ConfigurationManager .ConnectionStrings[“RabbitMQ”].ToString()))
            {
                bus.Subscribe< TestMessage>(“word_subscriber” , message => RunTable(message),x=>x.WithArgument(“x-ha-policy” , “all”));
                Console.WriteLine(“Subscription Started. Hit any key quit” );
                Console.ReadKey();
            }
 
step4:
客户端使用负载服务器172.16.3.110 (panyuntao3)发送消息,队列会被复制到所有节点,当然策略也可以配置制定某几个节点,这时任何节点故障 、或者重启将不会影响我们正常使用某个队列
 到这里我们完成了高可用配置(所有节点都宕机那没有办法了)。
 
使用rabbitmq管理端可以看到集群镜像模式中对列状态

 

 

参考:

http://www.rabbitmq.com/clustering.html

http://www.rabbitmq.com/ha.html

http://www.rabbitmq.com/parameters.html#policies

http://www.nsbeta.info/archives/555

http://blog.csdn.net/linvo/article/details/7793706