More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  蝈蝈俊的共享空间PhotosProfileFriendsBlog Tools Explore the Spaces community

Blog

August 07

点燃主火炬的火炬手我猜是 “体操王子”李宁。 采用方式类似凌波微步或者叫凤回巢的方式飞过去。

写个非技术的。

奥运会点燃主火炬的我猜是“体操王子”李宁。

点火方式我猜会吊钢丝,类似中国功夫的凌波微步,或者叫凤回巢的方式飞过去。

李宁的照片,看起来老了。

July 30

应用程序池 与 W3WP.exe 进程的对应关系

       阅读本博客前,建议阅读我前一篇博客:IIS5、IIS6、IIS7的ASP.net 请求处理过程比较  这样知识会比较连贯。

       对于 IIS6、IIS7,  每个应用程序池都会创建一个 W3WP.exe 进程。  但是, 并不是所有情况都是一个应用程序池对应一个 W3WP.exe 进程。 Web Garden , 或者一些异常发生时候,就会一个 应用程序池对应多个 W3WP.exe 进程。

 

       Web Garden 指的是一个应用程序可以在多个进程(w3wp.exe)中来执行,一次请求使用其中的一个。用这个的主要目的是提高程序的可用性。当其中一个进程发生错误,那么也不会影响其他进程。发生错误的进程可以根据规则关闭,而其他的进程则可以继续工作。

       需要注意的是:一般使用 InProc HttpSessionState / HttpApplicationState / 静态变量来储存关键信息 的程序是不支持 Web Garden的。

 

       由于应用程序池会在没有请求的时候定时回收,或者发生错误的时候,自动重新建立一个处理进程 W3WP.exe 进程。如果你中大运,你可能会看到没有配置 Web Garden 时, 一个应用程序池对应多个 W3WP.exe 进程, 如这篇文章中提到的 http://www.eggheadcafe.com/forumarchives/inetserveriis/Feb2006/post25881024.asp

       

IIS 6 (Win2003 )中查看某个应用程序池对应那个 W3WP.exe 进程,可以使用如下命令,输出结果类似如下:

C:\WINDOWS\system32>cscript iisapp.vbs
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.
W3WP.exe PID: 1172 AppPoolId: StsAdminAppPool
W3WP.exe PID: 2656 AppPoolId: MSSharePointAppPool
W3WP.exe PID: 2148 AppPoolId: WMS App Pool
W3WP.exe PID: 3604 AppPoolId: defaultwebsite

参看这篇博客: http://blogs.msdn.com/jb/archive/2006/02/22/536693.aspx

 

IIS 7 中则是如下命令,输出结果类似如下:

C:\Windows\system32>%windir%/system32/inetsrv/appcmd list wp
WP “5716″ (applicationPool:DefaultAppPool)
WP “968″ (applicationPool:MyOtherAppPool)
WP “5836″ (applicationPool:TheThirdAppPool)

参看: http://dirk.net/2008/06/01/identify-which-w3wpexe-belongs-to-which-application-pool-in-iis7/

 

参考资料:

找出“w3wp.exe进程”对应的“应用程序池”
http://www.cnblogs.com/windpole/archive/2007/11/19/964819.html

Which w3wp.exe process belongs to which App Pool in IIS6
http://weblogs.asp.net/owscott/archive/2004/09/21/Which-w3wp.exe-process-belongs-to-which-App-Pool-in-IIS6.aspx

What Application Pool does this W3WP.EXE belong to?
http://blogs.msdn.com/jb/archive/2006/02/22/536693.aspx

New in IIS 7 - App Pool Isolation
http://adopenstatic.com/cs/blogs/ken/archive/2008/01/29/15759.aspx

对w3wp.exe的一点认识!
http://www.cnblogs.com/hjh1982/archive/2006/06/28/438032.html

关于Web Garden与Web Farms
http://www.cnblogs.com/huashanlin/archive/2007/07/30/836652.html

你的程序支持 IIS6 - Web Garden 吗?
http://blog.joycode.com/lostinet/archive/2005/02/02/44017.aspx

Web Farm And Web Garden
http://www.cnblogs.com/kingclever/archive/2007/12/13/993152.html

Identify which w3wp.exe belongs to which Application Pool in IIS7
http://dirk.net/2008/06/01/identify-which-w3wpexe-belongs-to-which-application-pool-in-iis7/

July 25

IIS5、IIS6、IIS7的ASP.net 请求处理过程比较

ASP.NET是一个非常强大的构建Web应用的平台,它提供了极大的灵活性和能力以致于可以用它来构建所有类型的Web应用。
绝大多数的人只熟悉高层的框架如: WebForms 和 WebServices --这些都在ASP.NET层次结构在最高层。

这篇文章的资料收集整理自各种微软公开的文档,通过比较 IIS5、IIS6、IIS7 这三代 IIS 对请求的处理过程, 让我们熟悉 ASP.NET的底层机制 并对请求(request)是怎么从Web服务器传送到ASP.NET运行时有所了解。通过对底层机制的了解,可以让我们对 ASP.net 有更深的理解。

IIS 5 的 ASP.net 请求处理过程

对图的解释:

IIS 5.x 一个显著的特征就是 Web Server 和真正的 ASP.NET Application 的分离。作为 Web Server 的IIS运行在一个名为 InetInfo.exe 的进程上,InetInfo.exe 是一个Native Executive,并不是一个托管的程序,而我们真正的 ASP.NET Application 则是运行在一个叫做 aspnet_wp 的 Worker Process 上面,在该进程初始化的时候会加载CLR,所以这是一个托管的环境。

ISAPI:  指能够处理各种后缀名的应用程序。 ISAPI 是下面单词的简写 :Internet Server Application Programe Interface,互联网服务器应用程序接口。

IIS 5 模式的特点:

1、首先,同一台主机上在同一时间只能运行一个 aspnet_wp 进程,每个基于虚拟目录的 ASP.NET Application 对应一个 Application Domain ,也就是说每个 Application 都运行在同一个 Worker Process 中,Application之间的隔离是基于 Application Domain 的,而不是基于Process的。

