线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转

2022-12-03,,,,

线程安全使用(四)

 

这是时隔多年第四篇,主要是因为身在东软受内网限制,好多文章就只好发到东软内部网站,懒的发到外面,现在一点点把在东软写的文章给转移出来。

这里主要讲解下CancellationTokenSource,CancellationTokenSource是用于取消线程,具体使用起来有点另类:首先定义实体,然后将其下的属性ToKen传递给线程,当需要取消线程时,调用下Cancel()方法。例子我依然采用了MSDN的例子,但我做了一些修改,这个例子虽然看起来挺复杂,但还是记录了许多内容。

由于不好理解,我就粗略讲解下:

Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), 上面是创建任务,创建10个线程,并且线程中增加了判断,如果随即数等于0就取消该线程。

再介绍下factory.ContinueWhenAll,他包含两个参数Task[] tasks,
Action<Task[]> continuationAction。MSDN的解释是:

ContinueWhenAll 方法执行 continuationAction 委托,在 tasks 数组的所有任务完成后,无论它们的完成状态。

MSDN上就这个翻译的还不错,其他的基本可以无视了。。。

继续看代码,代码中增加了try catch,这是为什么呢,看下ContinueWhenAll英文解释:

The exception that is thrown when the tasks array is empty

这里千万不能看中文解释,不然你会凌乱的。看了英文解释就懂了,让任务为空就抛异常。

那么为什么任务为空呢,因为任务已经被取消了啊,所以为空了。具体代码如下。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
    public static void Main()
    {
        // Define the cancellation token.
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken token = source.Token;
        Random rnd = new Random();
        Object lockObj = new Object();
        List<Task<int[]>> tasks = new List<Task<int[]>>();
        TaskFactory factory = new TaskFactory(token);
        for (int taskCtr = 0; taskCtr <= 10; taskCtr++)
        {
            int iteration = taskCtr + 1;
            tasks.Add(factory.StartNew(() =>
            {
                int value;
                int[] values = new int[10];
                for (int ctr = 1; ctr <= 10; ctr++)
                {
                    lock (lockObj)
                    {
                        value = rnd.Next(0, 101);
                    }
                    if (value == 0)
                    {
                        source.Cancel();
                        Console.WriteLine("Cancelling at task {0}", iteration);
                        break;
                    }

                    values[ctr - 1] = value;
                }
                Console.WriteLine("NO Cancel at task {0}", iteration);
                return values;
            }, token));
        }
        try
        {
            Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(),
                                                         (results) =>
                                                         {
                                                             Console.WriteLine("Calculating overall mean...");
                                                             long sum = 0;
                                                             int n = 0;
                                                             foreach (var t in results)
                                                             {
                                                                 foreach (var r in t.Result)
                                                                 {
                                                                     sum += r;
                                                                     n++;
                                                                 }
                                                             }
                                                             return sum / (double)n;
                                                         }, token);
            Console.WriteLine("The mean is {0}.", fTask.Result);
        }
        catch (AggregateException ae)
        {
            foreach (Exception e in ae.InnerExceptions)
            {
                if (e is TaskCanceledException)
                    Console.WriteLine("Unable to compute mean: {0}",
                                      ((TaskCanceledException)e).Message);
                else
                    Console.WriteLine("Exception: " + e.GetType().Name);
            }
        }
        Console.ReadLine();

    }
}

显示结果图片,每次的结果都不一样的,所以我也是运行了好几次,看这个结果会发现一件事,线程只执行了两个,即当线程2中调用Cancel后,其他线程也被取消了。

[.NET] 简单接入微信公众号开发:实现自动回复

 

简单接入微信公众号开发:实现自动回复

一、前提

  先申请微信公众号的授权,找到或配置几个关键的信息(开发者ID、开发者密码、IP白名单、令牌和消息加解密密钥等)。

二、基本配置信息解读

  开发者ID:固定的;

  开发者密码:自己扫一下就可以看到;

  IP白名单:设置自己配置服务器的地址;

  服务器地址(URL):稍后详解;

  令牌:随便写,按规则;

  消息加解密密钥:随便写,或者随机生成;

三、配置服务器地址(URL)

  服务器地址(URL)应该怎么配置呢?图片上的配置的地址是:http://www.nidie.com.cn/wechat ,那么它对应的控制器应该是怎么样子的呢?

  在这里,我使用了第三方的包,需要通过 Nuget 来安装:

  <package id="Senparc.Weixin" version="4.22.1" targetFramework="net471" />
  <package id="Senparc.Weixin.MP" version="14.14.0" targetFramework="net471" />
  <package id="Senparc.Weixin.MP.MVC" version="5.4.5" targetFramework="net471" />

  接下来新建一个 WeChatController.cs:

using System.Threading.Tasks;
using System.Web.Mvc;
using Senparc.Weixin.MP;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MvcExtension;
using Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers;
using Wen.MvcDemo.Infrastructure.Configuration;

namespace Wen.MvcDemo.Web.Controllers
{
    /// <summary>
    /// 微信
    /// </summary>
    public class WeChatController : Controller
    {
        #region private static field

        private static readonly string AppId = ApplicationSettingsFactory.GetApplicationSettings().WeChatAppId;

        private static readonly string EncodingAesKey = ApplicationSettingsFactory.GetApplicationSettings().WeChatEncodingAesKey;

        private static readonly string Token = ApplicationSettingsFactory.GetApplicationSettings().WeChatToken;

        #endregion private static field

        /// <summary>
        /// 微信后台验证地址
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="timestamp"></param>
        /// <param name="nonce"></param>
        /// <param name="echostr"></param>
        /// <returns></returns>
        [HttpGet]
        public ActionResult Index(string signature, string timestamp, string nonce, string echostr)
        {
            return Content(echostr);
        }

        /// <summary>
        /// 处理用户发送消息后
        /// </summary>
        /// <param name="postModel"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> Index(PostModel postModel)
        {
            //校验签名
            if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
                return new WeixinResult("参数错误!");

            postModel.AppId = AppId;
            postModel.EncodingAESKey = EncodingAesKey;
            postModel.Token = Token;

            //接收消息,自定义 MessageHandler,对微信请求进行处理
            var messageHandler = new CustomMessageHandler(Request.InputStream, postModel);

            //执行微信处理过程
            await messageHandler.ExecuteAsync();
            //返回处理结果
            return new FixWeixinBugWeixinResult(messageHandler);
        }
    }
}

  

  代码分析:

  里面主要包含了三个静态字段和两个 Index 方法。

  其中静态字段对应的就是基本配置信息里面对应的几个参数,平常都是写入配置文件中来进行读取。

  其中一个标识特性为 HttpGet 的 Index 方法,它是用来通过服务器地址(URL)验证的,当你成功部署到你的服务器后,再点击提交认证就可以通过了。注意的是,需要将代码先提交到服务器,再进行提交确认。

  可能你看到该方法好像只返回 return Content(echostr); 这么简单的代码感到质疑:这能行吗?“我”记得官方文档好像要调用很复杂的方法进行校验才行的!?

  上图就是官方文档,但是我只关心通过配置提交认证,也就是我用红圈着色的部分,即原样返回 echostr 参数内容即可。

  第二个是实现 Post 请求的 Index 方法,在这里我进行了签名校验(也就是上图文档的校验逻辑),因为使用了第三方库,我们知道传哪些参数过去就可以了,签名通过后就是读取请求信息并进行后续处理的步骤了。

四、请求处理

  在上面的处理请求信息的代码中,我自定义了一个类 CustomMessageHandler 来处理消息。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Senparc.Weixin.MP.AppStore;
using Senparc.Weixin.MP.AppStore.Utility;
using Senparc.Weixin.MP.Entities;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MessageHandlers;

