红俊's profile蝈蝈俊的共享空间PhotosBlogListsMore Tools Help

Blog


    July 30

    对 ActiveMQ .NET 1.1.0 组件接受中文信息乱码的修复

    最近在使用 Apache ActiveMQ 消息队列做一些工作,在使用的时候,发现发送消息的内容是中文时,获得消息时,获得的是乱码。

    发送和接收我使用的是 ActiveMQ .NET 1.1.0 组件。

    分析原因进去后,发现是这个开源组件的 Apache.NMS.ActiveMQ 组件的 \Apache.NMS.ActiveMQ-1.1.0-src\src\main\csharp\Transport\Stomp\StompWireFormat.cs 文件对应的 StompWireFormat 类的 public Object Unmarshal(BinaryReader dis) 方法中有错误。

    错误在该文件的 166 行

    之前的错误代码如下:

    public Object Unmarshal(BinaryReader dis)
    {
    	string command;
    	do {
    		command = ReadLine(dis);
    	}
    	while (command == "");
    	
    	Tracer.Debug("<<<< command: " + command);
    	
    	IDictionary headers = new Hashtable();
    	string line;
    	while ((line = ReadLine(dis)) != "")
    	{
    		int idx = line.IndexOf(':');
    		if (idx > 0)
    		{
    			string key = line.Substring(0, idx);
    			string value = line.Substring(idx + 1);
    			headers[key] = value;
    			
    			Tracer.Debug("<<<< header: " + key + " = " + value);
    		}
    		else
    		{
    			// lets ignore this bad header!
    		}
    	}
    	byte[] content = null;
    	string length = ToString(headers["content-length"]);
    	if (length != null)
    	{
    		int size = Int32.Parse(length);
    		content = dis.ReadBytes(size);
    		// Read the terminating NULL byte for this frame.
    		int nullByte = dis.Read();
    		if(nullByte != 0)
    		{
    			Tracer.Debug("<<<< error reading frame null byte.");
    		}
    	}
    	else
    	{
    		MemoryStream ms = new MemoryStream();
    		int nextChar;
    		while((nextChar = dis.Read()) != 0)
    		{
    		    if( nextChar < 0 )
    		    {
    		        // EOF ??
    		        break;
    		    }
    			ms.WriteByte((byte)nextChar);
    		}
                    content = ms.ToArray();
    	}
    	Object answer = CreateCommand(command, headers, content);
    	Tracer.Debug("<<<< received: " + answer);
    	return answer;
    }
    
    internal String ReadLine(BinaryReader dis)
    {
        MemoryStream ms = new MemoryStream();
        while (true)
        {
            int nextChar = dis.Read();
            if (nextChar < 0)
            {
                throw new IOException("Peer closed the stream.");
            }
            if( nextChar == 10 )
            {
                break;
            }
            ms.WriteByte((byte)nextChar);
        }
        byte[] data = ms.ToArray();
        return encoding.GetString(data, 0, data.Length);
    }

    这个源代码文件可以在下面地址看到:

    https://svn.apache.org/repos/asf/activemq/activemq-dotnet/Apache.NMS.ActiveMQ/tags/1.1.0/src/main/csharp/Transport/Stomp/StompWireFormat.cs

    正确的代码如下:

    internal string ReadLine(NetworkStream ns)
    {
        MemoryStream ms = new MemoryStream();
        while (true)
        {
            int nextChar = ns.ReadByte();
            if (nextChar < 0)
            {
                throw new IOException("Peer closed the stream.");
            }
            if (nextChar == 10)
            {
                break;
            }
            ms.WriteByte((byte)nextChar);
        }
        byte[] data = ms.ToArray();
        return encoding.GetString(data, 0, data.Length);
    }
    public Object Unmarshal(BinaryReader dis)
    {
        NetworkStream ns = dis.BaseStream as NetworkStream;
        if (ns == null) return null;
        if (!ns.CanRead) return null;
    
        // 读取 command 信息
        string command;
        do
        {
            command = ReadLine(ns);
        }
        while (command == "");
    
        Tracer.Debug("<<<< command: " + command);
    
        // 读取 header 信息
        IDictionary headers = new Hashtable();
        string line;
        while ((line = ReadLine(ns)) != "")
        {
            int idx = line.IndexOf(':');
            if (idx > 0)
            {
                string key = line.Substring(0, idx);
                string value = line.Substring(idx + 1);
                headers[key] = value;
    
                Tracer.Debug("<<<< header: " + key + " = " + value);
            }
            else
            {
                // lets ignore this bad header!
            }
        }
    
        // 读取消息内容
        MemoryStream ms = new MemoryStream();
        do
        {
            int t = ns.ReadByte();
            if (t <= 0) break;
            ms.WriteByte((byte)t);
        }
        while (ns.DataAvailable);
    
        byte[] content = ms.ToArray();
                           
        Object answer = CreateCommand(command, headers, content);
        Tracer.Debug("<<<< received: " + answer);
        return answer;
    }

    之前的错误代码,在发送“a1郭红俊b2” 这样的中英文数字混合的信息时,发送时,发送的 byte数组 信息如下:

    97,49,233,131,173,231,186,162,228,191,138,98,50

    接受时,接受到的 byte 数组信息就变成了

    97,49,239,191,189,239,191,189,98,50

     

    原先的 BinaryReader dis 其实是个这个开源组件自己写的派生自BinaryReader 的  OpenWireBinaryReader 类。这个类有很多不完善的地方。

    这部分的逻辑可以在 TcpTransport 类的下面调用中看到

    private readonly Socket socket;
    private BinaryReader socketReader;

    socketReader = new OpenWireBinaryReader(new NetworkStream(socket));

    原先的 OpenWireBinaryReader  不完善的地方:

    1、传送中文时,会丢数据;ms.WriteByte((byte)nextChar); 会让本来nextChar对应的 byte 数组,只取了数组的第一项,数组的其他项则丢失了;

    2、编码混乱,unicode 和 utf-8 转换有问题。

     

    参考资料:

    Apache ActiveMQ
    http://activemq.apache.org/

    ActiveMQ .NET
    http://activemq.apache.org/nms/

    July 27

    Path.Combine("d:\\projects", "\\20090716\\11")); 的计算结果

    下面代码执行的结果一样么?

    Console.WriteLine(Path.Combine("d:\\projects", "\\20090716\\11"));
    Console.WriteLine(Path.Combine("d:\\projects", "/20090716/11")); Console.WriteLine(Path.Combine("d:\\projects", "20090716\\11"));

    答案是不一样的,执行的结果分别是:

    \20090716\11
    /20090716/11
    d:\projects\20090716\11

    为何会这样呢??

    MSDN 中文上的解释如下,红色字体是导致这个问题的根本原因:

    如果 path2 不包括根(例如,如果 path2 没有以分隔符或驱动器规格起始),则结果是两个路径的串联,具有介于其间的分隔符。如果 path2 包括根,则返回 path2。

    MSDN 英文的解释如下:

    If path2 does not include a root (for example, if path2 does not start with a separator character or a drive specification), the result is a concatenation of the two paths, with an intervening separator character. If path2 includes a root, path2 is returned.

    显然,除了 除了驱动器开头的路径会认为是根, / 或者 \ 开头的也被认为是根,所以才有上面的计算结果

    比如: \\myserver\myshare\foo\bar\baz.txt  这个路径中, 我们就可以看到 / 确实有必要作为根路径

    在这里 / 或者 \ 被等同处理,是因为各个操作系统上确实用的不一样。如下:

    Path.DirectorySeparatorChar 字段 
    提供平台特定的字符,该字符用于在反映分层文件系统组织的路径字符串中分隔目录级别。
    该字段的值在 Unix 上为斜杠(“/”),在 Windows 和 Macintosh 操作系统上为反斜杠(“\”)。

    Path.AltDirectorySeparatorChar 字段
    提供平台特定的替换字符,该替换字符用于在反映分层文件系统组织的路径字符串中分隔目录级别。
    该字段的值在 Unix 上为反斜杠(“\”),在 Windows 和 Macintosh 操作系统上为斜杠(“/”)。

    Path.VolumeSeparatorChar 字段
    提供平台特定的卷分隔符。
    该字段的值在 Windows 和 Macintosh 上为冒号(“:”),在 Unix 操作系统上为斜杠(“/”)。

    参考资料:

    Path.Combine 方法
    http://msdn.microsoft.com/zh-cn/library/fyy7a5kt.aspx

    http://msdn.microsoft.com/en-us/library/fyy7a5kt.aspx

     

    Path.Combine (合并两个路径字符串)方法的一些使用细节
    http://blog.joycode.com/ghj/archive/2006/08/07/79611.joy

    July 16

    学习笔记:11种行为型设计模式简单对比

    这几种行为型设计模式分别为:

    • Template Method 模板方法模式
    • Command 命令模式
    • Interpreter 解释器模式
    • Mediator 中介者模式
    • Iterator 迭代器模式
    • Observer 观察者模式
    • Chain Of Responsibility 职责链模式
    • Memento 备忘录模式
    • State 状态模式
    • Strategy 策略模式
    • Visitor 访问者模式

    对比:

    • Template Method模式封装算法结构,支持算法子步骤变化
    • Strategy 策略模式注重封装算法,支持算法的变化
    • State模式注重封装与状态相关的行为,支持状态的变化
    • Memento备忘录模式注重封装对象状态变化,支持状态保存/恢复
    • Mediator 中介者模式注重封装对象间的交互,支持对象交互的变化
    • Chain Of Responsibility 模式注重封装对象责任,支持责任的变化
    • Command 模式注重将请求封装为对象,支持请求的变化
    • Iterator 迭代器模式注重封装集合对象内部结构,支持集合的变化
    • Interpreter 解释器模式注重封装特定领域变化,支持领域问题的频繁变化
    • Observer 模式注重封装对象通知,支持通信对象的变化
    • Visitor 模式注重封装对象操作变化,支持在运行时为类层次结构动态添加新的操作。

    参考:

    学习笔记:7种结构型设计模式简单对比
    http://blog.joycode.com/ghj/archive/2009/06/08/115607.joy

    学习笔记:5种创建型设计模式简单对比
    http://blog.joycode.com/ghj/archive/2009/05/08/115570.joy

    Gof 23 中模式关系图
    http://www.blogjava.net/images/blogjava_net/fantasyamin/design_pattern_relation.png

    July 15

    同名函数的 带 params 参数 与 不带 params 参数的执行优先级

    看下面代码,编译会不会报错,如果不报错,执行的结果是啥?

     using System;
    
     class Program
     {
         static void Main(string[] args)
         {
             Console.WriteLine(GetIP());
             Console.WriteLine(GetIP("a"));
             Console.WriteLine(GetIP("a", "b"));
             Console.WriteLine(GetIP("a", "b","c"));
             Console.ReadLine();
         }
    
         public static string GetIP(params string[] ipArr)
         {
             return "3";
         }
    
         public static string GetIP(string ip)
         {
             return "2";
         }
    
         public static string GetIP()
         {
             return "1";
         }
     }

    答案:

    编译不会报错,执行结果:

    1
    2
    3
    3

    使用ILdasm察看,显然,params 是一个语法糖,编译时,如果发现有完全匹配的函数,就优先调用完全匹配的函数,否则就调用带params 参数的函数。

    这个逻辑是编译时确定的,执行时根本不考虑这个问题。

    July 03

    Connection Pool Timeout 与 Connect Timeout = 0

    最近在做多线程处理数据库的程序时,这个程序总是会报如下错误:

    超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。

    System.Data

       在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)

       在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)

       在 System.Data.SqlClient.SqlConnection.Open()

    仔细用 SQL Server Management Studio 中的 Activity Monitor 查看数据库链接,竟然是只有2,3个数据库链接时,就报上述错误,很是怪异。

    一步步删除掉代码,反复试验后,竟然是数据库链接字符串中的 Connect Timeout=0 来作怪的。

    比如下述数据库链接字符串就会出现上述问题,

    Persist Security Info=False;Integrated Security=SSPI;Initial Catalog=DB1;server=(local);Connect Timeout=0

    而把数据库链接字符串修改为
    Persist Security Info=False;Integrated Security=SSPI;Initial Catalog=DB1;server=(local)

    就不会有问题了。

     

    这个bug在微软的反馈中可以看到,如下:

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331882&wa=wsignin1.0

    其中微软的官方反馈是:

    The fix was submitted to the source branch of the next major .Net release.

    Microsoft 在 2008/8/15 11:31 发送

    照这么说,估计.net 4.0 中会修复这个bug。

     

    参考资料:

    Why Does a Connection Pool Overflow?
    http://msdn.microsoft.com/en-us/library/aa175863%28SQL.80%29.aspx

    Trace a SqlConnection - Connection Pooling issues
    http://www.experts-exchange.com/Software/Server_Software/Web_Servers/Q_22589733.html

    Fixing connection pooling timeout exceptions on third-party code
    http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=14

    Connection Pooling and the "Timeout expired" exception FAQ
    http://blogs.msdn.com/angelsb/archive/2004/08/25/220333.aspx

    ADO.NET Connection Pooling at a Glance
    http://blog.csdn.net/tuwen/archive/2008/05/28/2490299.aspx

    FIX: Sp_reset_connection Does Not Reset the Rowcount Settings for the DELETE and UPDATE Statements
    http://support.microsoft.com/kb/310617/

    ADO.NET数据连接池
    http://tech.it168.com/db/s/2006-10-18/200610181013413.shtml

    关于ADO.Net连接池(Connection Pool)的一些个人见解
    http://www.cnblogs.com/rickie/archive/2004/10/02/48546.aspx

    How to: Open Activity Monitor (SQL Server Management Studio)
    http://msdn.microsoft.com/en-us/library/ms175518.aspx

    Connect Timeout = 0 / Connection Pool Timeout

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331882&wa=wsignin1.0