2、其次,ASP.NET  ISAPI 不但负责创建 aspnet_wp Worker Process,而且负责监控该进程,如果检测到 aspnet_wp 的 Performance 降低到某个设定的下限,ASP.NET  ISAPI 会负责结束掉该进程。当 aspnet_wp 结束掉之后,后续的 Request 会导致ASP.NET ISAPI 重新创建新的 aspnet_wp Worker Process。

3、最后,由于 IIS 和 Application 运行在他们各自的进程中,他们之间的通信必须采用特定的通信机制。本质上 IIS 所在的 InetInfo 进程和 Worker Process 之间的通信是同一台机器不同进程的通信(local interprocess communications),处于Performance的考虑,他们之间采用基于Named pipe的通信机制。ASP.NET ISAPI和Worker Process之间的通信通过他们之间的一组Pipe实现。同样处于Performance的原因,ASP.NET ISAPI 通过异步的方式将Request 传到Worker Process 并获得 Response,但是 Worker Process 则是通过同步的方式向 ASP.NET ISAPI 获得一些基于 Server 的变量。

 

IIS6 的 ASP.net 请求处理过程

对图的解释:

IIS 5.x 是通过 InetInfo.exe 监听 Request 并把Request分发到Work Process。换句话说,在IIS 5.x中对Request的监听和分发是在User Mode中进行,在IIS 6中,这种工作被移植到kernel Mode中进行,所有的这一切都是通过一个新的组件:http.sys 来负责。

注:为了避免用户应用程序访问或者修改关键的操作系统数据,windows提供了两种处理器访问模式:用户模式(User Mode)和内核模式(Kernel Mode)。一般地,用户程序运行在User mode下,而操作系统代码运行在Kernel Mode下。Kernel Mode的代码允许访问所有系统内存和所有CPU指令。

在User Mode下,http.sys接收到一个基于 aspx 的http request,然后它会根据IIS中的 Metabase 查看该基于该 Request 的 Application 属于哪个Application Pool, 如果该Application Pool不存在,则创建之。否则直接将 request 发到对应Application Pool 的 Queue中。

每个 Application Pool 对应着一个Worker Process:w3wp.exe,毫无疑问他是运行在User Mode下的。在IIS Metabase 中维护着 Application Pool 和worker process的Mapping。WAS(Web Administrative service)根据这样一个mapping,将存在于某个Application Pool Queue的request 传递到对应的worker process(如果没有,就创建这样一个进程)。在 worker process 初始化的时候,加载ASP.NET ISAPI,ASP.NET ISAPI 进而加载CLR。最后的流程就和IIS 5.x一样了:通过AppManagerAppDomainFactory 的 Create方法为 Application 创建一个Application Domain;通过 ISAPIRuntime 的 ProcessRequest处理Request,进而将流程进入到ASP.NET Http Runtime Pipeline。

 

IIS 7  的 ASP.net 请求处理过程

 

IIS7 站点启动并处理请求的步骤如下图:

步骤 1 到 6 ,是处理应用启动,启动好后,以后就不需要再走这个步骤了。

上图的8个步骤分别如下:

1、当客户端浏览器开始HTTP 请求一个WEB 服务器的资源时,HTTP.sys 拦截到这个请求。
2、HTTP.sys contacts WAS to obtain information from the configuration store.

3、WAS 向配置存储中心请求配置信息。applicationHost.config。
4、WWW 服务接受到配置信息,配置信息指类似应用程序池配置信息,站点配置信息等等。
5、WWW 服务使用配置信息去配置 HTTP.sys 处理策略。
6、WAS starts a worker process for the application pool to which the request was made.

7、The worker process processes the request and returns a response to HTTP.sys.

8、客户端接受到处理结果信息。

W3WP.exe 进程中又是如果处理得呢?? IIS 7 的应用程序池的托管管道模式分两种: 经典和集成。 这两种模式下处理策略各不相通。

本文作者:郭红俊 http://blog.joycode.com/ghj

 

IIS 6 以及 IIS7 经典模式的托管管道的架构

       在IIS7之前,ASP.NET 是以 IIS ISAPI extension 的方式外加到 IIS,其实包括 ASP 以及 PHP,也都以相同的方式配置(PHP 在 IIS 采用了两种配置方式,除了 IIS ISAPI extension 的方式,也包括了 CGI 的方式,系统管理者能选择 PHP 程序的执行方式),因此客户端对 IIS 的 HTTP 请求会先经由 IIS 处理,然后 IIS 根据要求的内容类型,如果是 HTML 静态网页就由 IIS 自行处理,如果不是,就根据要求的内容类型,分派给各自的 IIS ISAPI extension;如果要求的内容类型是 ASP.NET,就分派给负责处理 ASP.NET 的 IIS ISAPI extension,也就是 aspnet_isapi.dll。下图是这个架构的示意图。

IIS  7 应用程序池的 托管管道模式  经典  模式也是这样的工作原理。 这种模式是兼容IIS 6 的方式, 以减少升级的成本。

IIS6 的执行架构图,以及 IIS7  应用程序池配置成经典模式的执行架构图

 

IIS  7 应用程序池的 托管管道模式  集成模式

       而 IIS 7 完全整合 .NET 之后,架构的处理顺序有了很大的不同(如下图),最主要的原因就是 ASP.NET 从 IIS 插件(ISAPI extension)的角色,进入了 IIS 核心,而且也能以 ASP.NET 模块负责处理 IIS 7 的诸多类型要求。这些 ASP.NET 模块不只能处理 ASP.NET 网页程序,也能处理其他如 ASP 程序、PHP 程序或静态 HTML 网页,也因为 ASP.NET 的诸多功能已经成为 IIS 7 的一部份,因此 ASP 程序、PHP 程序或静态 HTML 网页等类型的要求,也能使用像是Forms认证(Forms Authentication)或输出缓存(Output Cache)等 ASP.NET 2.0 的功能(但须修改 IIS 7 的设定值)。也因为 IIS 7 允许自行以 ASP.NET API 开发并加入模块,因此 ASP.NET 网页开发人员将更容易扩充 IIS 7 和网站应用程序的功能,甚至能自行以 .NET 编写管理 IIS 7 的程序(例如以程控 IIS 7 以建置网站或虚拟目录)。

