Home

利用 GAWK 实现模板文件替换

对于产品开发需要的情况下,我们通常会选择某些模板引擎生成一些文件,如 StringTemplate、Volecity,但是如果我们只是需要完成一些简单重复或者自动化的工作的话,依旧使用这样重量级的东西未免杀鸡用牛刀。

好在有个强大的 GNU AWK!
于是乎,花了一些时间来写了一个 awk 脚本,实现如下功能。 从 ini 文件读取键值,通过键名替换值。

其实就是一个简单的模板功能。

现在我们看一个简单的场景:
文件内容 Talk.tpl

1$(Famale) : Who are you!  
2$(Male_FirstName) : $(Male_FirstName), $(Male_FirstName) $(Male_LastName).

INI字典文件 Conf.ini

1Famale=Jane  
2Male_FirstName=James  
3Male_LastName=Bond

执行以下命令:

1gawk -f DictReplace.awk Conf.ini Talk.tpl > Talk.txt

则会生成文件 Talk.txt

1Jane : Who are you!  
2James : James, James Bond.

awk 脚本 DictReplace.awk

 1#!/usr/bin/gawk -f
 2#<code>
 3#<owner name="Zealic" email="[email protected]"/>
 4#<version>1.0</version>
 5#</code>
 6BEGIN {
 7fileCount = 0;
 8fullContent[0] = "";
 9fullLength = 0;
10}
11
12# MAIN  
13{
14# File flag
15if(FNR == 1)
16{
17  fileCount++;
18}
19# Load dict
20if(fileCount == 1 && $0 ~ /w+=.*/)
21{
22  len = length($0);
23  klen = index($0, "=") - 1;
24  key = substr($0, 0, klen);
25  value = substr($0, klen + 2, len);
26  repDict[key] = value;
27}
28else if(fileCount == 2)
29{
30  # Replace and store
31  outValue = $0;
32  for(dKey in repDict)
33  {
34    # Dynamic reglur exp
35    nowRegex = "\$\(" dKey "\)";
36    dValue = repDict[dKey];
37    gsub(nowRegex, dValue, outValue);
38  }
39  fullContent[fullLength] = outValue;
40  fullLength++;
41}
42}
43
44END {
45# Output result
46for(i=0;i<fullLength;i++)
47{
48  print(fullContent[i]);
49}
50}

上述脚本代码在 UnxUtils 的 gawk 下执行通过。
你可以直接在这里下载完整内容查看结果并获得 gawk.exe。

AWK 的动态构造正则真要命,搞了半天才发现,直接构造字符串就可以,然后直接放到参数中就可以作为正则使用,就是上面代码的蓝色部分。不过也基本学会 AWK,以后又有件利器可用啦。

稍后再测试能否在 Linux 下工作。

View Comments

妙用扩展方法

1.参数检查

对于写库的大多数人来说,参数检查是一件极其麻烦的事情,我们需要重复写以下代码:

1public void Foo(string name)
2{
3  if(name == null) throw new ArgumentNullException("name");
4}

现在我们有了扩展方法,则可以这样:

 1/// <summary>
 2/// 参数检查扩展方法类
 3/// </summary>
 4public static class RequireExtension
 5{
 6  public static void RequireNotNull(this object obj, string name)
 7  {
 8    if(name == null) throw new ArgumentNullException("name");
 9    if(obj is null) throw new ArgumentNullException(name);
10  }
11}
12
13// 示例
14public void Foo(string name)
15{
16  name.RequireNotNull("name");
17}

2.本地化

当我们使用枚举,经常需要在枚举和本地化字符串之间转换,或者编码规范为英文,需要将枚举转换为中文,扩展方法可以让我们这样。

 1/// <summary>
 2/// 本地化后的友好名称
 3/// </summary>
 4public class LocalizedNameAttribute : Attribute
 5{
 6  public string Name { get;set;}
 7}
 8
 9/// <summary>
10/// 扩展枚举
11/// </summary>
12public static class EnumExtension
13{
14  public static void ToLocalized(this Enum e)
15  {
16    Type type = member.GetType();
17
18    foreach (FieldInfo field in type.GetFields())
19    {
20      if (!field.IsDefined(typeof(LocalizedNameAttribute), false))
21        continue;
22      LocalizedNameAttribute att = (LocalizedNameAttribute)Attribute.
23      GetCustomAttribute(field, typeof(LocalizedNameAttribute));
24      return att.Name;
25    }
26    return member.ToString();
27  }
28}
29
30/// <summary>
31/// 性别
32/// </summary>
33public enum Gender
34{
35  [LocalizedName(Name = "男")]
36  Male,
37  [LocalizedName(Name = "女")]
38  Famale,
39  [LocalizedName(Name = "东方不败")]
40  EastNotFail,
41}