namespace Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers
{
    /// <summary>
    /// 自定义消息处理
    /// </summary>
    public class CustomMessageHandler : MessageHandler<CustomMessageContext>
    {
        public CustomMessageHandler(Stream inputStream, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(inputStream, postModel, maxRecordCount, developerInfo)
        {
        }

        public CustomMessageHandler(XDocument requestDocument, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(requestDocument, postModel, maxRecordCount, developerInfo)
        {
        }

        public CustomMessageHandler(RequestMessageBase requestMessageBase, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(requestMessageBase, postModel, maxRecordCount, developerInfo)
        {
        }

        /// <summary>
        /// 默认
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
        {
            var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型
            responseMessage.Content = $"您好,目前使用的微信公众号仍处于开发阶段,现已接入了【图灵机器人】,您可以尝试和他(她)交流。";
            return responseMessage;
        }
    }
}

  

  CustomMessageHandler 类继承了 MessageHandler 类,然后重写了 DefaultResponseMessage() 方法,返回固定的文本值。base.CreateResponseMessage<T>() 方法可以返回多种不同类型的结果值,如:

ResponseMessageText - 对应文本消息

ResponseMessageNews - 对应图文消息

ResponseMessageMusic - 对应音乐消息

ResponseMessageXXX - 其他类型以此类推

  

  上述方法只是一种默认的消息处理,我们也可以专门针对不同的请求类型做出不同的回应,比如重写 OnTextRequest(),其它重载需要自己观察基类成员:

        /// <summary>
        /// 文本请求
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
        {
            var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
            responseMessage.Content = $"您刚才发送的文字信息是:{requestMessage.Content}。";  //\r\n用于换行,requestMessage.Content即用户发过来的文字内容
            return responseMessage;
        }

  因为在继承 MessageHandler<T> 类的同时,我创建了一个 CustomMessageContext 自定义消息上下文的类,该类内容如下,并没有包含其它方法,直接继承 MessageContext<IRequestMessageBase, IResponseMessageBase> 即可:

using Senparc.Weixin.Context;
using Senparc.Weixin.MP.Entities;

namespace Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers
{
    /// <summary>
    /// 自定义消息上下文
    /// </summary>
    public class CustomMessageContext : MessageContext<IRequestMessageBase, IResponseMessageBase>
    {
    }
}

  

  这样,就完成了所有代码的编写,现在我们再次把代码部署好之后就可以开始进行测试了。

  因为我除了部署自己的站点之外,还接入了【图灵机器人】回复,所以你看到了两条信息。

[C#]C#中字符串的操作

 

1.Replace(替换字符):
public string Replace(char oldChar,char newChar);在对象中寻找oldChar,如果寻找到,就用newChar将oldChar替换掉。
如:
            string st = "abcdef";
            string newstring = st.Replace('a', 'x');
            Console.WriteLine(newstring);   //即:xbcdef

public string Replace(string oldString,string newString);在对象中寻找oldString,如果寻找到,就用newString将oldString替换掉。
如:
            string st = "abcdef";
            string newstring = st.Replace("abc", "xyz");
            Console.WriteLine(newstring);   //即:xyzdef

2.Remove(删除字符):
public string Remove(int startIndex);从startIndex位置开始,删除此位置后所有的字符(包括当前位置所指定的字符)。
如:  
     string st = "abcdef";
            string newstring = st.Remove(4);
            Console.WriteLine(newstring);  //即:abcd

public string Remove(int startIndex,int count);从startIndex位置开始,删除count个字符。
如:  
     string st = "abcdef";
            string newstring = st.Remove(4,1);
            Console.WriteLine(newstring);  //即:abcdf

3.Substring(字符串提取):
public string Substring(int startIndex);从startIndex位置开始,提取此位置后所有的字符(包括当前位置所指定的字符)。
如:  
     string st = "abcdef";
            string newstring = st.Substring(2);
            Console.WriteLine(newstring);  //即:cdef

public string Substring(int startIndex,int count);从startIndex位置开始,提取count个字符。
如:  
     string st = "abcdef";
            string newstring = st.Substring(2,2);
            Console.WriteLine(newstring);  //即:cd

4.Trim(清空空格):
public string Trim ():将字符串对象包含的字符串两边的空格去掉后返回。
public string Trim ( params char[] trimChars ):从此实例的开始和末尾移除数组中指定的一组字符的所有匹配项。
如:
     string st ="abcdef";
     string newstring = st.Trim(new char[] {'a'});//寻找st字符串中开始与末尾是否有与'a'匹配,如有,将其移除。
     Console.WriteLine(newstring); //即:bcdef
注:如果字符串为"aaaabcdef",返回依然为bcdef。当移除第一个a时,开始依然为a,继续移除,直到没有。
public string TrimEnd ( params char[] trimChars ):对此实例末尾与指定字符进行匹配,true则移除
public string TrimStart ( params char[] trimChars ):对此实例开始与指定字符进行匹配,true则移除

5.ToLower(转换大小写)

public string ToLower():将字符串对象包含的字符串中的大写全部转换为小写。

6.IndexOf(获取指定的字符串的开始索引)
public int IndexOf (sring field):在此实例中寻找field,如果寻找到,返回开始索引,反之,返回-1。
如:
       string st = "abcdef";
            int num=st.IndexOf("bcd");
            Console.WriteLine(num);  //即:1

7.Equals(是否相等)
public bool Equals (string value):比较调用方法的字符串对象包含字符串和参数给出的对象是否相同,如相同,就返回true,反之,返回false。
如:        string a = "abcdef";
            bool b = a.Equals("bcdef");
            Console.WriteLine(b);//即:false

public bool Equals ( string value, StringComparison comparisonType ):比较调用方法的字符串对象包含字符串和参数给出的对象是否在不区分大小写的情况下相同,如相同,就返回true,反之,返回false,第二个参数将指定区域性、大小写以及比较所用的排序规则.
如:
       string a = "ABCDEF";
            bool b = a.Equals("abcdef",StringComparison.CurrentCultureIgnoreCase);
            Console.WriteLine(b);//即:true

8.Split(拆分)
public string[] Split ( params char[] separator ):根据separator 指定的没有字符分隔此实例中子字符串成为Unicode字符数组, separator可以是不包含分隔符的空数组或空引用。
public string[] Split ( char[] separator, int count ):参数count 指定要返回的子字符串的最大数量。 
如:
            string st = "语文|数学|英语|物理";
            string[] split = st.Split(new char[]{'|'},2);
            for (int i = 0; i < split.Length; i++)
            {
                Console.WriteLine(split[i]);
            }
注:count不填则全部拆分

public enum StringSplitOptions 
成员名称            说明
None                返回值包括含有空字符串的数组元素
RemoveEmptyEntries  返回值不包括含有空字符串的数组元素

如:
            string st = "语文|数学||英语|物理";
            string[] split = st.Split(new char[]{'|'},StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < split.Length; i++)
            {
                Console.WriteLine(split[i]);
            }
将StringSplitOptions枚举和Split()方法联系起来:
1.  public string[] Split ( char[] separator, StringSplitOptions options ):options指定StringSplitOptions枚举的RemoveEmptyEntries以省略返回的数组中的空数组元素,或指定StringSplitOptions枚举的None以包含返回的数组中的空数组元
2.  public string[] Split ( char[] separator, int count, StringSplitOptions options ) 
3.  public string[] Split ( string[] separator, StringSplitOptions options ) 
4.  public string[] Split ( string[] separator, int count, StringSplitOptions options )

9.Contains(判断是否存在)
public bool Contains(string text):如果字符串中出现text,则返回true,反之false,如果text为("")也返回true。
如:
 string st="语文数学英语";
 bool b=st.Contains("语文");
 Console.WriteLine(b);//true

10.EndsWith,StartsWith(判断字符串的开始或结束)
public bool EndsWith ( string value ):判断对象包含字符串是否以value指定的字符串结束,是则为 true;否则为 false。 
public bool EndsWith ( string value, StringComparison comparisonType ):第二个参数设置比较时区域、大小写和排序规则。
public bool StartsWith ( string value ):判断对象包含字符串是否以value指定的字符串开始,是则为 true;否则为 false。 
public bool StartsWith ( string value, StringComparison comparisonType ) :第二个参数设置比较时区域、大小写和排序规则。
如:
 string st="语文数学英语abc";
 bool b=st.EndsWith("英语ABC",StringComparison.CurrentCultureIgnoreCase);//第二个参数忽略大小比较。
 Console.WriteLine(b);//true

11.Insert(字符串插入)
public string Insert ( int startIndex, string value ):在指定的字符串下标为startIndex前插入字符串value。返回插入后的值。
如:
 string st="语文数学英语abc";
 string newst=st.Insert(6,"物理");//注:在指定索引“前”插入。
 Console.WriteLine(newst);//即:语文数学英语物理abc

 
 

自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化

 

二进制序列化可以方便快捷的将对象进行持久化或者网络传输,并且体积小、性能高,应用面甚至还要高于json的序列化;开始之前,先来看看dotcore/dotne自带的二进制序列化:C#中对象序列化和反序列化一般是通过BinaryFormatter类来实现的二进制序列化、反序列化的。

BinaryFormatter序列化:

1 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
2
3 System.IO.MemoryStream memStream = new System.IO.MemoryStream();
4
5 serializer.Serialize(memStream, request);

BinaryFormatter反序列化:

 1  memStream.Position=0;
 2
 3  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =
 4
 5  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 6
 7  object newobj = deserializer.Deserialize(memStream);
 8
 9  memStream.Close();
10
11  return newobj;

用着多了就发现BinaryFormatter有很多地方不妥,下面就来数数这个序列化的“三宗罪”:

1.类名上面要加上[Serializable],不加不给序列化;正常的用法应该是序列化一个对象,不需的地方加上NonSerialized才合理吧;

2.序列化byte[]结果非常大,使用System.Text.Encoding.UTF8.GetString(bytes)查看下,发现里面有一大堆的元数据;对比看看google的protobuf,pb为什么在网络上应用的越来越多,这和他本身序列化完后体积小有着绝大部门的原因;

3.序列化对象需要完全一致,连类的命名空间都要相同,这点对于分面式开发的应用来说也是不可接受的;

既然BinaryFormatter不好用,那就只能动手自行实现一个解决上述问题的二进制序列化方案;首先去掉[Serializable]这个标签,接着主要是分析对象,并定义对象序列化后的数据结构;这里的想法是按长度加内容的方式来定义,举个例子:使用int作为长度,来保存一个int值,序列化完应该是:4,0,0,0,1,0,0,0这样的一组bytes,同理可以将int、short、long、float、double、datetime、enum、array、string、class、generic等按照这个格式进行序列化,这里主要使用的是BitConverter、反射等来实现序列化与反序列化;

序列化实现如下:

  1         public static byte[] Serialize(object param)
  2         {
  3             List<byte> datas = new List<byte>();
  4
  5             var len = 0;
  6
  7             byte[] data = null;
  8
  9             if (param == null)
 10             {
 11                 len = 0;
 12             }
 13             else
 14             {
 15                 if (param is string)
 16                 {
 17                     data = Encoding.UTF8.GetBytes((string)param);
 18                 }
 19                 else if (param is byte)
 20                 {
 21                     data = new byte[] { (byte)param };
 22                 }
 23                 else if (param is bool)
 24                 {
 25                     data = BitConverter.GetBytes((bool)param);
 26                 }
 27                 else if (param is short)
 28                 {
 29                     data = BitConverter.GetBytes((short)param);
 30                 }
 31                 else if (param is int)
 32                 {
 33                     data = BitConverter.GetBytes((int)param);
 34                 }
 35                 else if (param is long)
 36                 {
 37                     data = BitConverter.GetBytes((long)param);
 38                 }
 39                 else if (param is float)
 40                 {
 41                     data = BitConverter.GetBytes((float)param);
 42                 }
 43                 else if (param is double)
 44                 {
 45                     data = BitConverter.GetBytes((double)param);
 46                 }
 47                 else if (param is DateTime)
 48                 {
 49                     var str = "wl" + ((DateTime)param).Ticks;
 50                     data = Encoding.UTF8.GetBytes(str);
 51                 }
 52                 else if (param is Enum)
 53                 {
 54                     var enumValType = Enum.GetUnderlyingType(param.GetType());
 55
 56                     if (enumValType == typeof(byte))
 57                     {
 58                         data = new byte[] { (byte)param };
 59                     }
 60                     else if (enumValType == typeof(short))
 61                     {
 62                         data = BitConverter.GetBytes((Int16)param);
 63                     }
 64                     else if (enumValType == typeof(int))
 65                     {
 66                         data = BitConverter.GetBytes((Int32)param);
 67                     }
 68                     else
 69                     {
 70                         data = BitConverter.GetBytes((Int64)param);
 71                     }
 72                 }
 73                 else if (param is byte[])
 74                 {
 75                     data = (byte[])param;
 76                 }
 77                 else
 78                 {
 79                     var type = param.GetType();
 80
 81
 82                     if (type.IsGenericType || type.IsArray)
 83                     {
 84                         if (TypeHelper.DicTypeStrs.Contains(type.Name))
 85                             data = SerializeDic((System.Collections.IDictionary)param);
 86                         else if (TypeHelper.ListTypeStrs.Contains(type.Name) || type.IsArray)
 87                             data = SerializeList((System.Collections.IEnumerable)param);
 88                         else
 89                             data = SerializeClass(param, type);
 90                     }
 91                     else if (type.IsClass)
 92                     {
 93                         data = SerializeClass(param, type);
 94                     }
 95
 96                 }
 97                 if (data != null)
 98                     len = data.Length;
 99             }
100             datas.AddRange(BitConverter.GetBytes(len));
101             if (len > 0)
102             {
103                 datas.AddRange(data);
104             }
105             return datas.Count == 0 ? null : datas.ToArray();
106         }

反序列化实现如下:

  1         public static object Deserialize(Type type, byte[] datas, ref int offset)
  2         {
  3             dynamic obj = null;
  4
  5             var len = 0;
  6
  7             byte[] data = null;
  8
  9             len = BitConverter.ToInt32(datas, offset);
 10             offset += 4;
 11             if (len > 0)
 12             {
 13                 data = new byte[len];
 14                 Buffer.BlockCopy(datas, offset, data, 0, len);
 15                 offset += len;
 16
 17                 if (type == typeof(string))
 18                 {
 19                     obj = Encoding.UTF8.GetString(data);
 20                 }
 21                 else if (type == typeof(byte))
 22                 {
 23                     obj = (data);
 24                 }
 25                 else if (type == typeof(bool))
 26                 {
 27                     obj = (BitConverter.ToBoolean(data, 0));
 28                 }
 29                 else if (type == typeof(short))
 30                 {
 31                     obj = (BitConverter.ToInt16(data, 0));
 32                 }
 33                 else if (type == typeof(int))
 34                 {
 35                     obj = (BitConverter.ToInt32(data, 0));
 36                 }
 37                 else if (type == typeof(long))
 38                 {
 39                     obj = (BitConverter.ToInt64(data, 0));
 40                 }
 41                 else if (type == typeof(float))
 42                 {
 43                     obj = (BitConverter.ToSingle(data, 0));
 44                 }
 45                 else if (type == typeof(double))
 46                 {
 47                     obj = (BitConverter.ToDouble(data, 0));
 48                 }
 49                 else if (type == typeof(decimal))
 50                 {
 51                     obj = (BitConverter.ToDouble(data, 0));
 52                 }
 53                 else if (type == typeof(DateTime))
 54                 {
 55                     var dstr = Encoding.UTF8.GetString(data);
 56                     var ticks = long.Parse(dstr.Substring(2));
 57                     obj = (new DateTime(ticks));
 58                 }
 59                 else if (type.BaseType == typeof(Enum))
 60                 {
 61                     var numType = Enum.GetUnderlyingType(type);
 62
 63                     if (numType == typeof(byte))
 64                     {
 65                         obj = Enum.ToObject(type, data[0]);
 66                     }
 67                     else if (numType == typeof(short))
 68                     {
 69                         obj = Enum.ToObject(type, BitConverter.ToInt16(data, 0));
 70                     }
 71                     else if (numType == typeof(int))
 72                     {
 73                         obj = Enum.ToObject(type, BitConverter.ToInt32(data, 0));
 74                     }
 75                     else
 76                     {
 77                         obj = Enum.ToObject(type, BitConverter.ToInt64(data, 0));
 78                     }
 79                 }
 80                 else if (type == typeof(byte[]))
 81                 {
 82                     obj = (byte[])data;
 83                 }
 84                 else if (type.IsGenericType)
 85                 {
 86                     if (TypeHelper.ListTypeStrs.Contains(type.Name))
 87                     {
 88                         obj = DeserializeList(type, data);
 89                     }
 90                     else if (TypeHelper.DicTypeStrs.Contains(type.Name))
 91                     {
 92                         obj = DeserializeDic(type, data);
 93                     }
 94                     else
 95                     {
 96                         obj = DeserializeClass(type, data);
 97                     }
 98                 }
 99                 else if (type.IsClass)
100                 {
101                     obj = DeserializeClass(type, data);
102                 }
103                 else if (type.IsArray)
104                 {
105                     obj = DeserializeArray(type, data);
106                 }
107                 else
108                 {
109                     throw new RPCPamarsException("ParamsSerializeUtil.Deserialize 未定义的类型:" + type.ToString());
110                 }
111
112             }
113             return obj;
114         }

其他详细的代码可以查看ParamsSerializeUtil.cs

功能基本实现了,下面对比一下10000次的实体序列化与反序列化测试结果:

实体代码:

 1             var groupInfo = new GroupInfo()
 2             {
 3                 GroupID = 1,
 4                 IsTemporary = false,
 5                 Name = "yswenli group",
 6                 Created = DateTimeHelper.Now,
 7                 Creator = new UserInfo()
 8                 {
 9
10                     ID = 1,
11                     Birthday = DateTimeHelper.Now.AddYears(-100),
12                     UserName = "yswenli"
13                 },
14                 Users = new System.Collections.Generic.List<UserInfo>()
15                 {
16                     new UserInfo()
17                     {
18
19                         ID = 1,
20                         Birthday = DateTimeHelper.Now.AddYears(-100),
21                         UserName = "yswenli"
22                     }
23                 }
24             };

测试代码:

 1         public static byte[] SerializeBinary(object request)
 2         {
 3
 4             System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
 5
 6             new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 7
 8             using (System.IO.MemoryStream memStream = new System.IO.MemoryStream())
 9             {
10                 serializer.Serialize(memStream, request);
11
12                 return memStream.ToArray();
13             }
14         }
15
16
17         public static object DeSerializeBinary(byte[] data)
18         {
19             using (System.IO.MemoryStream memStream = new System.IO.MemoryStream(data))
20             {
21                 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =
22
23                 new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
24
25                 return deserializer.Deserialize(memStream);
26             }
27         }
28
29         static void SerializeTest()
30         {
31             var groupInfo = new GroupInfo()
32             {
33                 GroupID = 1,
34                 IsTemporary = false,
35                 Name = "yswenli group",
36                 Created = DateTimeHelper.Now,
37                 Creator = new UserInfo()
38                 {
39
40                     ID = 1,
41                     Birthday = DateTimeHelper.Now.AddYears(-100),
42                     UserName = "yswenli"
43                 },
44                 Users = new System.Collections.Generic.List<UserInfo>()
45                 {
46                     new UserInfo()
47                     {
48
49                         ID = 1,
50                         Birthday = DateTimeHelper.Now.AddYears(-100),
51                         UserName = "yswenli"
52                     }
53                 }
54             };
55
56             var count = 100000;
57             var len1 = 0;
58             var len2 = 0;
59
60             Stopwatch sw = new Stopwatch();
61             sw.Start();
62
63             List<byte[]> list = new List<byte[]>();
64             for (int i = 0; i < count; i++)
65             {
66                 var bytes = SerializeBinary(groupInfo);
67                 len1 = bytes.Length;
68                 list.Add(bytes);
69             }
70             ConsoleHelper.WriteLine($"BinaryFormatter实体序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
71
72             sw.Restart();
73             for (int i = 0; i < count; i++)
74             {
75                 var obj = DeSerializeBinary(list[i]);
76             }
77             ConsoleHelper.WriteLine($"BinaryFormatter实体反序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
78             ConsoleHelper.WriteLine($"BinaryFormatter序列化生成bytes大小:{len1 * count * 1.0 / 1024 / 1024} Mb");
79             list.Clear();
80             sw.Restart();
81
82             for (int i = 0; i < count; i++)
83             {
84                 var bytes = RPC.Serialize.ParamsSerializeUtil.Serialize(groupInfo);
85                 len2 = bytes.Length;
86                 list.Add(bytes);
87             }
88             ConsoleHelper.WriteLine($"ParamsSerializeUtil实体序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
89             sw.Restart();
90             for (int i = 0; i < count; i++)
91             {
92                 int os = 0;
93
94                 var obj = RPC.Serialize.ParamsSerializeUtil.Deserialize(groupInfo.GetType(), list[i], ref os);
95             }
96             ConsoleHelper.WriteLine($"ParamsSerializeUtil实体反序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
97             ConsoleHelper.WriteLine($"ParamsSerializeUtil序列化生成bytes大小:{len2 * count * 1.0 / 1024 / 1024} Mb");
98             sw.Stop();
99         }

运行结果:

自已动手做高性能消息队列

 

前言

本人觉得码农的技术提升应该是从how to do到why do,而项目或产品都是从why do到how to do,按题来,所以呢下面先从大的方面介绍一下消息队列。

消息队列是分布式高并发面目中必不可少的一部分,随着互联网、云计算、大数据的使用,消息队列的应用越来越多,消息队列在系统的可伸缩性、稳定性、提升吞吐量等方面有着显著的作用;它主要的作用一般如下:

1.通过异步处理提高系统性能

如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。

通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:

因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。

2.降低系统耦合性

我们知道模块分布式部署以后聚合方式通常有两种:1.分布式消息队列和2.分布式服务。先来简单说一下分布式服务目前使用比较多的用来构建SOA(Service Oriented Architecture面向服务体系结构)分布式服务框架是阿里巴巴开源的Dubbo.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:《高性能优秀的服务框架-dubbo介绍》:https://juejin.im/post/5acadeb1f265da2375072f9c 再来谈我们的分布式消息队列:我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。

我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:

        消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计

消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。

        另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。

        前面说了这么多消息队列的重要性、使用场景、工作模式,有很多人就可能会说了,现有的ActiveMQ、RabbitMQ、Kafka、RocketMQ等多了去了,那在项目架构的时候选一个用上去就不行了,完全没有必要重复造轮子啊!本人认为对于重复造轮子的事情和其它任何事情都是一样的——任何事情没有绝对的好处或者坏处,比如是刚入门的码农、又或者很急的项目,完全可以选用现有一种通用的、成熟的产品,没必要去从零开始做;实际上没有任何一个优秀的产品全部使用三方的产品来组装完成的,任何一个好一点的项目发展到一定的时候都不约而同的进行底层开发。原因很简单:第一个任何通用型的产品总用功能覆盖不到的场景;第二个任何通用型的产品为了实现通用必将做了一些性能或架构的牺牲;现在道理都讲完了,开始动手了(都听你逼半天,能动手就尽量少逼逼!)。

概述

  动手前先构思一下,本人需要一个简单的、可发布订阅的、高吞吐量的消息队列,并将之简单大的方面分成QServer、QClient;QServer主要有Exchange、Binding、MessageQueue构成;QClient和QServer共用一套相同的传输编解码器QCoder ,主要实现Publish、Subscribe、Unsubcribe、Closes等功能;先想这么多,开干!

Exchange

  主要在QServer中提供发布、订阅、连接、队列信息等管理

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: Exchange
  8 *版本号: V1.0.0.0
  9 *唯一标识:6a576aad-edcc-446d-b7e5-561a622549bf
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 16:36:44
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 16:36:44
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Generic;
 29 using System.Linq;
 30 using System.Text;
 31 using System.Threading;
 32 using System.Threading.Tasks;
 33
 34 namespace SAEA.QueueSocket.Model
 35 {
 36     class Exchange : ISyncBase
 37     {
 38         object _syncLocker = new object();
 39
 40         public object SyncLocker
 41         {
 42             get
 43             {
 44                 return _syncLocker;
 45             }
 46         }
 47
 48         long _pNum = 0;
 49
 50         long _cNum = 0;
 51
 52         long _inNum = 0;
 53
 54         long _outNum = 0;
 55
 56         private Binding _binding;
 57
 58         private MessageQueue _messageQueue;
 59
 60         public Exchange()
 61         {
 62             this._binding = new Binding();
 63
 64             this._messageQueue = new MessageQueue();
 65         }
 66
 67
 68         public void AcceptPublish(string sessionID, QueueResult pInfo)
 69         {
 70             lock (_syncLocker)
 71             {
 72                 this._binding.Set(sessionID, pInfo.Name, pInfo.Topic);
 73
 74                 this._messageQueue.Enqueue(pInfo.Topic, pInfo.Data);
 75
 76                 _pNum = this._binding.GetPublisherCount();
 77
 78                 Interlocked.Increment(ref _inNum);
 79             }
 80         }
 81
 82         public void AcceptPublishForBatch(string sessionID, QueueResult[] datas)
 83         {
 84             if (datas != null)
 85             {
 86                 foreach (var data in datas)
 87                 {
 88                     if (data != null)
 89                     {
 90                         AcceptPublish(sessionID, data);
 91                     }
 92                 }
 93             }
 94         }
 95
 96
 97         public void GetSubscribeData(string sessionID, QueueResult sInfo, int maxSize = 500, int maxTime = 500, Action<List<string>> callBack = null)
 98         {
 99             lock (_syncLocker)
100             {
101                 var result = this._binding.GetBingInfo(sInfo);
102
103                 if (result == null)
104                 {
105                     this._binding.Set(sessionID, sInfo.Name, sInfo.Topic, false);
106
107                     _cNum = this._binding.GetSubscriberCount();
108
109                     Task.Factory.StartNew(() =>
110                     {
111                         while (this._binding.Exists(sInfo))
112                         {
113                             var list = this._messageQueue.DequeueForList(sInfo.Topic, maxSize, maxTime);
114                             if (list != null)
115                             {
116                                 list.ForEach(i => { Interlocked.Increment(ref _outNum); });
117                                 callBack?.Invoke(list);
118                                 list.Clear();
119                                 list = null;
120                             }
121                         }
122                     });
123                 }
124             }
125         }
126
127         public void Unsubscribe(QueueResult sInfo)
128         {
129             Interlocked.Decrement(ref _cNum);
130             this._binding.Del(sInfo.Name, sInfo.Topic);
131         }
132
133         public void Clear(string sessionID)
134         {
135             lock (_syncLocker)
136             {
137                 var data = this._binding.GetBingInfo(sessionID);
138
139                 if (data != null)
140                 {
141                     if (data.Flag)
142                     {
143                         Interlocked.Decrement(ref _pNum);
144                     }
145                     else
146                     {
147                         Interlocked.Decrement(ref _cNum);
148                     }
149                     this._binding.Remove(sessionID);
150                 }
151             }
152         }
153
154         public Tuple<long, long, long, long> GetConnectInfo()
155         {
156             return new Tuple<long, long, long, long>(_pNum, _cNum, _inNum, _outNum);
157         }
158
159         public List<Tuple<string, long>> GetQueueInfo()
160         {
161             List<Tuple<string, long>> result = new List<Tuple<string, long>>();
162             lock (_syncLocker)
163             {
164                 var list = this._messageQueue.ToList();
165                 if (list != null)
166                 {
167                     var tlts = list.Select(b => b.Topic).Distinct().ToList();
168
169                     if (tlts != null)
170                     {
171                         foreach (var topic in tlts)
172                         {
173                             var count = this._messageQueue.GetCount(topic);
174                             var t = new Tuple<string, long>(topic, count);
175                             result.Add(t);
176                         }
177                         tlts.Clear();
178                     }
179                     list.Clear();
180                 }
181             }
182             return result;
183         }
184
185     }
186 }

  思维发散:这里可以增加全局消息队列、指定连接消息队列等;将连接通过类型redis cluster模式进行一个均衡分布等

Binding

  主要功能是将连接、主题进行映射管理

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: Binding
  8 *版本号: V1.0.0.0
  9 *唯一标识:7472dabd-1b6a-4ffe-b19f-2d1cf7348766
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 17:10:19
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 17:10:19
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Generic;
 29 using System.Linq;
 30 using System.Text;
 31
 32 namespace SAEA.QueueSocket.Model
 33 {
 34     /// <summary>
 35     /// 连接与主题的映射
 36     /// </summary>
 37     class Binding : ISyncBase, IDisposable
 38     {
 39         List<BindInfo> _list = new List<BindInfo>();
 40
 41         object _syncLocker = new object();
 42
 43         public object SyncLocker
 44         {
 45             get
 46             {
 47                 return _syncLocker;
 48             }
 49         }
 50
 51         bool _isDisposed = false;
 52
 53         int _minutes = 10;
 54
 55         public Binding(int minutes = 10)
 56         {
 57             _minutes = minutes;
 58
 59             ThreadHelper.PulseAction(() =>
 60             {
 61                 lock (_syncLocker)
 62                 {
 63                     var list = _list.Where(b => b.Expired <= DateTimeHelper.Now).ToList();
 64                     if (list != null)
 65                     {
 66                         list.ForEach(item =>
 67                         {
 68                             _list.Remove(item);
 69                         });
 70                         list.Clear();
 71                         list = null;
 72                     }
 73                 }
 74             }, new TimeSpan(0, 0, 10), _isDisposed);
 75         }
 76
 77
 78         public void Set(string sessionID, string name, string topic, bool isPublisher = true)
 79         {
 80
 81             lock (_syncLocker)
 82             {
 83                 var result = _list.FirstOrDefault(b => b.Name == name && b.Topic == topic);
 84                 if (result == null)
 85                 {
 86                     _list.Add(new BindInfo()
 87                     {
 88                         SessionID = sessionID,
 89                         Name = name,
 90                         Topic = topic,
 91                         Flag = isPublisher,
 92                         Expired = DateTimeHelper.Now.AddMinutes(_minutes)
 93                     });
 94                 }
 95                 else
 96                 {
 97                     result.Expired = DateTimeHelper.Now.AddMinutes(_minutes);
 98                 }
 99             }
100         }
101
102         public void Del(string sessionID, string topic)
103         {
104             lock (_syncLocker)
105             {
106                 var result = _list.FirstOrDefault(b => b.Name == sessionID && b.Topic == topic);
107                 if (result != null)
108                 {
109                     _list.Remove(result);
110                 }
111             }
112         }
113
114         public void Remove(string sessionID)
115         {
116             lock (_syncLocker)
117             {
118                 var result = _list.Where(b => b.SessionID == sessionID).ToList();
119                 if (result != null)
120                 {
121                     result.ForEach((item) =>
122                     {
123                         _list.Remove(item);
124                     });
125                     result.Clear();
126                 }
127             }
128         }
129
130         public BindInfo GetBingInfo(QueueResult sInfo)
131         {
132             lock (_syncLocker)
133             {
134                 var bi = _list.FirstOrDefault(b => b.Name == sInfo.Name && b.Topic == sInfo.Topic);
135
136                 if (bi != null)
137                 {
138                     if (bi.Expired <= DateTimeHelper.Now)
139                     {
140                         Remove(bi.SessionID);
141                     }
142                     else
143                     {
144                         return bi;
145                     }
146                 }
147                 return null;
148             }
149         }
150
151         public BindInfo GetBingInfo(string sessionID)
152         {
153             lock (_syncLocker)
154             {
155                 return _list.FirstOrDefault(b => b.SessionID == sessionID);
156             }
157         }
158
159         public bool Exists(QueueResult sInfo)
160         {
161             lock (_syncLocker)
162             {
163                 var data = _list.FirstOrDefault(b => b.Name == sInfo.Name && b.Topic == sInfo.Topic);
164
165                 if (data != null)
166                 {
167                     if (data.Expired <= DateTimeHelper.Now)
168                     {
169                         Remove(data.SessionID);
170
171                         return false;
172                     }
173
174                     data.Expired = DateTimeHelper.Now.AddMinutes(_minutes);
175
176                     return true;
177                 }
178             }
179             return false;
180         }
181
182
183         public IEnumerable<BindInfo> GetPublisher()
184         {
185             lock (_syncLocker)
186             {
187                 return _list.Where(b => b.Flag);
188             }
189         }
190
191         public int GetPublisherCount()
192         {
193             lock (_syncLocker)
194             {
195                 return _list.Where(b => b.Flag).Count();
196             }
197         }
198
199         public IEnumerable<BindInfo> GetSubscriber()
200         {
201             lock (_syncLocker)
202             {
203                 return _list.Where(b => !b.Flag);
204             }
205         }
206
207         public int GetSubscriberCount()
208         {
209             lock (_syncLocker)
210             {
211                 return _list.Where(b => !b.Flag).Count();
212             }
213         }
214
215
216         public void Dispose()
217         {
218             _isDisposed = true;
219             lock (_syncLocker)
220             {
221                 _list.Clear();
222                 _list = null;
223             }
224         }
225     }
226 }

  思维发散:实现多个QServer的主题与队列映射克隆、或者队列消息转发实现容灾集群或大容量集群等

MessageQueue

  将主题与队列形成一个映射,并对主题映射进行管理

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: QueueCollection
  8 *版本号: V1.0.0.0
  9 *唯一标识:89a65c12-c4b3-486b-a933-ad41c3db6621
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/6 10:31:11
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/6 10:31:11
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Concurrent;
 29 using System.Collections.Generic;
 30 using System.Linq;
 31 using System.Threading.Tasks;
 32
 33 namespace SAEA.QueueSocket.Model
 34 {
 35     public class MessageQueue : ISyncBase, IDisposable
 36     {
 37         bool _isDisposed = false;
 38
 39         ConcurrentDictionary<string, QueueBase> _list;
 40
 41         object _syncLocker = new object();
 42
 43         public object SyncLocker
 44         {
 45             get
 46             {
 47                 return _syncLocker;
 48             }
 49         }
 50
 51         public MessageQueue()
 52         {
 53             _list = new ConcurrentDictionary<string, QueueBase>();
 54
 55             ThreadHelper.Run(() =>
 56             {
 57                 while (!_isDisposed)
 58                 {
 59                     var list = _list.Values.Where(b => b.Expired <= DateTimeHelper.Now);
 60                     if (list != null)
 61                     {
 62                         foreach (var item in list)
 63                         {
 64                             if (item.Length == 0)
 65                             {
 66                                 _list.TryRemove(item.Topic, out QueueBase q);
 67                             }
 68                         }
 69                     }
 70                     ThreadHelper.Sleep(10000);
 71                 }
 72             }, true, System.Threading.ThreadPriority.Highest);
 73         }
 74
 75
 76         public void Enqueue(string topic, string data)
 77         {
 78             var queue = _list.Values.FirstOrDefault(b => b.Topic.Equals(topic));
 79             lock (_syncLocker)
 80             {
 81                 if (queue == null)
 82                 {
 83                     queue = new QueueBase(topic);
 84                     _list.TryAdd(topic, queue);
 85                 }
 86             }
 87             queue.Enqueue(data);
 88         }
 89
 90
 91         public string Dequeue(string topic)
 92         {
 93             var queue = _list.Values.FirstOrDefault(b => b.Topic.Equals(topic));
 94             if (queue != null)
 95             {
 96                 return queue.Dequeue();
 97             }
 98             return null;
 99         }
100
101         /// <summary>
102         /// 批量读取数据
103         /// </summary>
104         /// <param name="topic"></param>
105         /// <param name="maxSize"></param>
106         /// <param name="maxTime"></param>
107         /// <returns></returns>
108         public List<string> DequeueForList(string topic, int maxSize = 500, int maxTime = 500)
109         {
110             List<string> result = new List<string>();
111             bool running = true;
112             var m = 0;
113             var task = Task.Factory.StartNew(() =>
114             {
115                 while (running)
116                 {
117                     var data = Dequeue(topic);
118                     if (data != null)
119                     {
120                         result.Add(data);
121                         m++;
122                         if (m == maxSize)
123                         {
124                             running = false;
125                         }
126                     }
127                     else
128                     {
129                         ThreadHelper.Sleep(1);
130                     }
131                 }
132             });
133             Task.WaitAll(new Task[] { task }, maxTime);
134             running = false;
135             return result;
136         }
137
138         public string BlockDequeue(string topic)
139         {
140             var queue = _list.Values.FirstOrDefault(b => b.Topic == topic);
141             if (queue != null)
142             {
143                 return queue.BlockDequeue();
144             }
145             return null;
146         }
147
148         public List<QueueBase> ToList()
149         {
150             lock (_syncLocker)
151             {
152                 return _list.Values.ToList();
153             }
154         }
155
156         public long GetCount(string topic)
157         {
158             var queue = _list.Values.FirstOrDefault(b => b.Topic == topic);
159             if (queue != null)
160                 return queue.Length;
161             return 0;
162         }
163
164         public void Dispose()
165         {
166             _isDisposed = true;
167             _list.Clear();
168                 _list = null;
169         }
170     }
171 }

  思维发散:增加硬盘持久化以实现down机容灾、增加ack确认再移除以实现高可靠性等

QCoder

  在QServer和QClient之间进行传输编解码,这个编解码的速度直接影响消息队列的传输性能;本人使用了2种方案:1.使用类似redis传输方案,使用回车作为分隔符方式,这种方案结果要么一个字节一个字节检查分隔符,这种for操作还是C、C++屌,C#做这个真心不行;要么先将字节数组通过Encoding转换成String再来for,虽说能提升几倍性能,但是遇到不完整的字节数组时,本人没有找一个好的方法。2.使用自定义类型+长度+内容这种格式

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Net
  7 *文件名: QCoder
  8 *版本号: V1.0.0.0
  9 *唯一标识:88f5a779-8294-47bc-897b-8357a09f2fdb
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 18:01:56
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 18:01:56
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24
 25 using SAEA.Commom;
 26 using SAEA.QueueSocket.Model;
 27 using SAEA.QueueSocket.Type;
 28 using SAEA.Sockets.Interface;
 29 using System;
 30 using System.Collections.Generic;
 31 using System.Text;
 32
 33 namespace SAEA.QueueSocket.Net
 34 {
 35     public sealed class QCoder : ICoder
 36     {
 37         static readonly int MIN = 1 + 4 + 4 + 0 + 4 + 0 + 0;
 38
 39         private List<byte> _buffer = new List<byte>();
 40
 41         private object _locker = new object();
 42
 43         public void Pack(byte[] data, Action<DateTime> onHeart, Action<ISocketProtocal> onUnPackage, Action<byte[]> onFile)
 44         {
 45
 46         }
 47
 48         /// <summary>
 49         /// 队列编解码器
 50         /// </summary>
 51         public QueueCoder QueueCoder { get; set; } = new QueueCoder();
 52
 53         /// <summary>
 54         /// 包解析
 55         /// </summary>
 56         /// <param name="data"></param>
 57         /// <param name="OnQueueResult"></param>
 58         public void GetQueueResult(byte[] data, Action<QueueResult> OnQueueResult)
 59         {
 60             lock (_locker)
 61             {
 62                 try
 63                 {
 64                     _buffer.AddRange(data);
 65
 66                     if (_buffer.Count > (1 + 4 + 4 + 0 + 4 + 0 + 0))
 67                     {
 68                         var buffer = _buffer.ToArray();
 69
 70                         QCoder.Decode(buffer, (list, offset) =>
 71                         {
 72                             if (list != null)
 73                             {
 74                                 foreach (var item in list)
 75                                 {
 76                                     OnQueueResult?.Invoke(new QueueResult()
 77                                     {
 78                                         Type = (QueueSocketMsgType)item.Type,
 79                                         Name = item.Name,
 80                                         Topic = item.Topic,
 81                                         Data = item.Data
 82                                     });
 83                                 }
 84                                 _buffer.RemoveRange(0, offset);
 85                             }
 86                         });
 87                     }
 88                 }
 89                 catch (Exception ex)
 90                 {
 91                     ConsoleHelper.WriteLine("QCoder.GetQueueResult error:" + ex.Message + ex.Source);
 92                     _buffer.Clear();
 93                 }
 94             }
 95         }
 96
 97
 98
 99         /// <summary>
100         /// socket 传输字节编码
101         /// 格式为:1+4+4+x+4+x+4
102         /// </summary>
103         /// <param name="queueSocketMsg"></param>
104         /// <returns></returns>
105         public static byte[] Encode(QueueSocketMsg queueSocketMsg)
106         {
107             List<byte> list = new List<byte>();
108
109             var total = 4 + 4 + 4;
110
111             var nlen = 0;
112
113             var tlen = 0;
114
115             byte[] n = null;
116             byte[] tp = null;
117             byte[] d = null;
118
119             if (!string.IsNullOrEmpty(queueSocketMsg.Name))
120             {
121                 n = Encoding.UTF8.GetBytes(queueSocketMsg.Name);
122                 nlen = n.Length;
123                 total += nlen;
124             }
125             if (!string.IsNullOrEmpty(queueSocketMsg.Topic))
126             {
127                 tp = Encoding.UTF8.GetBytes(queueSocketMsg.Topic);
128                 tlen = tp.Length;
129                 total += tlen;
130             }
131             if (!string.IsNullOrEmpty(queueSocketMsg.Data))
132             {
133                 d = Encoding.UTF8.GetBytes(queueSocketMsg.Data);
134                 total += d.Length;
135             }
136
137             list.Add(queueSocketMsg.Type);
138             list.AddRange(BitConverter.GetBytes(total));
139             list.AddRange(BitConverter.GetBytes(nlen));
140             if (nlen > 0)
141                 list.AddRange(n);
142             list.AddRange(BitConverter.GetBytes(tlen));
143             if (tlen > 0)
144                 list.AddRange(tp);
145             if (d != null)
146                 list.AddRange(d);
147             var arr = list.ToArray();
148             list.Clear();
149             return arr;
150         }
151
152         /// <summary>
153         /// socket 传输字节解码
154         /// </summary>
155         /// <param name="data"></param>
156         /// <param name="onDecode"></param>
157         public static bool Decode(byte[] data, Action<QueueSocketMsg[], int> onDecode)
158         {
159             int offset = 0;
160
161             try
162             {
163                 if (data != null && data.Length > offset + MIN)
164                 {
165                     var list = new List<QueueSocketMsg>();
166
167                     while (data.Length > offset + MIN)
168                     {
169                         var total = BitConverter.ToInt32(data, offset + 1);
170
171                         if (data.Length >= offset + total + 1)
172                         {
173                             offset += 5;
174
175                             var qm = new QueueSocketMsg((QueueSocketMsgType)data[0]);
176                             qm.Total = total;
177
178                             qm.NLen = BitConverter.ToInt32(data, offset);
179                             offset += 4;
180
181
182                             if (qm.NLen > 0)
183                             {
184                                 var narr = new byte[qm.NLen];
185                                 Buffer.BlockCopy(data, offset, narr, 0, narr.Length);
186                                 qm.Name = Encoding.UTF8.GetString(narr);
187                             }
188                             offset += qm.NLen;
189
190                             qm.TLen = BitConverter.ToInt32(data, offset);
191
192                             offset += 4;
193
194                             if (qm.TLen > 0)
195                             {
196                                 var tarr = new byte[qm.TLen];
197                                 Buffer.BlockCopy(data, offset, tarr, 0, tarr.Length);
198                                 qm.Topic = Encoding.UTF8.GetString(tarr);
199                             }
200                             offset += qm.TLen;
201
202                             var dlen = qm.Total - 4 - 4 - qm.NLen - 4 - qm.TLen;
203
204                             if (dlen > 0)
205                             {
206                                 var darr = new byte[dlen];
207                                 Buffer.BlockCopy(data, offset, darr, 0, dlen);
208                                 qm.Data = Encoding.UTF8.GetString(darr);
209                                 offset += dlen;
210                             }
211                             list.Add(qm);
212                         }
213                         else
214                         {
215                             break;
216                         }
217                     }
218                     if (list.Count > 0)
219                     {
220                         onDecode?.Invoke(list.ToArray(), offset);
221                         return true;
222                     }
223                 }
224             }
225             catch (Exception ex)
226             {
227                 ConsoleHelper.WriteLine($"QCoder.Decode error:{ex.Message} stack:{ex.StackTrace} data:{data.Length} offset:{offset}");
228             }
229             onDecode?.Invoke(null, 0);
230             return false;
231         }
232
233
234         /// <summary>
235         /// dispose
236         /// </summary>
237         public void Dispose()
238         {
239             _buffer.Clear();
240             _buffer = null;
241         }
242
243
244
245     }
246 }

测试

  简单的How to do和Why do已经完成了,是时候定义个Producer、Consumer来测试一把了

  1 using SAEA.QueueSocket;
  2 using SAEA.Commom;
  3 using SAEA.QueueSocket.Model;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Diagnostics;
  7 using System.Text;
  8 using System.Threading;
  9 using System.Threading.Tasks;
 10
 11 namespace SAEA.QueueSocketTest
 12 {
 13     class Program
 14     {
 15         static void Main(string[] args)
 16         {
 17             do
 18             {
 19                 ConsoleHelper.WriteLine("输入s启动队列服务器,输入p启动生产者,输入c启动消费者");
 20
 21                 var inputStr = ConsoleHelper.ReadLine();
 22
 23                 if (!string.IsNullOrEmpty(inputStr))
 24                 {
 25                     var topic = "测试频道";
 26
 27                     switch (inputStr.ToLower())
 28                     {
 29                         case "s":
 30                             ConsoleHelper.Title = "SAEA.QueueServer";
 31                             ServerInit();
 32                             break;
 33                         case "p":
 34                             ConsoleHelper.Title = "SAEA.QueueProducer";
 35                             ConsoleHelper.WriteLine("输入ip:port连接到队列服务器");
 36                             inputStr = ConsoleHelper.ReadLine();
 37                             ProducerInit(inputStr, topic);
 38                             break;
 39                         case "c":
 40                             ConsoleHelper.Title = "SAEA.QueueConsumer";
 41                             ConsoleHelper.WriteLine("输入ip:port连接到队列服务器");
 42                             inputStr = ConsoleHelper.ReadLine();
 43                             ConsumerInit(inputStr, topic);
 44                             break;
 45                         default:
 46                             ServerInit();
 47                             inputStr = "127.0.0.1:39654";
 48                             ProducerInit(inputStr, topic);
 49                             ConsumerInit(inputStr, topic);
 50                             break;
 51                     }
 52                     ConsoleHelper.WriteLine("回车退出!");
 53                     ConsoleHelper.ReadLine();
 54                     return;
 55                 }
 56             }
 57             while (true);
 58         }
 59
 60
 61
 62         static QServer _server;
 63         static void ServerInit()
 64         {
 65             _server = new QServer();
 66             _server.OnDisconnected += Server_OnDisconnected;
 67             _server.CalcInfo((ci, qi) =>
 68             {
 69                 var result = string.Format("生产者:{0} 消费者:{1} 收到消息:{2} 推送消息:{3}{4}", ci.Item1, ci.Item2, ci.Item3, ci.Item4, Environment.NewLine);
 70
 71                 qi.ForEach((item) =>
 72                 {
 73                     result += string.Format("队列名称:{0} 堆积消息数:{1} {2}", item.Item1, item.Item2, Environment.NewLine);
 74                 });
 75                 ConsoleHelper.WriteLine(result);
 76             });
 77             _server.Start();
 78         }
 79
 80         private static void Server_OnDisconnected(string ID, Exception ex)
 81         {
 82             _server.Clear(ID);
 83             if (ex != null)
 84             {
 85                 ConsoleHelper.WriteLine("{0} 已从服务器断开,err:{1}", ID, ex.ToString());
 86             }
 87         }
 88
 89         static void ProducerInit(string ipPort, string topic)
 90         {
 91             int pNum = 0;
 92
 93             //string msg = "主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。";
 94             string msg = "123";
 95             if (string.IsNullOrEmpty(ipPort)) ipPort = "127.0.0.1:39654";
 96
 97             QClient producer = new QClient("productor" + Guid.NewGuid().ToString("N"), ipPort);
 98
 99             producer.OnError += Producer_OnError;
100
101             producer.OnDisconnected += Client_OnDisconnected;
102
103             producer.ConnectAsync((s) =>
104             {
105                 Task.Factory.StartNew(() =>
106                 {
107                     var old = 0;
108                     var speed = 0;
109                     while (producer.Connected)
110                     {
111                         speed = pNum - old;
112                         old = pNum;
113                         ConsoleHelper.WriteLine("生产者已成功发送:{0} 速度:{1}/s", pNum, speed);
114                         Thread.Sleep(1000);
115                     }
116                 });
117
118                 var list = new List<Tuple<string, byte[]>>();
119
120
121                 while (producer.Connected)
122                 {
123
124                     producer.Publish(topic, msg);
125
126                     Interlocked.Increment(ref pNum);
127                 }
128             });
129
130
131         }
132
133         private static void Producer_OnError(string ID, Exception ex)
134         {
135             ConsoleHelper.WriteLine("id:" + ID + ",error:" + ex.Message);
136         }
137
138         static void ConsumerInit(string ipPort, string topic)
139         {
140             if (string.IsNullOrEmpty(ipPort)) ipPort = "127.0.0.1:39654";
141             QClient consumer = new QClient("subscriber-" + Guid.NewGuid().ToString("N"), ipPort);
142             consumer.OnMessage += Subscriber_OnMessage;
143             consumer.OnDisconnected += Client_OnDisconnected;
144             consumer.ConnectAsync((s) =>
145             {
146                 Task.Factory.StartNew(() =>
147                 {
148                     var old = 0;
149                     var speed = 0;
150                     while (consumer.Connected)
151                     {
152                         speed = _outNum - old;
153                         old = _outNum;
154                         ConsoleHelper.WriteLine("消费者已成功接收:{0} 速度:{1}/s", _outNum, speed);
155                         Thread.Sleep(1000);
156                     }
157                 });
158
159                 consumer.Subscribe(topic);
160             });
161
162         }
163
164         private static void Client_OnDisconnected(string ID, Exception ex)
165         {
166             ConsoleHelper.WriteLine("当前连接已关闭");
167         }
168
169         static int _outNum = 0;
170
171         private static void Subscriber_OnMessage(QueueResult obj)
172         {
173             if (obj != null)
174                 _outNum += 1;
175         }
176     }
177 }

  单线程的、单生产者、单消费者、单队列服务器的测试结果如下图:

  到此一个自行实现的简单的消息队列完成了,虽说它离实际产品还很遥远,但是本人还是觉的技术的提升离不开钻研,路漫漫其修远兮,吾将上下而求索!

转载请标明本文来源:http://www.cnblogs.com/yswenli/p//9029587.html
更多内容欢迎star作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是yswenli 。

自行实现高性能MVC WebAPI

 

  wcf虽然功能多、扩展性强但是也面临配置忒多,而且restful的功能相当怪异,并且目前没法移植。asp.net core虽然支持webapi,但是功能也相对繁多、配置复杂。就没有一个能让码农们安安心心的写webapi,无需考虑性能、配置、甚至根据问题场景自行设计、改造等问题的方案么?

  当然不是,特别是在dnc2.0已经相当强大的此时,完全可以自行设计一套简洁、高效的webapi框架!说到自行写一套框架,很多码农们就可能会想到开发工作量难以想像,事实真的如此么?java因为开源众多,很多对mvc稍有了解的都可以拿这个拿那个拼出一个自已的mvc框架;而面对日益强大的dnc,本人觉得C#根本无需东拼西凑这么麻烦,完全可以根据自已的需求简单快速的写出一个来,不服就开干!

  设计的编码思路就是仿asp.net mvc,原因就是asp.net mvc成功发展了这么多年,有着大量的C#码农习惯了这套优良的编码方式;至于spring mvc、spring boot那些,站在使用者的角度来说,光配置和注解都能敲死人,如要要说简洁快速,asp.net mvc比他强多了,更别提ruby on rails。不扯远了,下面就按C#经典来。那么需要考虑的问题有tcp、http、request、response、server、controller、actionresult、routetable等,下面就一一来解决这个问题。

  一、Tcp:这个是实现传输通信的底层,当然采用IOCP来提高吞吐量和性能,本人之前在做Redis Client等的时候就使用这个IOCP Socket的框架,此时正好也可以用上

 1 /****************************************************************************
 2 *Copyright (c) 2018 Microsoft All Rights Reserved.
 3 *CLR版本: 4.0.30319.42000
 4 *机器名称:WENLI-PC
 5 *公司名称:Microsoft
 6 *命名空间:SAEA.WebAPI.Http.Net
 7 *文件名: ServerSocket
 8 *版本号: V1.0.0.0
 9 *唯一标识:ab912b9a-c7ed-44d9-8e48-eef0b6ff86a2
10 *当前的用户域:WENLI-PC
11 *创建人: yswenli
12 *电子邮箱:wenguoli_520@qq.com
13 *创建时间:2018/4/8 17:11:15
14 *描述:
15 *
16 *=====================================================================
17 *修改标记
18 *修改时间:2018/4/8 17:11:15
19 *修改人: yswenli
20 *版本号: V1.0.0.0
21 *描述:
22 *
23 *****************************************************************************/
24 using SAEA.Sockets.Core;
25 using SAEA.Sockets.Interface;
26 using System;
27 using System.Collections.Generic;
28 using System.Net;
29 using System.Text;
30
31 namespace SAEA.WebAPI.Http.Net
32 {
33     class ServerSocket : BaseServerSocket
34     {
35         public event Action<IUserToken, string> OnRequested;
36
37         public ServerSocket(int bufferSize = 1024 * 100, int count = 10000) : base(new HContext(), bufferSize, true, count)
38         {
39
40         }
41
42         protected override void OnReceiveBytes(IUserToken userToken, byte[] data)
43         {
44             HCoder coder = (HCoder)userToken.Coder;
45
46             coder.GetRequest(data, (result) =>
47             {
48                 OnRequested?.Invoke(userToken, result);
49             });
50         }
51
52         public void Reply(IUserToken userToken, byte[] data)
53         {
54             base.Send(userToken, data);
55             base.Disconnected(userToken);
56         }
57     }
58 }

  二、Http:这个是个应用协议,本人了解下来至少有3个版本,完全熟悉的话估计没个半年都搞不定;但是只需要关键,比如说http1.1的工作模式、传输格式、常见异常code、常见mime类型、js跨域支持等,这些基本能覆盖绝大部分日常场景,至于更多的那些细枝末节的理它作甚,本人的做法就是用Chrome的开发人员工具来查看相关network详情,这样的话就可以清楚http这个协议的具体编码解码了。

 1         public void GetRequest(byte[] data, Action<string> onUnpackage)
 2         {
 3             lock (_locker)
 4             {
 5                 var str = Encoding.UTF8.GetString(data);
 6
 7                 var index = str.IndexOf(ENDSTR);
 8
 9                 if (index > -1)
10                 {
11                     var s = str.Substring(0, index);
12
13                     _result.Append(s);
14
15                     onUnpackage.Invoke(_result.ToString());
16
17                     _result.Clear();
18
19                     if (str.Length > index + 4)
20                     {
21                         _result.Append(str.Substring(index + 4));
22                     }
23                 }
24                 else
25                 {
26                     _result.Append(str);
27                 }
28             }
29         }

  经过分析后http的内容格式其实就是字符回车分隔,再加上一些约定生成的分隔符bound完成的。

 1         public HttpRequest(Stream stream)
 2         {
 3             this._dataStream = stream;
 4             var data = GetRequestData(_dataStream);
 5             var rows = Regex.Split(data, Environment.NewLine);
 6
 7             //Request URL & Method & Version
 8             var first = Regex.Split(rows[0], @"(\s+)")
 9                 .Where(e => e.Trim() != string.Empty)
10                 .ToArray();
11             if (first.Length > 0) this.Method = first[0];
12             if (first.Length > 1)
13             {
14                 this.Query = first[1];
15
16                 if (this.Query.Contains("?"))
17                 {
18                     var qarr = this.Query.Split("?");
19                     this.URL = qarr[0];
20                     this.Params = GetRequestParameters(qarr[1]);
21                 }
22                 else
23                 {
24                     this.URL = this.Query;
25                 }
26
27                 var uarr = this.URL.Split("/");
28
29                 if (long.TryParse(uarr[uarr.Length - 1], out long id))
30                 {
31                     this.URL = this.URL.Substring(0, this.URL.LastIndexOf("/"));
32                     this.Params.Set("id", id.ToString());
33                 }
34             }
35             if (first.Length > 2) this.Protocols = first[2];
36
37             //Request Headers
38             this.Headers = GetRequestHeaders(rows);
39
40             //Request "GET"
41             if (this.Method == "GET")
42             {
43                 this.Body = GetRequestBody(rows);
44             }
45
46             //Request "POST"
47             if (this.Method == "POST")
48             {
49                 this.Body = GetRequestBody(rows);
50                 var contentType = GetHeader(RequestHeaderType.ContentType);
51                 var isUrlencoded = contentType == @"application/x-www-form-urlencoded";
52                 if (isUrlencoded) this.Params = GetRequestParameters(this.Body);
53             }
54         }

  看到上面,有人肯定会说你这个传文件咋办?一个呢本人这个是针对webapi;另外一个,如真有这个场景,可以用Chrome的开发人员工具来查看相关network详情,也可以使用httpanalyzerstd、httpwatch等众多工具分析下,其实也就是使用了一些约定的分隔符bound完成,每个浏览器还不一样,有兴趣的完全可以自行扩展一个。

  三、Reponse这个是webapi服务端相当重要的一个组件,本人也是尽可能方便并且按尽量按asp.net mvc的命名来实现,另外这里加入支持js跨域所需大部分场景heads,如果还有特殊的heads,完全可以自已添加。

  四、HttpServer:这个就是承载webapi的容器;有人说不是有IIS和Apache么?本人想说的是:有self-host方便么?有无需安装,无需配置、随便高性能开跑好么?asp.net core里面都有了这个,没这个就没有逼格....(此处省略一万字),前面还研究tcp、http这个当然不能少了

 1 /****************************************************************************
 2 *Copyright (c) 2018 Microsoft All Rights Reserved.
 3 *CLR版本: 4.0.30319.42000
 4 *机器名称:WENLI-PC
 5 *公司名称:Microsoft
 6 *命名空间:SAEA.WebAPI.Http
 7 *文件名: HttpServer
 8 *版本号: V1.0.0.0
 9 *唯一标识:914acb72-d4c4-4fa1-8e80-ce2f83bd06f0
10 *当前的用户域:WENLI-PC
11 *创建人: yswenli
12 *电子邮箱:wenguoli_520@qq.com
13 *创建时间:2018/4/10 13:51:50
14 *描述:
15 *
16 *=====================================================================
17 *修改标记
18 *修改时间:2018/4/10 13:51:50
19 *修改人: yswenli
20 *版本号: V1.0.0.0
21 *描述:
22 *
23 *****************************************************************************/
24 using SAEA.Sockets.Interface;
25 using SAEA.WebAPI.Common;
26 using SAEA.WebAPI.Http.Net;
27 using System;
28 using System.Collections.Generic;
29 using System.IO;
30 using System.Text;
31
32 namespace SAEA.WebAPI.Http
33 {
34     class HttpServer
35     {
36         ServerSocket _serverSocket;
37
38         public HttpServer()
39         {
40             _serverSocket = new ServerSocket();
41             _serverSocket.OnRequested += _serverSocket_OnRequested;
42         }
43
44         public void Start(int port = 39654)
45         {
46             _serverSocket.Start(port);
47         }
48
49
50         private void _serverSocket_OnRequested(IUserToken userToken, string htmlStr)
51         {
52             var httpContext = HttpContext.CreateInstance(this, userToken, htmlStr);
53
54             var response = httpContext.Response;
55
56             response.End();
57         }
58
59         internal void Replay(IUserToken userToken, byte[] data)
60         {
61             _serverSocket.Reply(userToken, data);
62         }
63
64         internal void Close(IUserToken userToken)
65         {
66             _serverSocket.Disconnected(userToken);
67         }
68
69
70     }
71 }

  五、Controller:为了实现类似于mvc的效果Controller这个大名鼎鼎的当然不能少了,其在C#中使用非常少量的代码即可实现

 1 /****************************************************************************
 2 *Copyright (c) 2018 Microsoft All Rights Reserved.
 3 *CLR版本: 4.0.30319.42000
 4 *机器名称:WENLI-PC
 5 *公司名称:Microsoft
 6 *命名空间:SAEA.WebAPI.Mvc
 7 *文件名: Controller
 8 *版本号: V1.0.0.0
 9 *唯一标识:a303db7d-f83c-4c49-9804-032ec2236232
10 *当前的用户域:WENLI-PC
11 *创建人: yswenli
12 *电子邮箱:wenguoli_520@qq.com
13 *创建时间:2018/4/10 13:58:08
14 *描述:
15 *
16 *=====================================================================
17 *修改标记
18 *修改时间:2018/4/10 13:58:08
19 *修改人: yswenli
20 *版本号: V1.0.0.0
21 *描述:
22 *
23 *****************************************************************************/
24
25 using SAEA.WebAPI.Http;
26
27 namespace SAEA.WebAPI.Mvc
28 {
29     /// <summary>
30     /// WebApi控制器
31     /// </summary>
32     public abstract class Controller
33     {
34         public HttpContext HttpContext { get; set; }
35
36         /// <summary>
37         /// 返回Json
38         /// </summary>
39         /// <param name="data"></param>
40         /// <returns></returns>
41         protected JsonResult Json(object data)
42         {
43             return new JsonResult(data);
44         }
45         /// <summary>
46         /// 自定义内容
47         /// </summary>
48         /// <param name="data"></param>
49         /// <returns></returns>
50         protected ContentResult Content(string data)
51         {
52             return new ContentResult(data);
53         }
54
55
56         /// <summary>
57         /// 小文件
58         /// </summary>
59         /// <param name="filePath"></param>
60         /// <returns></returns>
61         protected FileResult File(string filePath)
62         {
63             return new FileResult(filePath);
64         }
65
66         /// <summary>
67         /// 空结果
68         /// </summary>
69         /// <returns></returns>
70         protected EmptyResult Empty()
71         {
72             return new EmptyResult();
73         }
74     }
75 }

  六、ActionResult:是mvc里面针对reponse结果进行了一个http格式的封装,本人主要实现了ContentResult、JsonResult、FileResult三个,至于其他的在WebAPI里基本上用不到。

 1 /****************************************************************************
 2 *Copyright (c) 2018 Microsoft All Rights Reserved.
 3 *CLR版本: 4.0.30319.42000
 4 *机器名称:WENLI-PC
 5 *公司名称:Microsoft
 6 *命名空间:SAEA.WebAPI.Mvc
 7 *文件名: JsonResult
 8 *版本号: V1.0.0.0
 9 *唯一标识:340c3ef0-2e98-4f25-998f-2bb369fa2794
10 *当前的用户域:WENLI-PC
11 *创建人: yswenli
12 *电子邮箱:wenguoli_520@qq.com
13 *创建时间:2018/4/10 16:48:06
14 *描述:
15 *
16 *=====================================================================
17 *修改标记
18 *修改时间:2018/4/10 16:48:06
19 *修改人: yswenli
20 *版本号: V1.0.0.0
21 *描述:
22 *
23 *****************************************************************************/
24 using SAEA.WebAPI.Common;
25 using System;
26 using System.Collections.Generic;
27 using System.Net;
28 using System.Text;
29
30 namespace SAEA.WebAPI.Mvc
31 {
32     public class JsonResult : ActionResult
33     {
34         public JsonResult(object model) : this(SerializeHelper.Serialize(model))
35         {
36
37         }
38         public JsonResult(string json) : this(json, Encoding.UTF8)
39         {
40
41         }
42
43         public JsonResult(string json, HttpStatusCode status)
44         {
45             this.Content = json;
46             this.ContentEncoding = Encoding.UTF8;
47             this.ContentType = "application/json; charset=utf-8";
48             this.Status = status;
49         }
50
51         public JsonResult(string json, Encoding encoding, string contentType = "application/json; charset=utf-8")
52         {
53             this.Content = json;
54             this.ContentEncoding = encoding;
55             this.ContentType = contentType;
56         }
57     }
58 }

  七、RouteTable:MVC里面有一个相当重要的概念叫约定优先,即为Controller、Action的名称是按某种规则来写编码的,其中将URL与自定义Controller对应起来的缓存映射就是RouteTable,并且作为缓存,也能极大的提升访问性能。当然这里并没有严格按照asp.net mvc里面的routetable来设计,而是根据只是实现webapi,并使用缓存反射结构能来实现的,并且只有约定,没有配置。

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.WebAPI.Mvc
  7 *文件名: RouteTable
  8 *版本号: V1.0.0.0
  9 *唯一标识:1ed5d381-d7ce-4ea3-b8b5-c32f581ad49f
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/4/12 10:55:31
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/4/12 10:55:31
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 using System;
 25 using System.Collections.Generic;
 26 using System.Linq;
 27 using System.Reflection;
 28 using System.Text;
 29
 30 namespace SAEA.WebAPI.Mvc
 31 {
 32     /// <summary>
 33     /// SAEA.WebAPI路由表
 34     /// </summary>
 35     public static class RouteTable
 36     {
 37         static object _locker = new object();
 38
 39         static List<Routing> _list = new List<Routing>();
 40
 41
 42         /// <summary>
 43         /// 获取routing中的缓存
 44         /// 若不存在则创建
 45         /// </summary>
 46         /// <param name="controllerType"></param>
 47         /// <param name="controllerName"></param>
 48         /// <param name="actionName"></param>
 49         /// <param name="isPost"></param>
 50         /// <returns></returns>
 51         public static Routing TryGet(Type controllerType, string controllerName, string actionName, bool isPost)
 52         {
 53             lock (_locker)
 54             {
 55                 var list = _list.Where(b => b.ControllerName.ToLower() == controllerName.ToLower() && b.ActionName.ToLower() == actionName.ToLower() && b.IsPost == isPost).ToList();
 56
 57                 if (list == null || list.Count == 0)
 58                 {
 59                     var routing = new Routing()
 60                     {
 61                         ControllerName = controllerName,
 62                         ActionName = actionName,
 63                         IsPost = isPost
 64                     };
 65
 66                     var actions = controllerType.GetMethods().Where(b => b.Name.ToLower() == actionName.ToLower()).ToList();
 67
 68                     if (actions == null || actions.Count == 0)
 69                     {
 70                         throw new Exception($"{controllerName}/{actionName}找不到此action!");
 71                     }
 72                     else if (actions.Count > 2)
 73                     {
 74                         throw new Exception($"{controllerName}/{actionName}有多个重复的的action!");
 75                     }
 76                     else
 77                     {
 78                         routing.Instance = System.Activator.CreateInstance(controllerType);
 79
 80                         //类上面的过滤
 81                         var attrs = controllerType.GetCustomAttributes(true);
 82
 83                         if (attrs != null)
 84                         {
 85                             var attr = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").FirstOrDefault();
 86
 87                             routing.Atrr = attr;
 88
 89                         }
 90                         else
 91                         {
 92                             routing.Atrr = null;
 93                         }
 94
 95                         routing.Action = actions[0];
 96
 97                         //action上面的过滤
 98                         if (routing.Atrr == null)
 99                         {
100                             attrs = actions[0].GetCustomAttributes(true);
101
102                             if (attrs != null)
103                             {
104                                 var attr = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").FirstOrDefault();
105
106                                 routing.Atrr = attr;
107
108                             }
109                             else
110                             {
111                                 routing.Atrr = null;
112                             }
113                         }
114                     }
115                     _list.Add(routing);
116                     return routing;
117                 }
118                 else if (list.Count > 1)
119                 {
120                     throw new Exception("500");
121                 }
122                 return list.FirstOrDefault();
123             }
124         }
125     }
126
127 }

  在MVC的思想里面ActionFilterAtrribute的这个AOP设计也一直伴随左右,比如记日志、黑名单、权限、验证、限流等等功能,所以路由的时候也会缓存这个。至此一些关键性的地方都已经弄的差不多了,为了更好的了解上面说的这些,下面是vs2017中项目的结构截图:

  纯粹干净单码,无任何晦涩内容,如果对mvc有一定了解的,这个差不多可以NoNotes,接下来就是按asp.net mvc命名方式,写个测试webapi看看情况,首先还是测试项目结构图:

  

  HomeController里面按asp.net mvc的习惯来编写代码:

  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.WebAPITest.Controllers
  7 *文件名: HomeController
  8 *版本号: V1.0.0.0
  9 *唯一标识:e00bb57f-e3ee-4efe-a7cf-f23db767c1d0
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/4/10 16:43:26
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/4/10 16:43:26
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 using SAEA.WebAPI.Mvc;
 25 using SAEA.WebAPITest.Attrubutes;
 26 using SAEA.WebAPITest.Model;
 27
 28 namespace SAEA.WebAPITest.Controllers
 29 {
 30     /// <summary>
 31     /// 测试实例代码
 32     /// </summary>
 33     //[LogAtrribute]
 34     public class HomeController : Controller
 35     {
 36         /// <summary>
 37         /// 日志拦截
 38         /// 内容输出
 39         /// </summary>
 40         /// <returns></returns>
 41         //[Log2Atrribute]
 42         public ActionResult Index()
 43         {
 44             return Content("Hello,I'm SAEA.WebAPI!");
 45         }
 46         /// <summary>
 47         /// 支持基本类型参数
 48         /// json序列化
 49         /// </summary>
 50         /// <param name="id"></param>
 51         /// <returns></returns>
 52         public ActionResult Get(int id)
 53         {
 54             return Json(new { Name = "yswenli", Sex = "男" });
 55         }
 56         /// <summary>
 57         /// 底层对象调用
 58         /// </summary>
 59         /// <returns></returns>
 60         public ActionResult Show()
 61         {
 62             var response = HttpContext.Response;
 63
 64             response.Content_Type = "text/html; charset=utf-8";
 65
 66             response.Write("<h3>测试一下那个response对象使用情况!</h3>参考消息网4月12日报道外媒称,法国一架“幻影-2000”战机意外地对本国一家工厂投下了...");
 67
 68             response.End();
 69
 70             return Empty();
 71         }
 72
 73         [HttpGet]
 74         public ActionResult Update(int id)
 75         {
 76             return Content($"HttpGet Update id:{id}");
 77         }
 78         /// <summary>
 79         /// 基本类型参数、实体混合填充
 80         /// </summary>
 81         /// <param name="isFemale"></param>
 82         /// <param name="userInfo"></param>
 83         /// <returns></returns>
 84         [HttpPost]
 85         public ActionResult Update(bool isFemale, UserInfo userInfo = null)
 86         {
 87             return Json(userInfo);
 88         }
 89         [HttpPost]
 90         public ActionResult Test()
 91         {
 92             return Content("httppost test");
 93         }
 94         /// <summary>
 95         /// 文件输出
 96         /// </summary>
 97         /// <returns></returns>
 98         public ActionResult Download()
 99         {
100             return File(HttpContext.Server.MapPath("/Content/Image/c984b2fb80aeca7b15eda8c004f2e0d4.jpg"));
101         }
102     }
103 }

  增加一个LogAtrribute打印一些内容:

 1 /****************************************************************************
 2 *Copyright (c) 2018 Microsoft All Rights Reserved.
 3 *CLR版本: 4.0.30319.42000
 4 *机器名称:WENLI-PC
 5 *公司名称:Microsoft
 6 *命名空间:SAEA.WebAPITest.Common
 7 *文件名: LogAtrribute
 8 *版本号: V1.0.0.0
 9 *唯一标识:2a261731-b8f6-47de-b2e4-aecf2e0e0c0f
10 *当前的用户域:WENLI-PC
11 *创建人: yswenli
12 *电子邮箱:wenguoli_520@qq.com
13 *创建时间:2018/4/11 13:46:42
14 *描述:
15 *
16 *=====================================================================
17 *修改标记
18 *修改时间:2018/4/11 13:46:42
19 *修改人: yswenli
20 *版本号: V1.0.0.0
21 *描述:
22 *
23 *****************************************************************************/
24 using SAEA.Commom;
25 using SAEA.WebAPI.Http;
26 using SAEA.WebAPI.Mvc;
27
28 namespace SAEA.WebAPITest.Attrubutes
29 {
30     public class LogAtrribute : ActionFilterAttribute
31     {
32         /// <summary>
33         /// 执行前
34         /// </summary>
35         /// <param name="httpContext"></param>
36         /// <returns>返回值true为继续,false为终止</returns>
37         public override bool OnActionExecuting(HttpContext httpContext)
38         {
39             return true;
40         }
41
42         /// <summary>
43         /// 执行后
44         /// </summary>
45         /// <param name="httpContext"></param>
46         /// <param name="result"></param>
47         public override void OnActionExecuted(HttpContext httpContext, ActionResult result)
48         {
49             ConsoleHelper.WriteLine($"请求地址:{httpContext.Request.Query},回复内容:{result.Content}");
50         }
51     }
52 }

  program.cs Main中启动一下服务:

1 MvcApplication mvcApplication = new MvcApplication();
2
3 mvcApplication.Start();

  最后F5跑起来看看效果:

  使用Apache ab.exe压测一下性能如何:

  至此,一个简洁、高效的WebApi就初步完成了!

转载请标明本文来源:http://www.cnblogs.com/yswenli/p/8858669.html 
更多内容欢迎star作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是yswenli 。

面试题随笔

 

innerHTML与innerText区别

innerHTML指的是从对象的起始位置到终止位置的全部内容,包括Html标签。
innerText   指的是从起始位置到终止位置的内容,但它去除Html标签。

setInterval和setTimeout的使用区别

setInterval在执行完一次代码之后,经过了那个固定的时间间隔,它还会自动重复执行代码,而setTimeout只执行一次那段代码。

MVC有几种向页面绑定数据的方法

1、指定页面数据的强类型Model 
2、使用ViewData绑定到页面 
3、使用ViewBag绑定到页面

字符串反转

 

var str = 'abcdefgh';

第一种

split()方法将一个字符串对象的每个字符拆出来,并且将每个字符串当成数组的每个元素reverse()方法用来改变数组,将数组中的元素倒个序排列,第一个数组元素成为最后一个,最后一个变成第一个join()方法将数组中的所有元素边接成一个字符串

 console.log(str.split('').reverse().join(''));

第二种

for循环给原字符串做一个递减遍历,然后将遍历的字符串重新合并成一个新字符串


function reverse2() {
   var str2 = '';
   for (var  i >= 0;  i = str.length - 1;i--){
       str2 += str[i];
   }
   console.log(str2);
}
 reverse2();

第三种

 function reverse3(str){
    var arr = str.split('');
    var i = 0,j = arr.length-1;
    while(i<j){
        tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
        i++;
        j--;
    }
    console.log(arr.join(""))
    return arr.join("");
}
reverse3(str);

再提供两种方式:
直接string.Concat(str.Reverse())
或使用Stack也可以

线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转的相关教程结束。

《线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转.doc》

下载本文的Word格式文档,以方便收藏与打印。