IIS 7 的执行架构图(集成托管信道模式下的架构)

 

小结

IIS5 到 IIS6 的改进,主要是 HTTP.sys 的改进。

IIS6 到 IIS7 的改进,主要是 ISAPI 的改进。

 

参考资料:

ASP.NET Process Model之一:IIS 和 ASP.NET ISAPI
http://www.cnblogs.com/artech/archive/2007/09/09/887528.html

ASP.NET Internals – IIS and the Process Model
http://dotnetslackers.com/articles/iis/ASPNETInternalsIISAndTheProcessModel.aspx

模组化的IIS 7 与.NET 能力整合
http://www.microsoft.com/taiwan/technet/columns/profwin/33-iis7-componentization-integration.mspx

Introduction to IIS 7.0 Architecture
http://learn.iis.net/page.aspx/101/introduction-to-iis7-architecture/

July 23

ASP.net 页面被关闭后,服务器端是否仍然执行中?

问题:当一个正在执行中的ASPX页面执行到一半的时候,浏览器中你关闭了这个页面,服务器端对应的这个页面的代码仍然在执行么?

答案:除非你代码里面做了特殊判断,否则仍然正在执行。

 

注意点:

1、客户端显示页面的时候,后台已经执行完了的页面对象早已经不存在了。当然这时候谈不上服务器段执行不执行的问题了。

2、页面还没有返回,处于等待状态的时候。关闭ASPX页面,才会涉及到上面提到的服务器端仍然在执行的情况。

3、客户端关闭的时候根本不向服务器发送指令。

4、除非你代码里面做了特殊判断,这里的特殊判断指用 if(!Response.IsClientConnected) 来检测状态而用代码终止运行。

下面的简单代码就是演示关闭页面后,看是否仍然在执行?

你可以在这个页面打开后, 还没有返回任何信息的时候把这个页面关闭,然后看指定目录下是否有对应文件被创建并填写内容。

        protected void Page_Load(object sender, EventArgs e)
        {
            StringBuilder txt = new StringBuilder();

            txt.AppendLine();
            txt.AppendLine(DateTime.Now.ToString("u"));
            txt.AppendLine("asvd");

            Response.Write(DateTime.Now.ToString("u"));
            Response.Write("<br />\r\n");
            Thread.Sleep(50000);


            txt.AppendLine(DateTime.Now.ToString("u"));
            Response.Write(DateTime.Now.ToString("u"));
            Response.Write("<br />\r\n");

            // 把一些信息写到另外一个文件,借此察看是否正在运行
            string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            DateTime dt = DateTime.Now;
            string shortfileName = string.Format("errors_{0:0000}{1:00}{2:00}.log", dt.Year, dt.Month, dt.Day);
            string fileName = Path.Combine(dir, shortfileName);

            StreamWriter sw;
            if (File.Exists(fileName))
                sw = File.AppendText(fileName);
            else
                sw = File.CreateText(fileName);

            sw.Write(txt.ToString());
            sw.Close();
            sw = null;

        }

 

作了特殊判断的情况简单例子:

注意: IsClientConnected 的判断在 VS.net 开发工具自带的开发站点 ASP.NET Development Server  是不支持的。 ASP.NET Development Server 永远返回 true 。

IIS 才是支持的。

        protected void Page_Load(object sender, EventArgs e)
        {

            StringBuilder txt = new StringBuilder();

            for (int i = 0; i < 100; i++)
            {
                if (this.Response.IsClientConnected)
                {
                    txt.AppendLine();
                    txt.AppendLine(DateTime.Now.ToString("u"));
                    txt.AppendLine(i.ToString());

                    Response.Write(DateTime.Now.ToString("u"));
                    Response.Write("<br />\r\n");
                    Thread.Sleep(500);
                }
                else
                {
                    Response.End();
                    return;
                }
            }

            txt.AppendLine(DateTime.Now.ToString("u"));
            Response.Write(DateTime.Now.ToString("u"));
            Response.Write("<br />\r\n");

            // 把一些信息写到另外一个文件,借此察看是否正在运行
            string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            DateTime dt = DateTime.Now;
            string shortfileName = string.Format("errors_{0:0000}{1:00}{2:00}.log", dt.Year, dt.Month, dt.Day);
            string fileName = Path.Combine(dir, shortfileName);

            StreamWriter sw;
            if (File.Exists(fileName))
                sw = File.AppendText(fileName);
            else
                sw = File.CreateText(fileName);

            sw.Write(txt.ToString());
            sw.Close();
            sw = null;
        }

这个例子中是发现中断,就抛弃之前做的任何东西。

当然我们也可以简单的修改上述代码,让把已经处理完成的东西记录下来,类似下面的代码

        protected void Page_Load(object sender, EventArgs e)
        {
            StringBuilder txt = new StringBuilder();

            for (int i = 0; i < 100; i++)
            {
                if (this.Response.IsClientConnected)
                {
                    txt.AppendLine();
                    txt.AppendLine(DateTime.Now.ToString("u"));
                    txt.Append("**********  ");
                    txt.AppendLine(i.ToString());

                    Response.Write(DateTime.Now.ToString("u"));
                    Response.Write("<br />\r\n");
                    Thread.Sleep(500);
                }
                else
                {
                    break;
                }
            }

            txt.AppendLine(DateTime.Now.ToString("u"));
            Response.Write(DateTime.Now.ToString("u"));
            Response.Write("<br />\r\n");

            // 把一些信息写到另外一个文件,借此察看是否正在运行
            string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            DateTime dt = DateTime.Now;
            string shortfileName = string.Format("errors_{0:0000}{1:00}{2:00}.log", dt.Year, dt.Month, dt.Day);
            string fileName = Path.Combine(dir, shortfileName);

            StreamWriter sw;
            if (File.Exists(fileName))
                sw = File.AppendText(fileName);
            else
                sw = File.CreateText(fileName);

            sw.Write(txt.ToString());
            sw.Close();
            sw = null;
        }

