2012年2月24日星期五

使用Memcached提高.NET應用程式的性能

在應用程式運行的過程中總會有一些經常需要訪問並且變化不頻繁的資料,如果每次獲取這些資料都需要從資料庫或者外部檔案系統中去讀取,性能肯定會受到影響,所以通常的做法就是將這部分資料緩存起來,只要資料沒有發生變化每次獲取這些資料的時候直接從記憶體中區獲取性能肯定會大大地提高。在.NET中提供了一個Cache類可以實現這些功能。在ASP.NET中可以通過HttpContext 物件的 Cache 屬性或 Page 物件的 Cache 屬性來獲取這個類的實例。 在大部分情況下我們都可以使用Cache類來提高ASP.NET的性能,但是使用Cache類也有一些不足,比如我們不能指定Cache類所佔用的記憶體的大小,此外在Cache中緩存的資料沒有辦法被另一台機器上的應用程式直接訪問,因此在本文中提出另一種資料緩存方案,那就是使用分散式緩存。分散式緩存的特點是緩存的資料不必和應用程式在同一台機器上,從而大大增強了緩存資料的複用性。在本文介紹如何在.NET應用中使用Memcache作為分散式緩存。
Memcached介紹
Memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 為首開發的一款軟體。在通常的應用中我們都會將資料保存到資料庫中,每次需要的時候都會從資料庫去查詢這些資料,如果應用程式的使用者很多就會出現大量併發訪問資料庫的情況,這樣就會增加應用程式的回應時間,使用Memcached就可以有效解決這個問題。memcached是高性能的分散式記憶體緩存伺服器。一般的使用目的是,通過緩存資料庫查詢結果,減少資料庫訪問次數,以提高動態Web應用的速度、提高可擴展性。像大名鼎鼎的Facebook網站就使用了Memcached。稍後會提供Windows平臺上32位和64位元的Memcached程式。

為了提高性能,Memcached中的資料都保存在Memcached內置的存儲空間中。因為當Memcached重啟會導致其中的資料全部丟失,所以一般的方案是將資料保存在資料庫中,每次請求資料的時候先查看在Memcached有沒有緩存,如果有就直接從緩存中取出資料;如果沒有,就從資料庫中取出資料返回給應用程式並將請求的資料緩存到Memcached中,這樣一來下次請求相同的資料就可以直接從Memcached中讀取而不用再去查資料庫了;一旦對資料有更新,同時更新資料庫和Memcached。

Memcached是一個命令列視窗程式,可以在命令列視窗中啟動也可以封裝在系統服務中啟動。在啟動Memcached時需要提供一些必須的參數,指定Memcached運行時監聽的埠和最大使用的記憶體大小等。如果緩存的資料大小超過指定記憶體,那麼Memcached就會按照LRU(Least Recently Used)演算法自動“刪除”不使用的緩存(標記為失效),新增的緩存資料就可以使用這些標記為失效的資料所佔用的記憶體,這樣就不用擔心Memcached超出所指定記憶體的問題。此外,為了提高性能,在緩存資料過期後Memcached並不是從實體記憶體中刪除緩存的資料,僅僅在取出改資料的時候檢查它是否已經過了有效期。

目前有多種平臺的Memcached版本,比如Linux、FreeBSD、Solaris (memcached 1.2.5以上版本)、Mac OS X及Windows平臺,在Windows平臺上還有32位和64位版本。

Memcached有一套協定,利用這套協定可以對Memcached進行資料存取和查看Memcached的狀態,很多程式語言都依據這套協議來操作Memcached,比如PHP、Java、C、C++及C#等。

獲取了對應平臺的Memcached版本就可以運行Memcached了。在這裡僅以Windows平臺上的32位Memcached為例。

運行Memcached:

memcached.exe -p 11121 -m 64

上面的命令是運行Memcached,指定它的監聽埠是11121(這是它的默認埠,可以指定為其它大於1024的埠,因為小於1024的埠已經有了預設指定),最大使用記憶體為64m,如果啟用了Windows防火牆,切記要在防火牆上打開這個埠。

在偵錯工具時可以使用下面的命令列來運行:
memcached.exe -p 11121 -m 64 -vv