我们就可以 Gender.Famle.ToLocalized() 直接获取本地化后的字符串。

3.强类型转换扩展

利用强类型转换,我们可以在代码中少写很多强制转换和类型检查代码。
我们经常写出这样又臭又长的代码:

1MyAttribute att = (MyAttribute)Attribute
2  .GetCustomAttribute(methodInfo, typeof(MyAttribute));

现在,我们可以这样:

1MyAttribute att = methodInfo.GetAttribute<MyAttribute>();

今天先到这里,下次有经验再分享。

View Comments

Socket 死连接详解

当使用 Socket 进行通信时,由于各种不同的因素,都有可能导致死连接停留在服务器端,假如服务端需要处理的连接较多,就有可能造成服务器资源严重浪费,对此,本文将阐述其原理以及解决方法。

在写 Socket 进行通讯时,我们必须预料到各种可能发生的情况并对其进行处理,通常情况下,有以下两种情况可能造成死连接:

  • 通讯程序编写不完善
  • 网络/硬件故障

a) 通讯程序编写不完善

这里要指出的一点就是,绝大多数程序都是由于程序编写不完善所造成的死连接,即对 Socket 未能进行完善的管理,导致占用端口导致服务器资源耗尽。当然,很多情况下,程序可能不是我们所写,而由于程序代码的复杂、杂乱等原因所导致难以维护也是我们所需要面对的。

网上有很多文章都提到 Socket 长时间处于 CLOSE_WAIT 状态下的问题,说可以使用 Keepalive 选项设置 TCP 心跳来解决,但是却发现设置选项后未能收到效果 。

因此,这里我分享出自己的解决方案:

Windows 中对于枚举系统网络连接有一些非常方便的 API:

  • GetTcpTable : 获得 TCP 连接表
  • GetExtendedTcpTable : 获得扩展后的 TCP 连接表,相比 GetTcpTable 更为强大,可以获取与连接的进程 ID
  • SetTcpEntry : 设置 TCP 连接状态,但据 MSDN 所述,只能设置状态为 DeleteTcb,即删除连接

相信大多数朋友看到这些 API ,就已经了解到我们下一步要做什么了;枚举所有 TCP 连接,筛选出本进程的连接,最后判断是否 CLOSE_WAIT 状态,如果是,则使用 SetTcpEntry 关闭。

其实 Sysinternal 的 TcpView 工具也是应用上述 API 实现其功能的,此工具为我常用的网络诊断工具,同时也可作为一个简单的手动式网络防火墙。

下面来看 Zealic 封装后的代码:


TcpManager.cs

  1/**
  2<code>
  3  <revsion>$Rev: 0 $</revision>
  4  <owner name="Zealic" mail="rszealic(at)gmail.com" />
  5</code>
  6**/
  7using System;
  8using System.Collections.Generic;
  9using System.Diagnostics;
 10using System.Net;
 11using System.Net.NetworkInformation;
 12using System.Runtime.InteropServices;
 13
 14
 15namespace Zealic.Network
 16{
 17    /// <summary>
 18    /// TCP 管理器
 19    /// </summary>
 20    public static class TcpManager
 21    {
 22        #region PInvoke define
 23        private const int TCP_TABLE_OWNER_PID_ALL = 5;
 24
 25        [DllImport("iphlpapi.dll", SetLastError = true)]
 26        private static extern uint GetExtendedTcpTable(
 27            IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, int tblClass, int reserved);
 28
 29        [DllImport("iphlpapi.dll")]
 30        private static extern int SetTcpEntry(ref MIB_TCPROW pTcpRow);
 31
 32
 33        [StructLayout(LayoutKind.Sequential)]
 34        private struct MIB_TCPROW
 35        {
 36            public TcpState dwState;
 37            public int dwLocalAddr;
 38            public int dwLocalPort;
 39            public int dwRemoteAddr;
 40            public int dwRemotePort;
 41        }
 42
 43        [StructLayout(LayoutKind.Sequential)]
 44        private struct MIB_TCPROW_OWNER_PID
 45        {
 46            public TcpState dwState;
 47            public uint dwLocalAddr;
 48            public int dwLocalPort;
 49            public uint dwRemoteAddr;
 50            public int dwRemotePort;
 51            public int dwOwningPid;
 52        }
 53
 54        [StructLayout(LayoutKind.Sequential)]
 55        private struct MIB_TCPTABLE_OWNER_PID
 56        {
 57            public uint dwNumEntries;
 58            private MIB_TCPROW_OWNER_PID table;
 59        }
 60        #endregion
 61
 62        private static MIB_TCPROW_OWNER_PID[] GetAllTcpConnections()
 63        {
 64            const int NO_ERROR = 0;
 65            const int IP_v4 = 2;
 66            MIB_TCPROW_OWNER_PID[] tTable = null;
 67            int buffSize = 0;
 68            GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, IP_v4, TCP_TABLE_OWNER_PID_ALL, 0);
 69            IntPtr buffTable = Marshal.AllocHGlobal(buffSize);
 70            try
 71            {
 72                if (NO_ERROR != GetExtendedTcpTable(buffTable, ref buffSize, true, IP_v4, TCP_TABLE_OWNER_PID_ALL, 0)) return null;
 73                MIB_TCPTABLE_OWNER_PID tab =
 74                    (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));
 75                IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
 76                tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];
 77
 78                int rowSize = Marshal.SizeOf(typeof(MIB_TCPROW_OWNER_PID));
 79                for (int i = 0; i < tab.dwNumEntries; i++)
 80                {
 81                    MIB_TCPROW_OWNER_PID tcpRow =
 82                        (MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));
 83                    tTable[i] = tcpRow;
 84                    rowPtr = (IntPtr)((int)rowPtr + rowSize);
 85                }
 86            }
 87            finally
 88            {
 89                Marshal.FreeHGlobal(buffTable);
 90            }
 91            return tTable;
 92        }
 93
 94        private static int TranslatePort(int port)
 95        {
 96            return ((port & 0xFF) << 8 | (port & 0xFF00) >> 8);
 97        }
 98
 99        public static bool Kill(TcpConnectionInfo conn)
100        {
101            if (conn == null) throw new ArgumentNullException("conn");
102            MIB_TCPROW row = new MIB_TCPROW();
103            row.dwState = TcpState.DeleteTcb;
104#pragma warning disable 612,618
105            row.dwLocalAddr = (int)conn.LocalEndPoint.Address.Address;
106#pragma warning restore 612,618
107            row.dwLocalPort = TranslatePort(conn.LocalEndPoint.Port);
108#pragma warning disable 612,618
109            row.dwRemoteAddr = (int)conn.RemoteEndPoint.Address.Address;
110#pragma warning restore 612,618
111            row.dwRemotePort = TranslatePort(conn.RemoteEndPoint.Port);
112            return SetTcpEntry(ref row) == 0;
113        }
114
115        public static bool Kill(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
116        {
117            if (localEndPoint == null) throw new ArgumentNullException("localEndPoint");
118            if (remoteEndPoint == null) throw new ArgumentNullException("remoteEndPoint");
119            MIB_TCPROW row = new MIB_TCPROW();
120            row.dwState = TcpState.DeleteTcb;
121#pragma warning disable 612,618
122            row.dwLocalAddr = (int)localEndPoint.Address.Address;
123#pragma warning restore 612,618
124            row.dwLocalPort = TranslatePort(localEndPoint.Port);
125#pragma warning disable 612,618
126            row.dwRemoteAddr = (int)remoteEndPoint.Address.Address;
127#pragma warning restore 612,618
128            row.dwRemotePort = TranslatePort(remoteEndPoint.Port);
129            return SetTcpEntry(ref row) == 0;
130        }
131
132
133        public static TcpConnectionInfo[] GetTableByProcess(int pid)
134        {
135            MIB_TCPROW_OWNER_PID[] tcpRows = GetAllTcpConnections();
136            if (tcpRows == null) return null;
137            List<TcpConnectionInfo> list = new List<TcpConnectionInfo>();
138            foreach (MIB_TCPROW_OWNER_PID row in tcpRows)
139            {
140                if (row.dwOwningPid == pid)
141                {
142                    int localPort = TranslatePort(row.dwLocalPort);
143                    int remotePort = TranslatePort(row.dwRemotePort);
144                    TcpConnectionInfo conn =
145                        new TcpConnectionInfo(
146                            new IPEndPoint(row.dwLocalAddr, localPort),
147                            new IPEndPoint(row.dwRemoteAddr, remotePort),
148                            row.dwState);
149                    list.Add(conn);
150                }
151            }
152            return list.ToArray();
153        }
154
155        public static TcpConnectionInfo[] GetTalbeByCurrentProcess()
156        {
157            return GetTableByProcess(Process.GetCurrentProcess().Id);
158        }
159
160    }
161}