需要注意的是, 使用 isClientConnected   是要占用一定的系统资源的。

isClientConnected   实际上需要向客户端输出一点东西,然后才知道客户端是否仍然在线。

这样,除非你的应用非常耗时,否则建议你不要用 isClientConnected   。 免得判断 isClientConnected   使用的资源比你实际业务逻辑使用的资源还要多。

在任何情况下, Response.IsClientConnected 都要有些开销,所以,只有在执行至少要用 500 毫秒(如果想维持每秒几十页的吞吐量,这是一个很长的时间了)的操作前才使用它。作为通常的规则,不要在紧密循环的每次迭代中调用它,例如当绘制表中的行,可能每  20 行或每 50 行调用一次。

 

 

参考资料:

how to increase timeout on aspx page?
http://p2p.wrox.com/topic.asp?TOPIC_ID=7504

asp.net能不能在客户端关闭后在后台继续将页面执行完毕?
http://topic.csdn.net/u/20070202/14/85ea5576-907a-4960-9c53-b206a05228e4.html

在 ASP.NET 中使用计时器(Timer)
http://blog.joycode.com/percyboy/articles/3595.aspx

ASP.NET 2.0 中的异步页
http://www.microsoft.com/china/msdn/library/webservices/asp.net/issuesWickedCodetoc.mspx?mfr=true

IsClientConnected的问题
http://topic.csdn.net/u/20080712/19/74fa4070-84bd-4fda-a99b-ac361f874738.html

Response.IsClientConnected 原理和用法
http://blog.51ait.cn/article.asp?id=357    第一个地址比较慢,可以看下一个地址
http://blog.joycode.com/ghj/archive/2008/07/23/115198.aspx

July 22

写给刚工作的网站技术人员

       工作九年了,网站相关的开发工作也干了八年多。负责带领团队也好几年了。面试和带领刚工作的人也不少了。其中的优秀者不少,但是大多数都存在下面提到的几个认识误区。把这些问题提出来,希望对刚参加工作没有多久的程序员们有所帮助,少走弯路。

 

      公司招你进来,其实最重要的就是看到你的工作能力和工作态度是可以接受的。

            工作能力指你能满足他们的工作期望,或者在可接受的时间范围内,经过培训后,可以满足这个工作期望。

            工作态度指你能有些做职员的基本素质。

      这个道理应该所有人都清楚。但是到实际事情时候就经常犯迷糊。下面几点是经常会出问题的地方:

 

1、不经测试,Review,就认为自己工作完成了。

       你的代码或者应用一旦被别人Review ,或者进行试用。这时候你代码的好坏,或者功能是否在各种场景下是否可用,都会影响你这个人在上级及同事眼里的可信任度。

       代码书写的规范,性能的高质量,各种功能在各个场景都可用,则表示你这个人是完全可信的。下次上级给你分派任务的时候,就可以给你更多的自由度来发挥。长此以往,前途和钱途自然就随手可得。

        反之,代码不规范,功能好些场景不可用。这只能让上级或同事觉得你不可信任。每次都需要处理你带来的这些问题,说恶心点就是你每次拉完大便都没擦屁股,每次都得你的同事和上级帮你擦屁股。数次都这样后,上级或同事下次跟你沟通的时候就会觉得你这个人不可信任,一件事情必须反复多次强调,总觉得你还会作出问题。你的信用已经非常危险了。

       你在别人眼里的信用就这样被你慢慢透支了。透支到一定程度,走人吧。整个团队的效率会因为你而变慢(每个人跟你沟通的成本都会影响到他本人的产出),你不走人谁走人。

 

2、最短可接受的工作时限

       你有没有统计过,公司分派给你一个工作时候,上级指定的这项工作计划做多久的预计,跟你自己的预计有多大差异?

       如果你预计时间大于上级给的工作计划时间,同时上级没有增派人手进行相关工作。除了BT的领导外,那只有一种情况:上级对你的工作态度非常不满,认为你的薪水对应的工作能力不是这么点。

       对于刚工作的,更多的是你表现出来的工作能力在公司的平均工作能力之下。同时公司觉得你对工作没有表现出足够的热情。 一个能力在平均水平下面,又缺乏工作激情的人,他的前途在那里??

       如果这个人还没有表现出几个月后能达到平均水平之上的希望,为啥会留这样一个人呢?

 

3、工作能力不等于技术水平

       我曾看到过有人抱怨说大公司的员工也不过是这技术水平, 这么简单的技术问题都不会。我自己早期也有这样想法,后来发现是不对的。

       不论大公司还是小公司,要得是解决问题的工作能力。 我的曾经手下就有好几个技术水平很牛的,但是作出来的应用却一次次返工的。为啥,工作能力这些非技术因素他们做的很不好。

       工作能力的非技术因素包括的很多: 责任心,表现就是对自己写出来的代码有一定要让人放心的责任; 沟通能力,一个典型的表现就是需求不理解或者需求不明时,及时得跟相关人沟通,而不是自己先按自己想法实现,造成代码写完后再返工的恶果等等。

       技术水平低,但是解决问题能力强的,我也碰到过一些人。 工作的能力更重要的是这些非技术的工作能力,而不是技术水平。技术人员很容易技术水平高,但是非技术的工作能力差。 这是很糟糕的。

     

 

4、发展潜力,学习能力

      公司使用的技术不可能一直不变,一直不变的公司只能慢慢被市场淘汰。这就要求员工能不断的学习新的知识,并应用到工作中来。

      要想不会出现几年后,自己发现跳槽找个工作都没人要,赶快学习吧。

      坚持,是一个人最难做到的。 但是不坚持,那就等着灭亡吧。

 

 