這樣就會看到如下的結果:
slab class   1: chunk size     88 perslab 11915
slab class   2: chunk size    112 perslab  9362
slab class   3: chunk size    144 perslab  7281
slab class   4: chunk size    184 perslab  5698
slab class   5: chunk size    232 perslab  4519
slab class   6: chunk size    296 perslab  3542
slab class   7: chunk size    376 perslab  2788
slab class   8: chunk size    472 perslab  2221
slab class   9: chunk size    592 perslab  1771
slab class  10: chunk size    744 perslab  1409
slab class  11: chunk size    936 perslab  1120
slab class  12: chunk size   1176 perslab   891
slab class  13: chunk size   1472 perslab   712
slab class  14: chunk size   1840 perslab   569
slab class  15: chunk size   2304 perslab   455
slab class  16: chunk size   2880 perslab   364
slab class  17: chunk size   3600 perslab   291
slab class  18: chunk size   4504 perslab   232
slab class  19: chunk size   5632 perslab   186
slab class  20: chunk size   7040 perslab   148
slab class  21: chunk size   8800 perslab   119
slab class  22: chunk size  11000 perslab    95
slab class  23: chunk size  13752 perslab    76
slab class  24: chunk size  17192 perslab    60
slab class  25: chunk size  21496 perslab    48
slab class  26: chunk size  26872 perslab    39
slab class  27: chunk size  33592 perslab    31
slab class  28: chunk size  41992 perslab    24
slab class  29: chunk size  52496 perslab    19
slab class  30: chunk size  65624 perslab    15
slab class  31: chunk size  82032 perslab    12
slab class  32: chunk size 102544 perslab    10
slab class  33: chunk size 128184 perslab     8
slab class  34: chunk size 160232 perslab     6
slab class  35: chunk size 200296 perslab     5
slab class  36: chunk size 250376 perslab     4
slab class  37: chunk size 312976 perslab     3
slab class  38: chunk size 391224 perslab     2
slab class  39: chunk size 489032 perslab     2
<96 server listening
<112 server listening
<116 send buffer was 8192, now 268435456
<116 server listening (udp)

在用戶端還可以通過telnet來查看和操作Memcached,前提是伺服器端和用戶端都支援Telnet協定,在Windows7和Windows2008中預設都不支援,需要在控制台中安裝和啟用。

首先打開控制台,然後點擊“打開或關閉Windows功能”,如下圖所示:
點擊“打開或關閉Windows功能”之後會看到當前系統啟用的功能的狀態,根據當前機器選擇打開Telnet伺服器端或者用戶端功能,如下圖所示:
經過上面的操作之後就可以在客服端遠端查看Memcached的狀態或者操作Memcached了。下面的命令就是連接到Memcached:
telnet localhost 11121
連接之後會出現一個命令列視窗,在這個命令列視窗中輸入"stats"就可以看到當前Memcached的狀態,如下就是剛剛啟動的Memcached的狀態資料:
STAT pid 852
STAT uptime 1399
STAT time 1300979378
STAT version 1.2.5
STAT pointer_size 32
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 3
STAT total_connections 5
STAT connection_structures 4
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT evictions 0
STAT bytes_read 23
STAT bytes_written 415
STAT limit_maxbytes 67108864
STAT threads 1

END

通過這個資料我們就可以瞭解Memcached的狀態了。
這些資料所代表的意義如下:
pid:32u,伺服器進程ID。
uptime:32u, 伺服器執行時間,單位秒。
time :32u, 伺服器當前的UNIX時間。
version :string, 伺服器的版本號。
curr_items :32u, 伺服器當前存儲的內容數量 Current number of items stored by the server
total_items :32u, 伺服器啟動以來存儲過的內容總數。
bytes :64u, 伺服器當前存儲內容所佔用的位元組數。
curr_connections :32u, 連接數量。
total_connections :32u, 伺服器運行以來接受的連接總數。
connection_structures:32u, 伺服器分配的連接結構的數量。
cmd_get :32u, 取回請求總數。
cmd_set :32u, 存儲請求總數。
get_hits :32u, 請求成功的總次數。
get_misses :32u, 請求失敗的總次數。
bytes_read :64u, 伺服器從網路讀取到的總位元組數。
bytes_written :64u, 伺服器向網路發送的總位元組數。
limit_maxbytes :32u, 伺服器在存儲時被允許使用的位元組總數。
上面的描述中32u和64u表示32位和64位不帶正負號的整數,string表示是string類型資料。
 