TcpConnectionInfo.cs

 1/**
 2<code>
 3  <revsion>$Rev: 608 $</revision>
 4  <owner name="Zealic" mail="rszealic(at)gmail.com" />
 5</code>
 6**/
 7using System;
 8using System.Collections.Generic;
 9using System.Net;
10using System.Net.NetworkInformation;
11
12
13namespace Zealic.Network
14{
15    /// <summary>
16    /// TCP 连接信息
17    /// </summary>
18    public sealed class TcpConnectionInfo : IEquatable<TcpConnectionInfo>, IEqualityComparer<TcpConnectionInfo>
19    {
20        private readonly IPEndPoint _LocalEndPoint;
21        private readonly IPEndPoint _RemoteEndPoint;
22        private readonly TcpState _State;
23
24        public TcpConnectionInfo(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, TcpState state)
25        {
26            if (localEndPoint == null) throw new ArgumentNullException("localEndPoint");
27            if (remoteEndPoint == null) throw new ArgumentNullException("remoteEndPoint");
28            _LocalEndPoint = localEndPoint;
29            _RemoteEndPoint = remoteEndPoint;
30            _State = state;
31        }
32
33        public IPEndPoint LocalEndPoint
34        {
35            get { return _LocalEndPoint; }
36        }
37
38        public IPEndPoint RemoteEndPoint
39        {
40            get { return _RemoteEndPoint; }
41        }
42
43        public TcpState State
44        {
45            get { return _State; }
46        }
47
48        public bool Equals(TcpConnectionInfo x, TcpConnectionInfo y)
49        {
50            return (x.LocalEndPoint.Equals(y.LocalEndPoint) && x.RemoteEndPoint.Equals(y.RemoteEndPoint));
51        }
52
53        public int GetHashCode(TcpConnectionInfo obj)
54        {
55            return obj.LocalEndPoint.GetHashCode() ^ obj.RemoteEndPoint.GetHashCode();
56        }
57
58        public bool Equals(TcpConnectionInfo other)
59        {
60            return Equals(this, other);
61        }
62
63        public override bool Equals(object obj)
64        {
65            if (obj == null || !(obj is TcpConnectionInfo))
66                return false;
67            return Equals(this, (TcpConnectionInfo)obj);
68        }
69
70    }
71}

至此,我们可以通过 TcpManager 类的 GetTableByProcess 方法获取进程中所有的 TCP 连接信息,然后通过 Kill 方法强制关连接以回收系统资源,虽然很C很GX,但是很有效。

通常情况下,我们可以使用 Timer 来定时检测进程中的 TCP 连接状态,确定其是否处于 CLOSE_WAIT 状态,当超过指定的次数/时间时,就把它干掉。

不过,相对这样的解决方法,我还是推荐在设计 Socket 服务端程序的时候,一定要管理所有的连接,而非上述方法。

b) 网络/硬件故障

现在我们再来看第二种情况,当网络/硬件故障时,如何应对;与上面不同,这样的情况 TCP 可能处于 ESTABLISHED、CLOSE_WAIT、FIN_WAIT 等状态中的任何一种,这时才是 Keepalive 该出马的时候。

默认情况下 Keepalive 的时间设置为两小时,如果是请求比较多的服务端程序,两小时未免太过漫长,等到它时间到,估计连黄花菜都凉了,好在我们可以通过 Socket.IOControl 方法手动设置其属性,以达到我们的目的。

关键代码如下:

1// 假设 accepted 到的 Socket 为变量 client
2// ...
3// 设置 TCP 心跳,空闲 15 秒,每 5 秒检查一次
4byte[] inOptionValues = new byte[4 * 3];
5BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
6BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, 4);
7BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, 8);
8client.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);

以上代码的作用就是设置 TCP 心跳为 5 秒,当三次检测到无法与客户端连接后,将会关闭 Socket。

相信上述代码加上说明,对于有一定基础读者理解起来应该不难,今天到此为止。

c) 结束语

其实对于 Socket 程序设计来说,良好的通信协议才是稳定的保证,类似于这样的问题,如果在应用程序通信协议中加入自己的心跳包,不仅可以处理多种棘手的问题,还可以在心跳中加入自己的简单校验功能,防止包数据被 WPE 等软件篡改。但是,很多情况下这些都不是我们所能决定的,因此,才有了本文中提出的方法。

警告 :本文系 Zealic 创作,并基于 CC 3.0 共享创作许可协议 发布,如果您转载此文或使用其中的代码,请务必先阅读协议内容。