5、笨鸟先飞

      一个人,在公司,如果工作能力在平均线以下, 加班吧, 不要有任何幻想。

      最可怕的是自己没这个意识, 自认为自己技术水平很牛, 但是解决问题的工作能力却在平均水平线以下, 眼高手低 , 这样的人, 公司是不能留的。

 

 

6、承诺到的事情一定要做到,不要找理由

       一件事情没有被做完,想找理由能找很多的。既然你承诺了某个时间点前完成,就不要再找各种理由推脱。

       公司同事和上级虽然可能这次接受了你的理由,但是下次呢, 慢慢的就会让你的上级,同事觉得你是一个喜欢推托的人。 感觉你干事是非常不可靠的。不知道那次就会不完成,下次谁敢再找你干事?

 

新补充的:

7、有能力者不用加班,能力在平均线之下的自觉加班吧

从CSDN的评论中,争议最大的一个在加班。我这里就补充一下我对加班的看法。

首先,所有人加班,这肯定是领导者的责任, 整体的工作进度压力过大了。

连续的不间断的加班,这也是领导者的责任,人不可能一直处于线绑紧的状态。

但是,如果一个部门,部分人加班,部分人不加班,而且并不是因为领导弱智的把一些人工作分派多,一些人工作分派少导致的。加班的人只能说明他的工作能力在部门的平均线之下。
同一个工作, A 员工干的话 3 天完成, B 员工干的话 5天完成。 最后如果这个工作分派给 B 员工, 很可能是要求他 4天完成。 而且他必须自觉加班。

这就是我所说的 : 有能力者不用加班,能力在平均线之下的自觉加班。

 

        可能很多人在看到我这篇博客的时候,觉得我写的很刻薄,好像都是从公司的角度欺压技术人员。很没有人情味。

        只要你不是公司的董事, 你永远是被剥削者,公司的目的就是利润最大化,这是公司存活的根本目的。作为普通的职员,要有所为的白领意识,其实就是被剥削意识。这是个适者生存的生态圈,不适用的人只能被淘汰。

        实际的公司其实有很多人情味在里面,或者同事和领导有些话不便于说出口。 这也就造成了一些技术人员被开除,还自以为如何如何? 都是没有这些意识造成的。我写这篇博客就是希望能增加技术人员的这些意识,不要犯了这些问题还自己不知道。

June 25

UTF-8 BOM导致的无法正常模拟Http请求

本文概述

StreamWriter 在产生UTF-8编码的内容时候,会在产生的这个UTF-8内容中增加BOM的信息,而这个BOM的信息,会干扰我们在一些情况的使用。
本文描述的情况,就是这种干扰让我们无法正常工作的一种情况。

 

何为BOM?

BOM(Byte Order Mark),BOM签名。
BOM的内容就可以表示unicode文件是何种编码。BOM签名的意思就是告诉编辑器当前文件采用何种编码,方便编辑器识别。

对于UTF-8 , BOM 信息为 EF BB BF。 我们如果在Google搜索 UTF-8 BOM 就会搜索到很多文章, BOM 在不少情况下,都会给我们添乱子。

 

下面是我碰到这个问题的描述

我碰到这个问题的场景:在书写一段模拟HTTP Post 请求的时候, 代码如下,但是却无法模拟Post请求:

private void do2()
 {
     string url = "http://localhost:39749/Default.aspx";



     string indata = "__VIEWSTATE=%2FwEPDwUKMTQ2OTkzNDMyMWRkyGd";
      indata += "iqWjBKr5rIKmHzSdD9AaojKw%3D&Button1=Button&__EVENTVALIDATION=%";
      indata += "2FwEWAgLohfrVDQKM54rGBu49QLoa7JmG9cEfUpTccMrUmJfD";
     HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
     req.ContentType = "application/x-www-form-urlencoded";
     req.Method = "Post";


     Stream myRequestStream = req.GetRequestStream();
     StreamWriter myStreamWriter = new StreamWriter(myRequestStream, Encoding.UTF8);
     myStreamWriter.Write(indata);
     myStreamWriter.Close();
     myRequestStream.Close();
     myStreamWriter.Dispose();
     myRequestStream.Dispose();


     HttpWebResponse res = (HttpWebResponse)req.GetResponse();
     StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8);
     string info = reader.ReadToEnd();
     reader.Close();
     res.Close();
     reader.Dispose();
     MessageBox.Show(info);
 }

而文中中间的代码修改成下面代码则可以成功模拟。

byte[] bytes = System.Text.Encoding.UTF8.GetBytes(indata);
req.ContentLength = bytes.Length;
Stream myRequestStream = req.GetRequestStream();
myRequestStream.Write(bytes, 0, bytes.Length);
myRequestStream.Close();

为何会这样呢?分析原因,竟然是 UTF-8 BOM 在作怪。

StreamWriter 在产生UTF-8编码的内容时候,会在产生的这个UTF-8内容中增加BOM的信息, 这样他发送的Post信息就比正常多了三个字节  EF BB BF。 就是因为这三个字节导致服务器端无法处理正常的Post请求。

 

解决方法:

1、自己重写UTF-8类,参看 http://www.19870202.com/?tid=381  。在调用的时候用这个自己写的类。
重写代码:
public class UTF8EncodingNoPreamble : System.Text.UTF8Encoding
{
        public override byte[] GetPreamble()
        {
            return new byte[0];
        }
}

2、不要用 StreamWriter ,参看我上面的替代方案。

 

参考资料:

System.IO.StreamWriter写UTF-8文件不写BOM
http://www.19870202.com/?tid=381

UTF-8, UTF-16, UTF-32 & BOM
http://unicode.org/faq/utf_bom.html#BOM

utf-8 保存文件的 bom 问题
http://www.uuzone.com/blog/tom/101761.htm

June 06

WEB Service 传递信息时候,会把 /r/n 变成 /n

我们在使用WEB Service时,需要注意的一点是,传递过程中会丢失一些字符,比较典型的是 /r/n 中 /r 回车字符会被丢弃。这是XML规范所导致的问题。XML规范关于这部分的描述如下:

2.11 行尾处理
为编辑的方便起见,存储XML已析实体的计算机文件经常用行来组织。通常这些行用回车符(#xD)和换行符(#xA)的一些组合来分隔。

为了使应用的工作简单化,对于一个外部已析实体或内部已析实体的常量实体值中包含的任何两字符常量序列"#xD#xA"或单独的常量#xD,XML处理器都应换成#xA传递给应用。(这可以通过在进行语法分析前将所有行分隔符规范成#xA而方便地实现。)

\r   回车(跑到最前面)  
\n  换行(下一行)  

参考资料:

WebServices eat \r in \r\n
http://vidmar.net/weblog/archive/2005/04/03/1203.aspx

XML规范对此相关的解释
http://www.w3.org/TR/2004/REC-xml-20040204/#sec-line-ends
中文版的介绍看下面地址:
http://xml.coverpages.org/xml10-chinese.html#sec-line-ends

\r\n和\r
http://topic.csdn.net/t/20060317/09/4620216.html

May 07

动态封杀与解封IP

我们在应对网站的恶意请求时候,一个解决方法就是把有问题的请求IP封杀掉。

如果想快速处理这种问题,就需要编写一段代码,达到一定门槛,自动封杀。再复杂点就是不是永久封杀,还可以自动在一定时间后解封。

封杀的逻辑代码看后面提供的。

需要说明的是:IIS7时,情况发生了不同。

 

下面的代码,在处理封杀IP时候,不论IIS6还是IIS7 都可以把需要封杀的IP加入封杀列表。但是需要注意的是我们代码写的是全部替换原先的数据。但是在IIS7下,执行的效果是原先的不替换,新加一批封杀IP。当然IIS7下,如果新加的IP原来就有了,则会报如下异常:

System.Runtime.InteropServices.COMException was caught
  Message="当文件已存在时,无法创建该文件。 (异常来自 HRESULT:0x800700B7)"
  Source="System.DirectoryServices"
  ErrorCode=-2147024713
  StackTrace:
       在 System.DirectoryServices.DirectoryEntry.CommitChanges()
       在 IIS_Security_ConsoleApplication.Program.IPDeny() 位置 D:\MyCodes\IIS_Security_ConsoleApplication\IIS_Security_ConsoleApplication\Program.cs:行号 109
  InnerException:

这就是说,IIS7, 我们可以通过编程接口增加封杀IP名单,但是没发通过编程接口剔出封杀IP。

 

参考代码:

这里提供了两套参考代码,其实原理都是一样的。

在IIS 6 下,都没有任何问题, IIS 7 下都会有没发删除原先已有数据的问题。

代码一:


using System.DirectoryServices;
using System.Reflection;
using System;

class Program
{

static void IPDeny()
{

try
{
string serverName = "localhost";
// retrieve the directory entry for the root of the IIS server
System.DirectoryServices.DirectoryEntry IIS = new System.DirectoryServices.DirectoryEntry(string.Format("IIS://{0}/w3svc/1/root", serverName));

// retrieve the list of currently denied IPs
Console.WriteLine("Retrieving the list of currently denied IPs.");

// get the IPSecurity property
Type typ = IIS.Properties["IPSecurity"][0].GetType();
object IPSecurity = IIS.Properties["IPSecurity"][0];


// retrieve the IPDeny list from the IPSecurity object
Array origIPDenyList = (Array)typ.InvokeMember("IPDeny", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Instance | BindingFlags.GetProperty, null, IPSecurity, null);

// 罗列已经被拒绝的地址
foreach (string s in origIPDenyList)
Console.WriteLine("Before: " + s);

// check GrantByDefault. This has to be set to true,
// or what we are doing will not work.
bool bGrantByDefault = (bool)typ.InvokeMember("GrantByDefault", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Instance | BindingFlags.GetProperty, null, IPSecurity, null);

Console.WriteLine("GrantByDefault = " + bGrantByDefault);
if (!bGrantByDefault)
{
// 必须设置 默认允许访问
typ.InvokeMember("GrantByDefault", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
| BindingFlags.SetProperty, null, IPSecurity, new object[] { true });
}


// 更新被拒绝的IP列表
// 注意这里是完全替换
// 如果你想保留原先的拒绝列表,需要原先的拒绝列表也在这个数组中

Console.WriteLine("Updating the list of denied IPs.");

object[] newIPDenyList = new object[4];
newIPDenyList[0] = "192.168.1.21, 255.255.255.255";
newIPDenyList[1] = "192.168.1.22, 255.255.255.255";
newIPDenyList[2] = "192.168.1.23, 255.255.255.255";
newIPDenyList[3] = "192.168.1.24, 255.255.255.255";

Console.WriteLine("Calling SetProperty");

// add the updated list back to the IPSecurity object
typ.InvokeMember("IPDeny", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
| BindingFlags.SetProperty, null, IPSecurity, new object[] { newIPDenyList });



IIS.Properties["IPSecurity"][0] = IPSecurity;

Console.WriteLine("Commiting the changes.");

// commit the changes
IIS.CommitChanges();
IIS.RefreshCache();

// 检查更新后的数据
Console.WriteLine("Checking to see if the update took.");

IPSecurity = IIS.Properties["IPSecurity"][0];
Array y = (Array)typ.InvokeMember("IPDeny",
BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty,
null, IPSecurity, null);

foreach (string s in y)
Console.WriteLine("After: " + s);
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}

}
}

 

代码二:

        using System.DirectoryServices;
using System.Reflection;
using System;



static void SetIPSecurityProperty(string metabasePath, string member, string item)
{
// metabasePath is of the form "IIS://<servername>/<path>"
// for example "IIS://localhost/SMTPSVC/1"
// member is of the form "IPGrant|IPDeny|DomainGrant|DomainDeny"
// item is of the form "<ipaddress|domain>", for example, 157.56.236.15 or domain.microsoft.com
Console.WriteLine("\nEnumerating the IPSecurity property at {0}:", metabasePath);

try
{
if (("IPGrant" != member) && ("IPDeny" != member) && ("DomainGrant" != member) && ("DomainDeny" != member))
{
Console.WriteLine(" Failed in SetIPSecurityProperty; second param must be one of IPGrant|IPDeny|DomainGrant|DomainDeny");
}
else
{
DirectoryEntry path = new DirectoryEntry(metabasePath);
path.RefreshCache();
object ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" });
Type t = ipsecObj.GetType();
Array data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null);
Console.WriteLine(" Old {0} =", member);
bool exists = false;
foreach (object dataItem in data)
{
Console.WriteLine(" {0}", dataItem.ToString());
if (dataItem.ToString().StartsWith(item))
{
exists = true;
}
}

if (exists)
{
Console.WriteLine(" {0} already exists in {1}", item, member);
}
else
{
object[] newData = new object[data.Length + 1];
data.CopyTo(newData, 0);
newData.SetValue(item, data.Length);

t.InvokeMember(member, BindingFlags.SetProperty, null, ipsecObj, new object[] { newData });

path.Invoke("Put", new object[] { "IPSecurity", ipsecObj });

path.CommitChanges();

path.RefreshCache();
ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" });
data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null);
Console.WriteLine(" New {0} =", member);
foreach (object dataItem in data)
Console.WriteLine(" {0}", dataItem.ToString());
Console.WriteLine(" Done.");
}
}
}
catch (Exception ex)
{
if ("HRESULT 0x80005006" == ex.Message)
Console.WriteLine(" Property IPSecurity does not exist at {0}", metabasePath);
else
Console.WriteLine("Failed in SetIPSecurityProperty with the following exception: \n{0}", ex.Message);
}
}