在.NET中應用Memcached
有很多.NET版本的Memcached用戶端程式,在這裡周公使用的Enyim Memcached,可以到https://github.com/enyim/EnyimMemcached/下載最新的版本。

要想在專案中使用Memcached,需要添加對Enyim.Caching.dll的應用。除此之外,我們可能還需要在config檔中配置Memcached的資訊(也可以在程式碼中指定,但那樣不靈活),如下就是一個config檔配置的例子:





 

 

   

     

      
 


     

   

   

     

       

         

         

       

       

     

   

 



如果我們配置了多個Memcached的實例,可以想上面的注釋部分那樣在節點下添加多個Memcached的實例配置。

這裡需要說明的是如果我們需要向Memcached中添加自訂資料類型時,我們需要將該資料類型添加上[Serializable]標記。

下面是一個Enyim Memcached的例子: 

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using Enyim.Caching;  
using Enyim.Caching.Memcached;  

/*  

  * 日期:2011-03-24  
 * 原文出處:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com  
 * 版權說明:本文可以在保留原文出處的情況下使用於非商業用途,對此不作任何擔保或承諾。  

 * */ 

namespace MemcachedMonitor  
{  

[Serializable]  

public class Person  

{  
    public int UserId { get; set; }  
    public string UserName { get; set; }  

}  
public class MemcachedDemo  
{  
    private static MemcachedClient client = new MemcachedClient("enyim.com/memcached");  


    public void SetDemo()  

    {  

        Person person = new Person { UserId = 1, UserName = "李剛" };  

        //不帶過期時間的存儲,Memcached將根據LRU來決定過期策略  

        bool success=client.Store(StoreMode.Add, person.UserName, person);  

        //帶過期時間的緩存  

        //bool success = client.Store(StoreMode.Add, person.UserName, person, DateTime.Now.AddMinutes(10));  

        Console.WriteLine("存儲[{0}]的結果:{1}", person.UserName, success);  
    }  

    public void GetDemo()  

    {  

        Person person = client.Get("李剛");  

        if (person != null)  

        {  

            Console.WriteLine("取回[{0}]的結果——UserId:{1},UserName:{2}", "李剛", person.UserId, person.UserName);  

        }  

        else 

        {  

            Console.WriteLine("取回[{0}]失敗!", "李剛");  

        }  

    }  


    public void MultiGetDemo()  

    {  

        List personNameList = new List();  

        for (int i = 0; i < 10; i++)  

        {  

            personNameList.Add("李剛00" + i);  

        }  

        //批量獲取,只通過一次網路通訊就取回所有personNameList中的指定的所有資料  

        IDictionary resultList = client.Get(personNameList);  

        Person person;  
        foreach (KeyValuePair item in resultList)  

        {  

            person = item.Value as Person;  

            if (person != null)  

            {  

                Console.WriteLine("取回[{0}]的結果——UserId:{1},UserName:{2}", "李剛", person.UserId, person.UserName);  

            }  

            else 

            {  

                Console.WriteLine("取回[{0}]失敗!", "李剛");  

            }  

        }  

    }  

}  

}  

說明:如果需要一次從Memcached中取回多個緩存的資料,可以參考MultiGetDemo()方法,這樣一來只需要一次網路通訊就可以取回全部資料,減少網路連線時間。此外,在Memcached用戶端可以使用Text或者Binary協定,經過周公千萬次測試比較,使用Binary協定性能略高於使用Text協定。在上面的config檔中周公就配置使用了Binary協定。

總結,使用Memcached這樣的分散式緩存可以大大提高應用程式的性能,經過周公測試,正確使用Memcached可以將單台伺服器的併發訪問數從20提高到1000左右,也就是提高了50倍,這是一個相當客觀的提升!限於篇幅,關於Memcached的更深更詳細的用法沒有在本篇介紹,此文算作抛磚引玉,讀者可以自行參考其它相關資料。


本文出自 http://zhoufoxcn.blog.51cto.com/792419/528212

2012年2月15日星期三

SqlDataReader,SqlDataAdapter與SqlCommand的一點總結.

1.SqlDataReader,線上應用,需要conn.open(),使用完之後要關閉.
SqlConnection conn = new SqlConnection(connStr);
 //conn.Open();
SqlCommand cmd = new SqlCommand("select top 10 * from tuser", conn);
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