View Comments

Understand Issue Tracking System

Issue Tracking System (ITS)是供软件开发所使用的系统,允许在软件开发中和追踪每个问题进程,直到问题最终被解决。在ITS中,一个”问题”,可能是Bug,可能是一个功能,也可能是一个测试。这些都可以被 ITS 管理,拥有者或者其它的什么管理单元所跟踪。

ITS 通常给用户提供了一条报告”问题”的通径,并且跟踪它的解决过程,了解造成”问题”的原因。它还允许系统管理者定制跟踪程序,避免问题解决部分的一些多余的文件造成时间的延误。许多行业的企业都使用ITS软件,包括软件开发商、生产商、IT帮助桌面和其它的服务提供者。

严格的讲,ITS 是 SCM 中的变更控制和过程控制理论的实现者,再加上 VCS,可以构成管理一个软件生产过程的基本模型。

现在大多数 ITS 都提供与 VCS 集成的功能,可以很方便的了解到一个”问题”所经历的源代码变化,并对此进行数据统计,改进工作计划或协调任务。

由于软件是无形的存在,人们在很大程度上难以掌握和估计,因此经常会发生对于低估某个项目的开发周期,最终造成项目的延迟甚至流产;而由于 ITS 的 Timeline 能在很大程序上直观的了解到项目的进度,以某个里程碑为推进目标;无论是开发者的士气还是管理者的焦虑,都能得到够好的效果。

并且,由于 ITS 几乎全盘接管项目的开发过程,因此只需要很少的文档,新成员或者项目维护者就能够了解到整个项目的历史、功能以及未决问题,从宏观上来说,非常有利。

此外,对于需求变化,使用 ITS 也能够做到较为迅速的反应和记录;我可以毫不顾忌的说,ITS、VCS 是实施敏捷所必需的工具,缺少任何一项,都不能称之为完整的敏捷。无论是快速迭代开发或者稳健的 TDD,ITS 都将是不可或缺的成员。

真正实施 ITS 需要的是团队成员的配合,如果仅仅是拿来做摆设,或者作为一种事后补充,几乎上述所有优点将丧失殆尽,ITS 的实施也就毫无意义。

最后介绍一些著名的 ITS :

  • Bugzilla
    作为一个老牌到牙都不掉的 ITS,无论是功能、社区支持还是与第三方工具协作方面,Bugzilla 都是极为稳定和高效的。
    Mozilla 出品,Free。

  • FogBugz
    Joel on Software 开发,具体不太了解,不过作为一个获得 JOLT 大奖的产品,想来不会太差。付费产品。

  • JIRA
    Australian atlassian 公司的出品的 JIRA 是一个基于 J2EE 成熟的 ITS,曾经荣获 JOLT 大奖。全球很多大型公司和组织都在使用 JIRA 管理企业的项目;JIRA 是一个商业程序。

  • Microsoft TFS
    Microsft 在 Visual Studio 2005 提供对应 TFS(Team Foundition System) 同样也是一个 ITS,并且集成源代码管理功能,不过由于 TFS 配置管理及硬件需求过高,维护起来也颇为麻烦,在实际中少见使用。也许这是一个遵循微软三版定律的软件。

  • Edgewall Trac
    Trac 是一个基于 Web 的轻量级 ITS,个人以为它比较适合精英类型的团队使用,人数考量应在 3-6 人之间,由于使用 SQLite 及 PostageSQL 作为数据后端,Trac 无法承载更大型的项目管理工作。Trac 是一个开源的产品,有稳定持续的维护者和开发计划。

本文遵循 CC创作共享许可协议,所有内容仅代表个人观点,但不包括第三方内容。

View Comments

搬家:新的起点

没有任何原因,大概是新年想换个节点吧。
我决定不再使用这个空间,可能这里都不会再更新了。
感谢你一直以来的关注。
原本我有两个主要空间:
一个是博客园的技术博客:https://zealic.cnblogs.com
主要分享一些技术心得和学习经验。
另一个就是本博,主要就是写点心情啥的,还是很隐晦的手法来写的。
这次决定搬家是因为新年的缘故,也是想放开心灵,以后写东西会稍微正常点的,也许还会记日记哦。
还有一个原因可能就是对 TX 不太放心吧,呵呵,至少 Microsoft 不会私自取走你的隐私。
请到我的新家,以后就会在这个博客里更新包括技术和生活方面的内容;当然,博客园也会同步更新。
https://zealic.spaces.live.com
Thank you.
新年快乐,爱你的 Zealic。

View Comments