static void Main(string[] args)
{

// 获取目前服务器上有哪些站点
DirectoryEntry root = new DirectoryEntry("IIS://localhost/W3SVC");
foreach (DirectoryEntry dir in root.Children)
{
if (dir.SchemaClassName == "IIsWebServer")
{
string ww = dir.Properties["ServerComment"].Value.ToString();

Console.Write("IIS://localhost/W3SVC/{0}/ROOT/ {1}\r\n", dir.Name, ww);
}
}


// IPDeny();

SetIPSecurityProperty("IIS://localhost/w3svc/1/root", "IPDeny", "192.168.5.79");

Console.ReadLine();
}

 

参考资料:

Blocking IIS IP Addresses with ASP.NET
http://www.west-wind.com/WebLog/posts/59731.aspx

How to Programmatically add IP Addresses to IIS's Deny Access List
http://www.codeproject.com/KB/security/iiswmi.aspx

HOWTO: 通过 IP 地址或域名称限制站点访问
http://support.microsoft.com/default.aspx/kb/324066

使用ADSI来操作IIS的路径
http://blog.joycode.com/ghj/archive/2004/06/08/24047.aspx

Setting IP Security Using System.DirectoryServices
http://www.cnblogs.com/drw/articles/17951.html

如何通过WEB方式,来控制iis的禁用IP名单。
http://blog.joycode.com/ghj/archive/2004/06/08/24075.aspx

Setting IP Security Using System.DirectoryServices
http://msdn.microsoft.com/en-us/library/ms524322(VS.85).aspx

how to automate adding denied IPs for IIS

http://www.nukeforums.com/forums/viewtopic.php?p=54746&highlight=&sid=1176c746e2037ed24acac86dd53ca747

IIS 7.0: Configure IPv4 Address and Domain Name Allow Rules
http://technet2.microsoft.com/windowsserver2008/en/library/d0de9475-0439-4ec1-8337-2bcedacd15c71033.mspx?mfr=true

May 04

每个分类取最新的几条的SQL实现

分类统计时候,我们可能经常会碰到这样的需求,每个分类按照一定顺序,取几条数据,然后在一起显示。

这个问题的解决方法,我们通过搜索引擎,可以找到很多中。但是不是SQL语句过于复杂,就是在数据量比较大时候,性能特别成问题。

今天我就碰到这样一个需求。而我自己的解决方案就是SQL过于复杂,或者性能比较差的。为此我在CSDN论坛发了个帖子,看有没有更好的解决方案。

http://topic.csdn.net/u/20080504/14/5c5866c3-8b91-45ef-ab17-f994f88f8e42.html

CSDN的 SQL Server 板块  不愧是高手云集,问题发出不到半小时,就获得了近10种解决方案。经过测试,我把性能最高,且SQL不复杂的方案整理出来。特别感谢 jinjazz 的解答。

 

问题详细描述如下:

比如,假设我们有下面这样结构的一张表,这张表的数据量非常巨大。

CREATE TABLE table1
(
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](128) NOT NULL,
[class] int not null,
[date] datetime not null
)

class 表示分类编号。 分类数不固定, 至少有上千种分类
date 表示该条记录被更新的时间
我们现在想获得每个分类最新被更新的5条记录。

 

解决方案

select id,name,class,date from(
select id,name,class,date ,row_number() over(partition by class order by date desc)
as rowindex from table1) a
where rowindex <= 5

 

解决方案简单说明:

这个解决方案的关键就是使用了SQL 2005 的 ROW_NUMBER 这个全新的函数。

ROW_NUMBER ( ) 函数的语法如下:
ROW_NUMBER ( )     OVER ( [ <partition_by_clause> ] <order_by_clause> )

OVER 子句中的 PARTITION BY 将结果集分为多个分区。
OVER 子句中的 ORDER BY 将对 ROW_NUMBER 进行排序。

下面是MSDN的几个简单例子:

 

以下示例将根据年初至今的销售额,返回 AdventureWorks 中销售人员的 ROW_NUMBER。

USE AdventureWorks; 
GO

SELECT c.FirstName, c.LastName ,
ROW_NUMBER() OVER(ORDER BY SalesYTD DESC) AS 'Row Number' ,s.SalesYTD, a.PostalCode