while (reader.Read())
{
    Console.WriteLine(reader.GetValue(2));
}
這段代碼報錯:ExecuteReader requires an open and available Connection. The connection's current state is closed.
應該將conn.Open()打開. 
2.SqlDataAdapter,離線應用,不需要用conn.open(), 它把這部分功能給封裝到自己內部了,不需要你來顯式的去調用, 它直接將資料fill到dataset中.  

SqlCommand與ADO時代的Command一樣,SqlDataAdapter則是ADO.NET中的新事物,它配合DataSet來使用。其實,DataSet就像是駐留在記憶體中的小資料庫,在DataSet中可以有多張DataTable,這些DataTable之間可以相互關聯,就像在資料庫中表關聯一樣!SqlDataAdapter的作用就是將資料從資料庫中提取出來,放在DataSet中,當DataSet中的資料發生變化時,SqlDataAdapter再將資料庫中的資料更新,以保證資料庫中的資料和DataSet中的資料是一致的!

用微軟顧問的話講:DataAdapter就像是一把鐵鍬,它負責把資料從資料庫“鏟”到DataSet中,或者將資料從DataSet“鏟”到資料庫中! 

調用DataAdapter的Fill方法時, 它會打開到資料庫的SqlConnection, 再通過創建一個SqlCommand和調用ExecuteReader的方式來執行命令, 然後, 通過一個隱式士創建的SqlDataReader, 從資料庫讀取資料, 結束行的讀取之後, SqlDataReader和SqlConnection會被關閉. 

DefaultView是DataTable類的一個屬性。可用DataView類為每個DataTable定義多個視圖。 

用Reflacter察看一下SqlDataAdapter及其父類的原代碼,其方法調用脈絡如下:
Fill(DataSet dataSet)->
Fill(dataSet, 0, 0, "Table", selectCommand, fillCommandBehavior)->
FillInternal(dataSet, null, startRecord, maxRecords, srcTable, command, behavior)->
Fill(dataset, srcTable, reader, startRecord, maxRecords)->
Fill(dataset, srcTable, reader, startRecord, maxRecords)->
FillFromReader(dataSet, null, srcTable, container, startRecord, maxRecords, null, null)->
FillLoadDataRow(mapping)->
while (dataReader.Read()) 

因此個人認為最精髓的一句總結就是:SqlDataAdapter內部獲取資料是通過調用SqlDataReader來實現的,而兩者都需要使用SqlConnection和SqlCommand。 

具體Reflacter察看一下SqlDataAdapter及其父類的原代碼如下:
2.1 SqlDataAdapter是DbDataAdapter的子類.
public sealed class SqlDataAdapter : DbDataAdapter, IDbDataAdapter, IDataAdapter, ICloneable

2.2 DbDataAdapter是一個抽象類別,裡面包含了Fill的具體實現.
public abstract class DbDataAdapter : DataAdapter, IDbDataAdapter, IDataAdapter, ICloneable
{
  public override int Fill(DataSet dataSet)
  {
    int num;
    IntPtr ptr;
    Bid.ScopeEnter(out ptr, " %d#, dataSet\n", base.ObjectID);
    try
    {
        IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand;
        CommandBehavior fillCommandBehavior = this.FillCommandBehavior;
        num = this.Fill(dataSet, 0, 0, "Table", selectCommand, fillCommandBehavior);
    }

    finally
    {
        Bid.ScopeLeave(ref ptr);
    }
    return num;
  }

