2010年12月16日 星期四

[C#] 基本網路技術(二) IP 位址與主機名稱轉換

System.Net 命名空間
Dns 類別
System.Net.Sockets 命名空間
SocketException 類別
//引用命名空間
using System.Net;
using System.Net.Sockets;

namespace TestIPAddress {
class Program {
static void Main(string[] args) {
try {
GetIPAddress();
}
catch (SocketException SockEx) {
Console.WriteLine(SockEx.Message);
GetIPAddress();
}
Console.ReadKey();
}
static void GetIPAddress() {
Console.WriteLine("輸入所要解析的主機名稱 (ex: www.pchome.com.tw):");
//取得主機名稱
string pHostName = Console.ReadLine();

IPHostEntry myAddress = Dns.GetHostEntry(pHostName);

Console.WriteLine("{0} 所對應的 IP 位址如下 : ", pHostName);
IPAddress[] myIPAddress = myAddress.AddressList;

foreach (IPAddress add in myIPAddress)
Console.WriteLine(add);

Console.WriteLine("輸入所要解析的 IP 位址 :");
//取得 IP 位址
string pAddress = Console.ReadLine();
IPHostEntry myHostName = Dns.GetHostEntry(pAddress);
Console.WriteLine("{0} 所對應的 主機名稱 如下 : ", pAddress);

Console.WriteLine(myHostName.HostName.ToString());
}
}
}

原文轉載自 http://www.dotblogs.com.tw/atowngit/archive/2010/01/16/13046.aspx

[C#] 基本網路技術(一) 取得主機名稱與 IP

Dns 類別
Dns 成員
IPHostEntry 類別

using System;
using System.Net;
using System.Collections;

class HostName {
static void Main() {
string s = Dns.GetHostName();
Console.WriteLine("主機名稱 :{0} ", s);

IPAddress[] IPS = Dns.GetHostEntry(s).AddressList;
IEnumerator iEnums = IPS.GetEnumerator();
while (iEnums.MoveNext()) {
Console.WriteLine("IP : {0}", iEnums.Current.ToString());
}

Console.ReadKey();
}
}

轉載自原文 http://www.dotblogs.com.tw/atowngit/archive/2010/01/16/13045.aspx

[C#] 基本網路技術(三) 建立 TCP 連線

TcpClient 類別 : 提供 TCP 網路服務的用戶端連接。
TcpListener 類別 : 接聽來自 TCP 網路用戶端的連接。
NetworkStream 類別 : 提供網路存取之資料的基礎資料流。

sample1 Server 端應用程式
//匯入命名空間
using System.Net.Sockets;
namespace TestNetworkServer {
class Program {
static void Main(string[] args) {
System.Net.IPAddress theIPAddress;
//建立 IPAddress 物件(本機)
theIPAddress = System.Net.IPAddress.Parse("127.0.0.1");

//建立監聽物件
TcpListener myTcpListener = new TcpListener(theIPAddress, 36000);
//啟動監聽
myTcpListener.Start();
Console.WriteLine("通訊埠 36000 等待用戶端連線...... !!");
Socket mySocket = myTcpListener.AcceptSocket();
do {
try {
//偵測是否有來自用戶端的連線要求,若是
//用戶端請求連線成功,就會秀出訊息。
if (mySocket.Connected) {
int dataLength;
Console.WriteLine("連線成功 !!");
byte[] myBufferBytes = new byte[1000];
//取得用戶端寫入的資料
dataLength = mySocket.Receive(myBufferBytes);

Console.WriteLine("接收到的資料長度 {0} \n ", dataLength.ToString());
Console.WriteLine("取出用戶端寫入網路資料流的資料內容 :");
Console.WriteLine(Encoding.ASCII.GetString(myBufferBytes, 0, dataLength) + "\n");
Console.WriteLine("按下 [任意鍵] 將資料回傳至用戶端 !!");
Console.ReadLine();
//將接收到的資料回傳給用戶端
mySocket.Send(myBufferBytes, myBufferBytes.Length, 0);
}
}
catch (Exception e) {
Console.WriteLine(e.Message);
mySocket.Close();
break;
}

} while (true);
}
}
}

sample2 Client 端應用程式
using System.Net.Sockets;
namespace TestNetworkClient {
class Program {
//宣告網路資料流變數
NetworkStream myNetworkStream;
//宣告 Tcp 用戶端物件
TcpClient myTcpClient;

static void Main(string[] args) {
Program myNetworkClient = new Program();

Console.WriteLine("輸入連接機名稱 : ");
//取得主機名稱
string hostName = Console.ReadLine();
Console.WriteLine("輸入連接通訊埠 : ");
//取得連線 IP 位址
int connectPort = int.Parse(Console.ReadLine());
//建立 TcpClient 物件
myNetworkClient.myTcpClient = new TcpClient();
try {
//測試連線至遠端主機
myNetworkClient.myTcpClient.Connect(hostName, connectPort);
Console.WriteLine("連線成功 !!\n");
}
catch {
Console.WriteLine
("主機 {0} 通訊埠 {1} 無法連接 !!", hostName, connectPort);
return;
}

myNetworkClient.WriteData();
myNetworkClient.ReadData();
Console.ReadKey();
}

//寫入資料
void WriteData() {
String strTest = "this is a test string !!";
//將字串轉 byte 陣列,使用 ASCII 編碼
Byte[] myBytes = Encoding.ASCII.GetBytes(strTest);

Console.WriteLine("建立網路資料流 !!");
//建立網路資料流
myNetworkStream = myTcpClient.GetStream();

Console.WriteLine("將字串寫入資料流 !!");
//將字串寫入資料流
myNetworkStream.Write(myBytes, 0, myBytes.Length);
}

//讀取資料
void ReadData() {
Console.WriteLine("從網路資料流讀取資料 !!");
//從網路資料流讀取資料
int bufferSize = myTcpClient.ReceiveBufferSize;
byte[] myBufferBytes = new byte[bufferSize];
myNetworkStream.Read(myBufferBytes, 0, bufferSize);
//取得資料並且解碼文字
Console.WriteLine(Encoding.ASCII.GetString(myBufferBytes, 0, bufferSize));
}
}
}

轉載自 http://www.dotblogs.com.tw/atowngit/archive/2010/01/16/13047.aspx

2010年12月9日 星期四

資料庫交易寫法TransactionScope、SqlTransact

資料庫交易寫法TransactionScope、SqlTransact
資料庫沒有了交易是很要命的,無法確保資料庫的正確,此篇就是在介紹交易的寫法。
string strConn = "連線字串"; 
SqlConnection conn = new SqlConnection(strConn);
conn.Open();
SqlTransaction tran = con.BeginTrasaction();
try
{
SqlCommand cmd = new SqlCommand("SQL語法", conn);
cmd.Transaction = tran;
//做你想做的...
//做完以後
tran.Commit();
}
catch
{
tran.Rollback();//發生例外就會滾回去
}
finally
{
conn.Dispose();
}


  using (TransactionScope scope = new TransactionScope()) 
{
string strConn = "連線字串";
SqlConnection conn = new SqlConnection(strConn);
SqlCommand cmd = new SqlCommand("SQL語法", conn);
try
{
conn.Open();
//做你想做的...
//做完以後
scope.Complete();
}
//Mission Accomplished!
catch (Exception ex)
{
}
//發生例外時,會自動rollback

finally
{
cmd.Dispose();
conn.Close();
conn.Dispose();
}
}

搬移大量資料至 SQL Server

ADO.NET 的SqlBulkCopy 類別,用來搬移大量資料蠻好用的,雖然不是什麼新玩意兒,最近有一些需要,因此拿來用,記錄一下:
SqlConnection sqlconn = new SqlConnection(connstring);
SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlconn);
using (sqlBulkCopy){ //大量複製的目的資料
sqlBulkCopy.DestinationTableName = destTable; sqlBulkCopy.WriteToServer(dr); //大量複製開始}
sqlconn.Close();

其中的destTable為 SQL Server 目的資料表,dr 則是來源資料,可以是DataReader或是 DataTable等等,來源資料不需要的是 SQL Server,只要能轉成DataReader或是 DataTable即可。

C# 使用SqlBulkCopy將資料批次寫入資料庫

之前demo有介紹利用SqlDataSoure和手動撰寫ADO.NET的方式大量新增資料的方法,雖然已經有效的改善了寫入的速度,但在發現了SqlBulkCopy以後,發現它更是威力強大,現在就來介紹SqlBulkCopy的猛。
  //一開始我們先產生一個DataTable來裝我們要寫入的資料 
DataTable dt = new DataTable();
dt.Columns.Add("id", typeof(int));
dt.Columns.Add("name", typeof(string));

//因為SqlBulkCopy的猛就是大量的一次寫入,所以我們也來跑10萬筆吧
int i;
for (i = 0; i < 100000; i++)
{
DataRow dr = dt.NewRow();
dr["name"] = i.ToString();
dt.Rows.Add(dr);
}

//宣告連結字串
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ASPNETDBConnectionString1"].ConnectionString);

conn.Open();
//宣告SqlBulkCopy
using (SqlBulkCopy sqlBC = new SqlBulkCopy(conn))
{
//設定一個批次量寫入多少筆資料
sqlBC.BatchSize = 1000;
//設定逾時的秒數
sqlBC.BulkCopyTimeout = 60;

//設定 NotifyAfter 屬性,以便在每複製 10000 個資料列至資料表後,呼叫事件處理常式。
sqlBC.NotifyAfter = 10000;
sqlBC.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied);

//設定要寫入的資料庫
sqlBC.DestinationTableName = "dbo.Table1";

//對應資料行
sqlBC.ColumnMappings.Add("id", "id");
sqlBC.ColumnMappings.Add("name", "name");

//開始寫入
sqlBC.WriteToServer(dt);
}
conn.Dispose();
}
void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
{
Response.Write("---
");
}

測試環境:SQL2005 Express
測試資料量:10萬筆
測試次數:10次
平均秒數:2.3532秒

太可怕啦,之前的寫法如果真的要寫10萬筆這種大量的資料都需花費一分鐘左右,但使用了SqlBulkCopy卻只要短短的兩秒鐘,下表看的出來如果資料筆數很少就沒必要使用SqlBulkCopy了。

寫入十萬筆資料10次的平均秒數
使用SqlBulkCopy:2.2051
使用AddWithValue:63.418
寫入一萬筆資料10次的平均秒數
使用SqlBulkCopy:0.2188
使用AddWithValue:6.3856
寫入一千筆資料10次的平均秒數
使用SqlBulkCopy:0.0187
使用AddWithValue:0.5805
寫入一百筆資料10次的平均秒數
使用SqlBulkCopy:0.0062
使用AddWithValue:0.0353
寫入十筆資料10次的平均秒數
使用SqlBulkCopy:0.004
使用AddWithValue:0.004

C# 將資料大量寫入資料庫時的優化寫法

當初學者需要利用for迴圈寫入資料時,常常會犯一個錯誤,就是SqlConnection開開關關,迴圈跑100次它就活生生開關一百次,雖然資料量小時看不出影響但這是相當浪費效能與資源的寫法,只要改變寫作習慣就可以避免掉這問題,來看看吧。

demo以GridView1秀出資料後再把它全部寫入到一個名為Table1的資料表。

以下是一般初學者會寫的code
for (int i = 0; i < this.GridView1.Rows.Count; i++) 
{
SqlConnection conn = demotools.getSqlConnection();//此為demo取得SqlConnection的方法
SqlCommand comm = new SqlCommand("INSERT INTO [Table1] ([name]) VALUES (@name)", conn);
conn.Open();
comm.Parameters.AddWithValue("name", this.GridView1.Rows[i].Cells[1].Text);
comm.ExecuteNonQuery();
conn.Dispose();
comm.Dispose();
}

沒錯這樣寫是可以正常的寫入資料庫,但是因為開關連結的方式包含在for迴圈內所以就會發生前文所說得問題,瘋狂的開關,為了節省效能我們應該這樣寫
SqlConnection conn = demotools.getSqlConnection(); 
SqlCommand comm = new SqlCommand("INSERT INTO [Table1] ([name]) VALUES (@name)", conn);
//以上都應該放在for迴圈外(除非你會變動=.=)

int i;
//宣告變數也應該放在外面不應該在for內

conn.Open();//開啟連結拿出來了

for (i = 0; i < this.GridView1.Rows.Count; i++)
{
comm.Parameters.Clear();//清除掉目前宣告出來的Parameters
comm.Parameters.AddWithValue("name", this.GridView1.Rows[i].Cells[1].Text);
comm.ExecuteNonQuery();
}
conn.Dispose();
comm.Dispose();


測試環境:SQL2005 Express
測試資料量:10萬筆
測試次數:10次
平均秒數:64.4867秒

這樣子的寫法是demo目前會的最好的寫法,當然我還很嫩其他的大大一定有更好的寫法還請多指教...當然其中還可以加上交易等判斷但那些不是此篇的重點所以demo以最簡單的方式寫出來,希望此篇對您會有所幫助,或許你懷疑SQL怎麼會那麼慢,請注意測試環境使用的是Express版本,並且10萬筆這種大筆數本來就不太應該用這方法寫...

C# 將資料大量寫入資料庫(使用SqlDataSoure)

上一篇介紹『將資料大量寫入資料庫時的優化寫法』是利用ADO.NET的寫法,但有些開發者習慣或是愛用等因素就是要用SqlDataSoure來完成,所以就出現了此篇文章啦

此篇只列出最重要的部份,您應該看了就懂了,至於頁面上的配置和該在什麼事件處理因人而異,demo就不多介紹了。

此篇的SqlDataSource名稱為SqlDataSource2
this.SqlDataSource2.InsertCommand = "INSERT INTO [Table1] ([name]) VALUES (@name)";//將Inster的SQL語句寫好 
this.SqlDataSource2.InsertParameters.Add("name", TypeCode.String, "");//宣告參數
int i;
for (i = 1; i <= 100; i++)
{
SqlDataSource2.InsertParameters["name"].DefaultValue = string.Format("第{0}筆", i);
SqlDataSource2.Insert();
}

【論譠】使用ASP.NET發送電子報(大量郵件)的一些建議

逛台灣MSDN論譠時,發現璉璉大哥與小朱大哥所提出ASP.NET發送電子報(大量郵件)有講到重點,
所以這裡就引介一下囉!

璉璉大哥:

基本上不要考慮用 網頁 (ASP.NET) 發大量郵件:

1. 預設每個執行緒只能跑 90 秒
2. 共用 AppPool 的網頁其中一個掛的話會導致整個重新啟用,凡是共用同一個 AppPool 的網頁都將被終結
3. 不易管理,例如信件發出去後,發現內容有誤,若用 Windows Form 很快可以暫停,用 asp.net 很麻煩~

小朱大哥:
如果公司資料庫是用 SQL Server 2005,可以考慮使用 Database Mail 來做,簡單又省時。
程式只要負責把寄送名單輸入資料庫,再寫一段呼叫 Database Mail 的程序來發送郵件,其他的就由 Database Mail 來處理即可。

原址網頁:http://social.msdn.microsoft.com/Forums/zh-TW/236/thread/cd6b90d0-c78b-47a3-bfcd-5a5b5a37c83d

介紹好用工具:Bat To Exe Converter

我時常在寫批次檔(Batch, *.bat)幫助我處理一些日常的工作,例如說備份資料庫、建立 SSH Tunnel、或是將所有子目錄下的 .svn 目錄改成 _svn 目錄、…等等。有些 Batch Script 甚至會寫超過一百行,若你想保護辛苦寫的批次檔時,就可以利用 Bat To Exe Converter 幫你轉成 *.exe 執行檔。

簡單的說,這套工具有 4 個特色:

1.隱藏原始碼。(這是無庸置疑的)
2.支援 “沒有互動視窗”的應用程式(Ghost applications),可讓你的程式在背景作業。
3.批次檔轉換後的執行檔可設定應用程式圖示、檔案版本、產品版本、公司名稱、程式描述、版權宣告等應用程式資訊。
4.可以不只將批次檔轉成執行檔,還可以將批次檔相關的檔案、文件或程式也一併結合在一起。
正所謂「一圖解千文」,如下圖示就是此工具唯一的 GUI 操作介面:

下載後的壓縮檔中會有兩個檔案,一個是 Bat_To_Exe_Converter.exe 執行檔,另一個是 help.chm 說明檔,可查詢指令列(Command Line)操作的用法。

若要透過指令列的方式執行轉換動作,至少需要設定 4 個參數才能執行,其參數的說明如下:

1.第一個參數是來源批次檔
2.第二個參數是輸出的執行檔
3.第三個參數是 Icon 的檔案路徑
4.第四個參數是設定你的應用程式類型 ( Console 或 Ghost )
5.第五個之後的參數都是要外掛進來的檔案,也就是要跟這個原本的批次檔一起工作的程式。

範例1:將 a.bat 轉成 a.exe,其中的 "" 代表不要設定 Icon,1 代表轉換成 Console application
Bat_To_Exe_Converter.exe a.bat a.exe "" 1

範例2:將 a.bat 轉成 a.exe,其中的 0 代表轉換成 Ghost application ( 背景作業模式 )
Bat_To_Exe_Converter.exe a.bat a.exe "" 0

範例3:將 a.bat 轉成 a.exe,並且也將 myprogram1.exe 與 test.jpg 兩個檔案都打包放進 a.exe 中,讓 a.exe 在執行時可以引用這兩個檔案。
Bat_To_Exe_Converter.exe a.bat a.exe "" 1 myprogram1.exe test.jpg

另外,我也做了個小測試,情境如下:

我寫一個 a.bat 內容是 type a.txt
然後建立一個 a.txt 內容是 12345
然後執行 Bat_To_Exe_Converter.exe a.bat a.exe "" 1 a.txt 將 a.txt 綁進 a.exe 中
之後將 a.txt 的內容修改成 67890
接者我執行 a.exe 得到的結果是 67890
接者我從該目錄刪除 a.txt 檔案
再執行一次 a.exe 我得到的結果是 12345
由此可知,即便將檔案一併綁進轉換後的執行檔中,原本批次檔讀取的訊息還是先從原本所在的目錄讀取檔案,若讀取不到才會讀取被綁進執行檔中的檔案。

原文轉載 http://blog.miniasp.com/post/2008/10/07/Useful-tool-Bat-to-Exe-Converter.aspx

2010年12月1日 星期三

ASP.NET 4.0 設定 了 validateRequest="false" 仍然會出現 具有潛在危險 Request.QueryString 的錯誤訊息

微軟的asp.net網頁都會預設進行Request Validation,但若是想規避這樣的內建行為。(建議仍要有其他的方法來防範XSS攻擊)

通常我們可以在.aspx中的Page Tag設定:
<@Page Language="C#" validateRequest="False" >

或是直接對整個站台設定Web.Config中 <system.web>下設定全域的
<pages validateRequest="false" />

不過,假如你現在在ASP.NET 4.0 的環境下的話,就算進行上述的設定,可能仍會出現驗證失敗的訊息(潛在危險Request.QueryString的錯誤訊息)

因為ASP.NET4.0與2.0版本在請求驗證的定義上已經有所不同:
ASP.NET Request Validation請求驗證是ASP.NET提供來保護XSS攻擊的一項功能
在先前的ASP.NET(ASP.NET 2.0)網頁,都是預設會進行網頁請求的請求驗證
僅會針對.aspx以及他們的class檔案進行驗證
不過到了ASP.NET 4.0,請求驗證範圍擴大到所有的請求
因為從BeginRequest階段就開始了HttpRequest
因此在這個期間任何的資源要求都會進行請求驗證
而非網頁檔案(.aspx)而已,還包含WebService呼叫以及自訂的http Handlers,都會去驗證http請求的內容
因此在ASP.NET 4.0下,可能在非網頁請求的情況下,仍會發生請求驗證錯誤的訊息
這時為了避免這樣的問題,一樣在Web.Config中 <system.web>下加入下列語句
<httpRuntime requestValidationMode="2.0" />

這樣就可以讓請求驗證只焦點在.aspx網頁上了。