FROM Sales.SalesPerson s INNER JOIN Person.Contact c ON s.SalesPersonID = c.ContactID
INNER JOIN Person.Address a ON a.AddressID = c.ContactID
WHERE TerritoryID IS NOT NULL AND SalesYTD <> 0;


以下示例将返回行号为 50 到 60 的行(包含这两行),并按 OrderDate 进行排序。

USE AdventureWorks; 
GO

WITH OrderedOrders AS ( SELECT SalesOrderID, OrderDate,
ROW_NUMBER() OVER (ORDER BY OrderDate) AS 'RowNumber'
FROM Sales.SalesOrderHeader )

SELECT * FROM OrderedOrders WHERE RowNumber BETWEEN 50 AND 60;


 

以下示例说明了如何使用 PARTITION BY 参数。

USE AdventureWorks; 
GO

SELECT c.FirstName, c.LastName ,
ROW_NUMBER() OVER (PARTITION BY PostalCode ORDER BY SalesYTD DESC) AS 'Row Number' ,
s.SalesYTD, a.PostalCode

FROM Sales.SalesPerson s INNER JOIN Person.Contact c ON s.SalesPersonID = c.ContactID
INNER JOIN Person.Address a ON a.AddressID = c.ContactID
WHERE TerritoryID IS NOT NULL AND SalesYTD <> 0;
April 24

【编程游戏】划拳机器人比赛

CSDN 网友 zswang 组织了一个编程游戏比赛。划拳机器人比赛。类似“Robocode” 的游戏。

比赛相关帖子如下:

【编程游戏】编写一个会划拳的机器人参加擂台赛,规则内详。
http://topic.csdn.net/u/20080421/17/6a4d1c20-e1d1-4e9d-99ae-d648cb422ca6.html

【编程游戏】划拳机器人,前面一贴的机器人都集中来了,继续PK。谨慎路过。 
http://topic.csdn.net/u/20080423/21/cda52758-a06d-49e1-9db0-eda2b009da8c.html

很有意思的,为了让更多人能参与到这个很有趣的 .NET 编程比赛中来,我转了下面的内容:

 


划拳介绍

      划拳又叫豁拳、猜枚、猜拳、拇战,即饮酒时两人同时伸出手指并各说一个数,谁说的数目跟双方所伸手指的总数相符,谁就算赢,输的人喝酒。此乃我国
古传至今仍时尚流行的饮酒游戏,它增添酒兴,烘托喜庆,是一种民间的酒令。 其技巧性颇强,给玩者留有神机斗智的余地,且因玩时须喊叫,易让人兴奋,极富竞争性。
  两人同时伸出一只手,用攥起的拳头和伸出一到五个手指,表示从零到五这几个数字,与此同时,嘴里喊出从零到10的数字,如果两人伸出的手指表示的数字相加与其中一个人嘴里喊出的数字相同,那么这个人就算赢了这一拳。举例说明:比如一个人伸出了三个手指,另一个人伸出了四个手指,一个人喊了七,另一个喊了六,那么这个人喊七的人就赢了;如一个人伸出攥紧的拳头(表示零),嘴里喊出了三,而另一个恰好伸出了三个手指可嘴里喊的七,那么喊三的就赢了。就这么简单。当然,如果自己喊的是“八仙过海”, 而自己仅伸出了一个手指,那么对方即使伸出五指也不可能凑成八,这种拳就叫臭拳,如果不是事先约定,是要罚酒的。


    划拳规则看完了,那我们就开始写一个会划拳的机器人吧!
    那么一个会划拳的机器会做什么事情呢?其实就是两件:


第一件、出拳,即:自己出几个手指?自己猜合计是多少。
第二件、知道划拳的结果,即:对方出几个手指,对方猜合计是多少,是否获胜还是平局还是其他。
   只要继承Drunkard这个类,重载Come()和Outcome()方法那么你就拥有了一个会划拳的机器人,参与这个游戏了!


【游戏规则】
1、比赛共1000局,即:出现胜负算一局,如出拳100次没有结果也算一局并双方均不得分;
2、赢一局得1分、输不扣分;
3、机器人执行中每出现一次异常,扣100分、对方加1分、记一局;
4、机器人执行中反应超时1000毫秒直接判负,每超时100毫秒,扣1分,超时10次以上直接判负;
5、自己得分高于对手并大于600分判胜;
6、自己得分为正数对手得分为负数判胜;
7、其他情况则判平。
具体执行的过程,算法的过程请参考Drunkery <T1, T2>类的实现


【入门提示】
1、机器人的命名建议是: <自己的id> +  <第几个> + "号",如:Zswang一号、Zswang二号,当然你也可以用“长江七号”
2、不允许修改Drunkard和Drunkery <T1, T2>;
3、机器人必须从Drunkard继承;
4、分析擂主代码是战胜擂主的关键;
5、打擂容易守擂难,大家自由发挥吧!

 
【擂台赛规则】
1、第一个打败擂主的机器人奖励20分,并成为新的擂主;
2、自己不能挑战自己编写的机器人;
3、最后一个擂主获得200专家分,发帖另付;
4、其余分数酌情散掉。
标准代码和比赛规则有什么不妥或者建议欢迎讨论,谢谢关注
(调试代码参考后面两帖,-_-!!!代码有点多,先别抢沙发。。。。)

 

由于空间有限, 这里就不贴调试代码了, 调试代码,以及基类代码看上面给的两个帖子的链接。

April 18

如何用.net Remoting实现一个客户端需要连接多个服务器端?

比如我们有下面的需求:

三台电脑:A,B,C。
我们在 B 和 C 上部署了同样的一个服务,电脑 A 需要根据客户端的选择,自动的切换到底是调用B的服务,还是C的服务。

要实现这个需求,核心就在客户端的调用上。下面我们用一个简单的演示这个功能的代码来说明如何实现。

 

首先:服务器段

服务器段逻辑,这是非常简单的,我们按照之前的.net编写规范,编写代码即可。熟悉.net Remoting 的完全可以跳过这部分。

下面是一段简单的