  protected virtual int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior)
  {
    int num;
    IntPtr ptr;

    Bid.ScopeEnter(out ptr, " %d#, dataSet, startRecord, maxRecords, srcTable, command, behavior=%d{ds.CommandBehavior}\n", base.ObjectID, (int) behavior);

    try
    {
        if (dataSet == null)
        {
            throw ADP.FillRequires("dataSet");
        }

        if (startRecord < 0)
        {
            throw ADP.InvalidStartRecord("startRecord", startRecord);
        }

        if (maxRecords < 0)
        {
            throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
        }

        if (ADP.IsEmpty(srcTable))
        {
            throw ADP.FillRequiresSourceTableName("srcTable");
        }

        if (command == null)
        {
            throw ADP.MissingSelectCommand("Fill");
        }
        num = this.FillInternal(dataSet, null, startRecord, maxRecords, srcTable, command, behavior);

    }

    finally
    {
        Bid.ScopeLeave(ref ptr);
    }
    return num;
  }

  private int FillInternal(DataSet dataset, DataTable[] datatables, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior)
  {
    bool flag = null == command.Connection;
    try
    {

        IDbConnection connection = GetConnection3(this, command, "Fill");
        ConnectionState open = ConnectionState.Open;
        if (MissingSchemaAction.AddWithKey == base.MissingSchemaAction)
        {
            behavior |= CommandBehavior.KeyInfo;
        }

        try
        {
            QuietOpen(connection, out open);
            behavior |= CommandBehavior.SequentialAccess;
            using (IDataReader reader = null)

            {
                reader = command.ExecuteReader(behavior);
                if (datatables != null)
                {
                    return this.Fill(datatables, reader, startRecord, maxRecords);
                }
                return this.Fill(dataset, srcTable, reader, startRecord, maxRecords);
            }
        }

        finally
        {
            QuietClose(connection, open);
        }
   }

    finally
    {
        if (flag)
        {
            command.Transaction = null;
            command.Connection = null;
        }
    }

  protected virtual int Fill(DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
  {
    int num;
    IntPtr ptr;

    Bid.ScopeEnter(out ptr, " %d#, dataSet, srcTable, dataReader, startRecord, maxRecords\n", this.ObjectID);
    try
    {
        if (dataSet == null)
        {
            throw ADP.FillRequires("dataSet");
        }

        if (ADP.IsEmpty(srcTable))
        {
            throw ADP.FillRequiresSourceTableName("srcTable");
        }
        if (dataReader == null)
        {
            throw ADP.FillRequires("dataReader");
        }

        if (startRecord < 0)
        {
            throw ADP.InvalidStartRecord("startRecord", startRecord);
        }

        if (maxRecords < 0)
        {
            throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
        }

        if (dataReader.IsClosed)
        {
            return 0;
        }

        DataReaderContainer container = DataReaderContainer.Create(dataReader, this.ReturnProviderSpecificTypes);

        num = this.FillFromReader(dataSet, null, srcTable, container, startRecord, maxRecords, null, null);
    }
    finally
    {
        Bid.ScopeLeave(ref ptr);
    }

    return num;
  }

  internal int FillFromReader(DataSet dataset, DataTable datatable, string srcTable, DataReaderContainer dataReader, int startRecord, int maxRecords, DataColumn parentChapterColumn, object   parentChapterValue)
  {
    int num2 = 0;
    int schemaCount = 0;
    do
    {
        if (0 < dataReader.FieldCount)
        {
            SchemaMapping mapping = this.FillMapping(dataset, datatable, srcTable, dataReader, schemaCount, parentChapterColumn, parentChapterValue);

            schemaCount++;
            if (((mapping != null) && (mapping.DataValues != null)) && (mapping.DataTable != null))
            {
                mapping.DataTable.BeginLoadData();
                try
                {
                    if ((1 == schemaCount) && ((0 < startRecord) || (0 < maxRecords)))
                    {
                       num2 = this.FillLoadDataRowChunk(mapping, startRecord, maxRecords);
                    }

                    else
                    {
                        int num3 = this.FillLoadDataRow(mapping);
                        if (1 == schemaCount)
                        {
                            num2 = num3;
                        }
                    }
                }

                finally
                {
                    mapping.DataTable.EndLoadData();
                }

                if (datatable != null)
                {
                    return num2;
                }

            }

        }

    }

    while (this.FillNextResult(dataReader));
    return num2;
  }
  private int FillLoadDataRow(SchemaMapping mapping)
  {
    int num = 0;
    DataReaderContainer dataReader = mapping.DataReader;

    if (!this._hasFillErrorHandler)
    {
       while (dataReader.Read())
        {
           mapping.LoadDataRow();
            num++;
        }
        return num;
    }

    while (dataReader.Read())
    {
        try
        {
            mapping.LoadDataRowWithClear();
            num++;
            continue;
        }
        catch (Exception exception)
        {
            if (!ADP.IsCatchableExceptionType(exception))

            {
                throw;
            }

            ADP.TraceExceptionForCapture(exception);
            this.OnFillErrorHandler(exception, mapping.DataTable, mapping.DataValues);
            continue;
        }

    }
    return num;
  }

}
source:http://www.cnblogs.com/liuzhendong/archive/2012/01/28/2330689.html

2011年12月20日星期二

[Security]SQL injection的簡介與預防

前言 『程式不是會動就好!』,在安全性上的設計,更是完全的貼切這句話。如果設計出在裸奔的網站(請參考黑大的文章),就算網站上的功能可以正常運作,但千瘡百孔的網站,可能淪為人家的玩物或是提款機。 SQL injection的問題,在前幾年掀起了不少次旋風,甚至包括現在可能還有一堆網站有著同樣的問題,要命的是,根本不需要到駭客等級,就可以把有SQL injection問題的網站搞掛。一個程式設計師寫出來的程式,最要不得的就是有SQL injection的問題,我個人認為那是程式設計師不夠專業,甚至不夠格的表現。這不需要多高深的技巧跟學問,要嘛就是程式設計師偷懶,要嘛就是還不夠專業。 這篇文章簡介一下,SQL injection的基本原理,以及基本的防範措施。 什麼是SQL injection 回到之前重構系列的第一篇文章:[ASP.NET]重構之路系列v1 – UI, Business logic, Data access概念分開 畫面: 需求: 當輸入的帳號密碼,在資料庫中有吻合的資料,代表帳號密碼是對的。(再強調一次,一般設計驗證密碼的部分,應該是透過畫面上輸入密碼的值進行hash,再與資料庫中該帳號所對應hash完的密碼比對,以保障密碼不是以明文存在於資料庫中,確保密碼無法被還原) 直覺設計且造成SQL injection問題的程式碼: view source print? 01 protected void Verify_Click(object sender, EventArgs e) 02 { 03 string connectionString = @"myConnectionString"; 04 int count; 05 using (SqlConnection cn = new SqlConnection(connectionString)) 06 { 07 cn.Open(); 08 string sqlStatement = @"Select Count(1) From SomeTable Where ID= '" + this.Id.Text + "' AND Password= '" + this.Password.Text + "'"; 09 SqlCommand sqlCommand = new SqlCommand(sqlStatement, cn); 10 count = (int)sqlCommand.ExecuteScalar(); 11 } 12 13 this.Result.Text = (count > 0) ? "Pass" : "帳號或密碼錯誤"; 14 } 這樣的程式碼會有什麼問題? 問題可大了! 假設我是壞人,知道或猜測這功能的SQL是這樣串的(大部分功能都很好猜的,猜錯了也不會被咬),那麼我只要在畫面上的Id,隨便輸入個值,接著在密碼的部分,輸入' OR 1=1; -- 這時候,這一支聰明的功能就可以把完整的SQL statement組出來,變成: view source print? 1 Select Count(1) From SomeTable Where ID= '隨便輸入' AND Password ='' OR 1=1; -- 輸入這樣的值代表什麼,代表所有畫面上的輸入值都無所謂,因為1=1恆等式一定會成立,且前面的condition為OR,所以可以把前面的where condition都忽略掉。後面再補上個分號,代表這一個SQL子句已經結束,最後再補上--,代表後面的sql為註解。 就這樣,最基本的SQL injection就成功了。是的,這個網站在裸奔了。 如果你以為,只有登入頁面會存在這樣的問題,或是這樣的問題影響不大,那就大錯特錯了。通常會寫出這樣程式的人,如果又都是自己設計,那洞就不只一個。首先,DB的存取帳號,可能就是預設的sa或admin,也就是權限開到最大。這時候的SQL injection要進DB做事,就跟進自己家廚房一樣,例如: view source print? 1 Select Count(1) From SomeTable Where ID= '隨便輸入' AND Password ='' OR 1=1; DROP DATABASE pubs -- 恭喜你,你DB中的pubs這個DataBase就被Drop掉了。 駭客高興的話,也可以針對sysobjects跟syscolumns來瞭解你DB中有哪些table, 欄位,再比對一下網頁的資料,接著透過Update的語法,把相關的欄位資料加入一些html tag或惡意的程式碼,進而引發從SQL injection觸發的Cross-site Scripting(XSS攻擊),常見的結果就是連到那一頁時,可能被導到別頁,或是XX政府網站上被掛上特殊的國旗,再來就是莫名其妙的個資外洩。 這些情況充斥在我們的生活中,這一切的原罪,都是因為程式設計師的偷懶或不專業。 除了上述的例子,還可以怎麼瞭解SQL的組成?以ASP.NET來說,一樣,通常會寫出那樣有問題的程式,web.config可能也沒有設定custom error頁面,所以當程式出錯時,IIS就大剌剌的幫你把程式錯誤的資訊show在網頁上(就是黃頁的那個),要讓錯誤資訊是掛在SQL statement執行錯誤,是一件再簡單不過的事。所以,要瞭解該網頁的輸入和對應SQL的組成,根本不需要駭客等級就可以做。瞭解之後,要啟動SQL injection,也就只是易如反掌的事。 如何避免SQL injection 簡單的說,過濾輸入值。 但,攻擊手法相當多種,要過濾的字元組合可能一個都漏不得,所以自己設定黑名單來進行過濾,是一件非不得以才做的事。如果是用ASP.NET寫,那所有人的強烈建議,就是使用Parameters。 Parameter的用法相當簡單,這也是為什麼我前面會說,寫出SQL injection問題的程式碼,是一種偷懶的行為。 對應剛剛上面的範例,使用Parameter的方式來改寫: view source print? 01 protected void Verify_Click(object sender, EventArgs e) 02 { 03 string connectionString = @"myConnectionString"; 04 var id = this.Id.Text; 05 var password = this.Password.Text; 06 07 int count; 08 using (SqlConnection cn = new SqlConnection(connectionString)) 09 { 10 cn.Open(); 11 string sqlStatement = @"Select Count(1) From SomeTable Where ID= @id AND Password= @password"; 12 SqlCommand sqlCommand = new SqlCommand(sqlStatement, cn); 13 14 ////定義parameter型別 15 sqlCommand.Parameters.Add("@id", SqlDbType.VarBinary); 16 sqlCommand.Parameters["@id"].Value = id; 17 18 ////讓ADO.NET自行判斷型別轉換 19 sqlCommand.Parameters.AddWithValue("@password", password); 20 21 count = (int)sqlCommand.ExecuteScalar(); 22 } 23 24 this.Result.Text = (count > 0) ? "Pass" : "帳號或密碼錯誤"; 25 } 透過使用parameter,在SqlCommand執行時,會自動過濾掉可能造成問題的字元。 另外,SQL injection並不只會發生在where condition,也有可能發生在Order by等語法中。所以只要是畫面上可能被改變的值,要串到SQL語法中,就應該使用parameter。如果是Parameter無法使用的語法,例如Table的名字,那也應該自己建立一個mapping module,來避免畫面上user可任意輸入且可能為惡意攻擊碼的值,直接存進DB中或被非預期地執行。 結論 在patterns & practices Developer Center簡單歸納出三個步驟防止SQL injection: 1. Constrain input. 2. Use parameters with stored procedures. 3. Use parameters with dynamic SQL.

2011年12月13日星期二

極速理解設計模式系列:簡單工廠模式(Simple Factory Pattern)

四個角色:抽象產品(Product)、具體產品(Concrete Product)、工廠(Creator)、用戶端(Client) 抽象產品(Product):需要創建的各種產品的父類。這類產品有共同的介面。 體產品(Concrete Product):需要創建的具體物件。 工廠(Creator):內部邏輯可以控制生成目標物件。 用戶端(Client):實例化工廠,然後工廠根據傳入參數得到各種產品。調用產品實現不同功能。 實現思路:首先將工廠產生實體,然後使用工廠創建產品賦值給抽象產品的引用,然後通過抽象產品的公共介面調用具體產品的方法以實現功能。 類圖: 應用場景:蘋果公司的工廠生產iphone 4、ipad 2、ipod nano 6。 分析:這裡多種產品都可以運行,所以有一個公共方法,然後抽象為父類。 下面我們在控制台程式去演示一下如何使用Simple Factory Pattern: 一、抽象產品(Product): //抽象產品(Product) abstract class Apple { public abstract void Run(); } 二、具體產品(Concrete Product): //具體產品(Concrete Product) class Iphone : Apple { public override void Run() { Console.WriteLine("iphone 4 開始運行!"); } } //具體產品(Concrete Product) class Ipad : Apple { public override void Run() { Console.WriteLine("ipad 2 開始運行!"); } } //具體產品(Concrete Product) class IpodNano : Apple { public override void Run() { Console.WriteLine("ipod Nano 6 開始運行!"); } } 三、工廠(Creator): //工廠(Creator) class AppleFactory { public Apple CreateApple(string productName) { switch (productName.ToUpper()) { case "IPHONE": return new Iphone(); case "IPAD": return new Ipad(); case "IPODNANO": return new IpodNano(); default: return null; } } } 四、用戶端(Client): //用戶端(Client) class Program { static void Main(string[] args) { AppleFactory factory = new AppleFactory(); Apple iphone= factory.CreateApple("iphone"); iphone.Run(); Apple ipad = factory.CreateApple("ipad"); ipad.Run(); Apple ipodnano = factory.CreateApple("ipodnano"); ipodnano.Run(); Console.ReadLine(); } }

2011年12月5日星期一

網站常用技巧

1.不管是IE那個版本都規定為IE8使用 2. 關閉自動完成功能 3.Title和keywords寫法 到底我們的Title屬性應該怎麼寫呢?我相信大部分人的網站title都是這樣寫的 網站名稱 - 所在網頁 這樣寫其實也沒有什麼,但是如果你想讓你的網站在從Google和Baidu過來更大的流量,我不建議只這樣寫,還應該把keywords完善 網站名稱|功能名稱|功能名稱的各種寫法 為什麼說是各種寫法呢,因為我們並不知道用戶是怎麼找到你網站的,也不清楚是根據什麼來找到的,比如HttpRequest 我們就可以這樣寫request|http|httpRequest等等,這樣你的網站被找到的幾率就會大在提高。 這個建議大家使用免費的站長統計工具來測試一下,現在的站長工具一般分析的都挺到位,比如http://www.51.la 會統計出來路,根據什麼關鍵字 ,來源IP等等大家可以自己查看一下。 然後你就可以根據使用者常用的關鍵字進行分析,來配置自己的網站,也許這就是簡單的SEo吧,呵呵,不過真正好的網站不能光靠這些,這是不夠的, 內容才是王道 4.網站的 description 我相信大部分人會忘記寫它,它們主要是用來做什麼的呢? 博客園 - 程式師的網上家園 不難看出來標題就是Title 而下面的說明就是description的值。大家應該知道怎麼寫了吧。 5.什麼樣的網站Seo最難優化 我個人認為目前Ajax的網站是最難優化的,因為Ajax的網站全是非同步的調用的,我們可以自己找一個Ajax的網站測試一下,當你調出了整個介面的 資訊時,你表面上看著是非常的多。 但實際是你右鍵看一下原始程式碼只有短短的幾行Js,那這樣的話,網路蜘蛛所抓取到的資訊也就只是一點點JS代碼。它可能會感覺你的網站沒有任何有 意義的東西, 自然權重和檢索量就會有一定的影響。 其實SEo最好優化的還是最基本的,所有資訊都鋪設在網頁 上,當然Ajax的方法,確實從客戶體驗上有大大的提升,也把好多壓力放在了用戶端。 具體要怎麼用,還得看大家的網站是做什麼的 如果對Seo要求不是太高的話,那就不需要管這些東西了。以客戶體驗為主。但相反的話而不然。 ----------------常用功能我使用方法---------------- 1. 將徹底遮罩滑鼠右鍵 oncontextmenu="window.event.returnValue=false" 用於Table
no
2. 取消選取、防止複製 3. 不准粘貼 onpaste="return false" 4. 防止複製 oncopy="return false;" oncut="return false;" 5. IE位址欄前換成自己的圖示 6. 可以在我的最愛中顯示出你的圖示 7. 關閉輸入法 8. 永遠都會帶著框架 9. 防止被人frame 10. 網頁將不能被另存為
menu
輸入框也可以做的很漂亮了
外向數:  沒回答的題數:
總得分:  結    論:

注意:修 改為即為打開最大 化視窗,而如果改為就變為視窗一打開就最小化 頁面自動刷新(說明) 當你做網頁時,是不是有的時候想讓你的網頁自動不停刷新,或者過一段時間自動跳轉到另外一個你自己設定的頁面?其實實現這個效果非常地簡單 ,而且這個效果甚至不能稱之為特效。你只要把如下代碼加入你的網頁中就可以了。 1,頁面自動刷新:把如下代碼加入區域中,其中20指每隔20秒刷新一次頁面. 2,頁面自動跳轉:把如下代碼加入區域中,其中20指隔20秒 後跳轉到http://www.williamlong.info/頁面。 頁面自動關閉 5000是指時間 快顯視窗自動關閉 10秒後快顯視窗自動關閉 注意:在新的tan.htm的body中要加 head View Code 注意:這段代碼是在新建檔中的 這個可不是