# [C# 網絡編程系列]專題十二:實現一個簡單的FTP服務器
**引言:**
休息一個國慶節后好久沒有更新文章了,主要是剛開始休息完心態還沒有調整過來的, 現在差不多進入狀態了, 所以繼續和大家分享下網絡編程的知識,在本專題中將和大家分享如何自己實現一個簡單的FTP服務器。在我們平時的上網過程中,一般都是使用FTP的客戶端來對商家提供的服務器進行訪問(上傳、下載文件),例如我們經常用到微軟的SkyDrive網盤,115網盤等,然而我們經常用到的都是網頁版本的,網頁版本和客戶端版本的不同,網頁版本的FTP客戶端,它與服務器的交流是使用HTTP協議發出對服務器的請求的,而客戶端版本采用的是FTP協議發出命令對服務器進行請求。然后我們接觸到FTP服務器卻很少的, 所以本專題中將和大家介紹下如何實現一個FTP服務器(不要覺得服務器很深奧一樣的,大家可以簡單的認為服務器也是一個程序,該程序是對客戶端發來的請求做處理的,請求大家可以簡單理解為字符串,從這個角度看, 服務器程序就是一個對字符串解析的過程。),也是為后面的一個專題做一個鋪墊,因為后面專題將和大家介紹下FTP客戶端——文件上傳下載器,有了自己自定義的FTP服務器后, 自定義的FTP客戶端就可以對自定義的FTP服務器進行訪問,使兩者形成一個完整的軟件,從而也讓大家對基于FTP協議的工具有一個初步的了解。
**一、基于FTP協議的客戶端和服務器是如何"溝通的"?**
FTP客戶端和FTP服務器之間的“溝通”分為四個階段的:
1\. 啟動FTP
客戶通過FTP客戶端軟件,發起FTP交互式的命令,就是告訴服務器(也就是一臺電腦,服務器上與一個程序(FTP服務)會接收命令,并解析發來的命令,然后發出回復信息)說:“我想和你聊聊天,可以嗎?”
2\. 建立控制連接
客戶端TCP層根據客戶給出的服務器IP地址,向服務器提供FTP服務的21號端口發出主動建立連接的請求,服務器接收到請求后,通過3次握手之后,客戶端和服務器之間就建立一個TCP連接(就是一條通道,就好比生活中馬路,有了馬路之后車才能夠在兩地之間運送東西),之后,所有用戶發出的FTP命令和服務器的回應都是通過該連接來傳送的, 所以也把這個TCP連接叫做**控制連接,**控制連接在用戶退出之前一直存在。
3\. 建立數據連接和進行文件傳輸
現在客戶端和服務器端已經建立聊天的通道了(控制連接),但是兩者聊天過程中如果互相想贈送禮物要怎么辦呢?(這里形象的把客戶端和服務器端文件的傳輸比喻兩個人通過聊天后互相贈送禮物的過程),此時我們就需要另外一條馬路(數據連接)來進行“禮物的贈送”了,具體贈送禮物的過程如下:
1. 客戶端通過控制連接向服務器發送一個上傳文件的命令時,會自己分配一個臨時的TCP端口號。
2. 客戶端通過控制連接向服務器發送一個命令(下面將會介紹的PORT命令)來告訴服務器自己的IP地址和臨時的端口號,然后再發送一條上傳文件的命令(可以理解為——客戶端要送禮物給服務器時,實際上不是簡單的發送一個送禮物命令的,在這之前還需要發送一條自我介紹命令(就是告訴服務器自己的IP地址和端口號)來告訴服務器自己就是剛剛和它聊天的那位,這也很符合我們日常送禮物的流程的,一般大家接到禮物都要弄明白送禮物的人是誰,是不是自己認識的)
3. 服務器接收到客戶端的IP地址和臨時端口號后,以這個IP地址和端口號為目標,使用服務器上的20端口(該端口是用來傳輸數據的端口)向客戶端發出主動建立連接的請求。
4. 客戶端收到請求后,通過3此握手后就與服務器之間建立了另外一條TCP連接——**數據連接**,即用來互相送禮物的通道。
5. 客戶端在自己的文件系統中選擇要贈送(上傳)的文件
6. 客戶端將文件寫入到文件傳輸進程中(寫入網絡流中)
7. 服務器端將傳輸來的文件在服務器端的文件系統中進行存儲
8. 文件傳輸完成后,由服務器主動關閉該數據的連接
4 關閉FTP
當用戶退出FTP時,通關客戶端發送退出命令,之后控制連接被關閉,FTP服務結束。
**二、從上面的溝通過程中你明白了什么?**
從上面客戶端與服務器端的溝通過程中,這里可以概括幾點:
(1)客戶端與服務器端進行交互過程中,傳輸層使用的是TCP協議而不是其他傳輸層協議
(2)溝通過程有兩條TCP連接——一條是控制連接,即傳輸命令和響應信息的通道,另一條是數據連接,即傳輸文件的馬路,并且必須先有控制連接才能建立數據連接,因為要進行文件傳輸首先必須知道客戶的IP地址和端口號,這個過程就是通過控制連接傳送的命令來告知服務器客戶端的IP地址和端口號,之后再在兩者之間建立數據連接來傳輸文件
(3)在服務器端,控制連接(端口號為21)和數據連接(端口號 為20)使用了不同的端口號
**三、贈送禮物的方式?——文件傳輸模式**
客戶端與FTP服務器建立數據連接之后,首先需要告訴服務器采用哪種文件傳輸模式,FTP提供了兩種文件傳輸模式,一種是主動(Port)模式,另一種是被動(Passive)模式。
主動模式——服務器向客戶端發起數據連接請求,被動模式——客戶端向服務器發起數據請求。
然而兩種模式有什么相同點和不同點呢?
兩種模式的相同點: 服務器都使用21號端口進行用戶驗證和管理
不同點: 傳送文件數據的方式不一樣,主動模式的FTP服務器數據端口固定在20,而被動模式的FTP服務器數據端口則在1025~65535之間的隨機數。
**3.1 主動模式**
主動模式——服務器主動連接客戶端,然后傳輸文件,在這種模式下,FTP客戶端先用一個端口N(N>1024)向服務器的21號端口發起控制連接,連接成功后,在發出PORT N+1命令告訴服務器自己監聽的端口為N+1;服務器接受到該命令后,用一個新的數據端口(20號端口)與客戶端的端口N+1建立連接,然后進行文件傳輸,而客戶端則通過監聽N+1端口接受文件數據。
_注意: 采用主動模式存在一個問題,如果客戶端安裝了防火墻或在內網時,由于防火墻一般不允許接受外部發起的標準端口以外的連接請求,因此外部FTP服務器就無法使用主動模式穿過防火墻主動連接客戶端(這里與客戶端連接的端口為N+1(N>1024),非標準端口),從而造成無法傳送文件數據,此時就需要采用被動模式傳送文件了。_
**3.2 被動模式**
被動模式——服務器被動接受客戶端連接請求,即控制連接請求和數據連接請求都是由客戶端發起,在這種模式下,FTP客戶端先隨機開始一個端口N向服務器的21號端口發起控制連接,然后向服務器發送PASV命令。服務器收到該命令后,會用一個新的端口P(P>1024)進行監聽,同時將該端口號告訴客戶端,客戶端接受到響應命令后,再通過新的端口N+1連接服務器的端口P,然后進行文件數據傳輸。
_注意:采用被動模式與主動模式也存在相同的問題,如果服務器安裝了防火墻,客戶端同樣可能無法與服務器端的端口P建立數據請求,因為該請求可能會被防火墻過濾掉。在實際應用中,服務器一般指定一個端口范圍,允許客戶端與該范圍內的端口建立數據連接,而不再這個范圍內的端口會被服務器的防火墻過濾掉,從而在一定程度上消除了針對服務器的惡意攻擊。_
**四、 FTP協議中有哪些命令的?**
協議簡單說就是一個規范,就好比打牌一樣,制定一個大家都能明白的規則,斗地主的規則被大家都認可的,但是私下我們也可以自定義規則來玩的(例如說三個只能帶一個等這樣的規則),同樣FTP規則也是大家都認可的一個協議,我們當然也可以自定義協議。
由于.Net平臺下目前還沒有提供對FTP服務器端開發的類庫,因此要實現一個FTP服務器端的應用程序,就必須了解FTP協議的詳細內容。
**4.1 FTP命令有哪些?**
FTP 協議中規定了一些大家都認識的命令和組成。FTP協議中的命令都由3~4個字母組成,命令與參數之間用空格隔開,每個命令用回車換行結束。
(1)訪問命令
(1)訪問命令有:
USER命令——格式為:USER <username>, 指定登錄的用戶名,以便服務器進行身份驗證。這個命令通常是控制連接后第一個發出的命令
PASS命令——格式為:PASS <password>, 指定用戶密碼,該命令必須跟在登錄用戶名命令之后。
REIN命令——格式為:REIN, 表示重新初始化用戶信息,該命令終止當前USER的傳輸,同時終止正在傳輸的數據,然后重置所有參數,并打開控制連接,以便客戶端再次發生USER命令。
QUIT命令——格式為:QUIT,關閉與服務器的連接
(2)模式設置命令:
PASV命令——格式為:PASV,該命令告訴FTP服務器,讓FTP服務器在指定的數據端口進行監聽,被動接受客戶端的請求。如果未指定任何模式,FTP服務器默認使用PASV模式
PORT命令——格式為:PORT <address>,該命令告訴FTP服務器,客戶端監聽的端口號是address,讓FTP服務器采用主動模式連接客戶端。
TYPE命令——格式為: TYPE <data type>,該命令指定要傳輸的數據類型,有ASCII和BINARY兩種類型。
MODE命令——格式為:MODE <mode>,該命令指定傳輸模式,S表示流,B表示塊,C表示壓縮。
(3)文件管理命令
CWD命令——格式為:CWD <directory>,該命令是用戶可以在不同的目錄或數據集下工作而不用改變登錄信息,directory一般是目錄名或與系統相關的文件集合。
PWD命令——格式為:PWD,該命令返回當前工作目錄。
MKD命令——格式為:MKD <directory>,該命令表示在指定路徑下創建新目錄,directory 表示特定目錄的字符串。
CDUP命令——格式為:CDUP,該命令表示回到上層目錄
RMD命令——格式為:RMD <directory>,刪除指定目錄,directory表示特定目錄的字符串。
LIST命令——格式為:LIST <name>,該命令返回指定路徑下的子目錄及文件列表,name 為路徑。省略路徑時,返回當前路徑下的文件列表。
NLIST命令——格式為:NLIST <directory>,該命令返回指定路徑下的目錄列表,省略路徑時,返回當前目錄。
RNFR命令——格式為:RNFR <old path>,該命令表示重新命名文件,該命令的下一條命令用RNTO指定新的文件名。
RNTO命令——格式為:RNTO <new path>,該命令和RNFR命令共同完成對文件的重命名。
DELE命令——格式為:DELE <filename>,該命令表示刪除指定路徑下的文件
(4)文件傳輸命令:
RETR命令——RETR <filename>,表示下載指定路徑的文件
STOR命令——STOR <filename>,表示上傳一個指定的文件,并將其存儲在指定的位置,如果文件已存在,原文件將被覆蓋,如果文件不存在,則創建新文件。
(5)其他命令
SYST命令——格式為:SYST,該命令返回服務器使用的操作系統。
**4.2 FTP響應碼**
客戶端發送FTP命令后,服務器需要返回FTP響應碼,響應碼即是回答,我們平常聊天中別人問了說了話或者問了問題,另外一方就需要回答,FTP協議中定義以響應碼的形式來作為回答,FTP響應碼由ASCII編碼的3位數字開頭,后面接一行文本提示信息,數字和提示信息中有一個空格,如XXX 接收請求。
每個響應碼同樣以回車換行結束。
FTP響應碼的3位數字每位都有特定的意義,具體見下表:
| | 響應碼 | 表示 |
| --- | --- |
| 第1位數字 | 1XX | 表示信息已被服務器正確接收,但尚未被處理 |
| | 2XX | 表示信息已被服務器正確處理完畢 |
| | 3XX | 彪西信息已被服務器正在接受,并正在處理中 |
| | 4XX | 表示信息處理錯誤(暫時) |
| | 5XX | 表示信息處理錯誤(永久) |
| 第2位數字 | X0X | 表示語法錯誤 |
| | X1X | 表示系統狀態與信息 |
| | X2X | 表示與FTP服務器系統連接狀態 |
| | X3X | 表示與用戶認證有關的信息 |
| | X4X | 表示未定義 |
| | X5X | 表示與文件系統有關的信息 |
下表列出了常用的響應碼所代表的意義:
| 響應碼 | 意義 | 響應碼 | 意義 |
| --- | --- | --- | --- |
| 110 | 重新啟動標記應答 | 332 | 登陸是需要賬戶信息 |
| 120 | 服務在指定時間內準備好 | 350 | 請求的文件操作需要進一步命令 |
| 125 | 數據連接打開——開始傳輸 | 421 | 服務關閉 |
| 150 | 文件狀態良好,將要打開數據連接 | 425 | 不能打開數據連接 |
| 200 | 命令成功 | 426 | 關閉連接,終止傳輸 |
| 202 | 命令沒有執行 | 450 | 文件不可用 |
| 211 | 系統狀態回復 | 451 | 中止請求操作:有本地錯誤 |
| 212 | 目錄狀態回復 | 452 | 磁盤空間不足 |
| 213 | 文件狀態回復 | 500 | 無效命令 |
| 214 | 幫助信息回復 | 501 | 語法錯誤 |
| 215 | 系統類型回復 | 502 | 命令未執行 |
| 220 | 服務就緒 | 503 | 命令順序錯誤 |
| 221 | 服務關閉控制連接,可以退出登陸 | 504 | 無效命令參數 |
| 225 | 數據連接打開,無傳輸正在進行 | 530 | 未登陸 |
| 226 | 關閉數據連接,請求的文件操作成功 | 532 | 存儲文件需要賬戶信息 |
| 227 | 進入被動模式 | 550 | 未執行請求操作 |
| 230 | 用戶已登陸 | 551 | 請求操作終止:頁類型未知 |
| 250 | 請求的文件操作完成 | 552 | 請求文件操作終止:超過存儲分配 |
| 257 | 創建路徑名 | 553 | 為執行請求的操作:文件名不合法 |
| 331 | 用戶名正確,需要口令 |
**五、實現自定義的FTP服務器**
相信大家看完上面的介紹對FTP協議以及FTP客戶端和FTP服務器的交互過程有一定的理解的,這時候大家知道理論后就一定很想知道知道這些之后可以做什么的?答案就是可以制作一個簡單的FTP服務器,大家可以根據代碼來進一步理解FTP協議。下面是程序中一些核心代碼片段:
View Code
```
// 啟動服務器
private void btnFtpServerStartStop_Click(object sender, EventArgs e)
{
if (myTcpListener == null)
{
listenThread = new Thread(ListenClientConnect);
listenThread.IsBackground = true;
listenThread.Start();
lstboxStatus.Enabled = true;
lstboxStatus.Items.Clear();
lstboxStatus.Items.Add("啟動Ftp服務...");
btnFtpServerStartStop.Text = "停止";
}
else
{
myTcpListener.Stop();
myTcpListener = null;
listenThread.Abort();
lstboxStatus.Items.Add("Ftp服務已停止!");
lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1;
btnFtpServerStartStop.Text = "啟動";
}
}
// 監聽端口,處理客戶端連接
private void ListenClientConnect()
{
myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text));
// 開始監聽傳入的請求
myTcpListener.Start();
AddInfo("啟動成功!");
AddInfo("Ftp服務運行中...[單機”停止“退出]");
while (true)
{
try
{
// 接收連接請求
TcpClient tcpClient = myTcpListener.AcceptTcpClient();
AddInfo(string.Format("客戶端({0})與本機({1})建立Ftp連接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint));
User user = new User();
user.commandSession = new UserSeesion(tcpClient);
user.workDir = tbxFtpRoot.Text;
Thread t = new Thread(UserProcessing);
t.IsBackground = true;
t.Start(user);
}
catch
{
break;
}
}
}
// 處理客戶端用戶請求
private void UserProcessing(object obj)
{
User user = (User)obj;
string sendString = "220 FTP Server v1.0";
RepleyCommandToUser(user, sendString);
while (true)
{
string receiveString = null;
try
{
// 讀取客戶端發來的請求信息
receiveString = user.commandSession.streamReader.ReadLine();
}
catch(Exception ex)
{
if (user.commandSession.tcpClient.Connected == false)
{
AddInfo(string.Format("客戶端({0}斷開連接!)", user.commandSession.tcpClient.Client.RemoteEndPoint));
}
else
{
AddInfo("接收命令失敗!" + ex.Message);
}
break;
}
if (receiveString == null)
{
AddInfo("接收字符串為null,結束線程!");
break;
}
AddInfo(string.Format("來自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString));
// 分解客戶端發來的控制信息中的命令和參數
string command = receiveString;
string param = string.Empty;
int index = receiveString.IndexOf(' ');
if (index != -1)
{
command = receiveString.Substring(0, index).ToUpper();
param = receiveString.Substring(command.Length).Trim();
}
// 處理不需登錄即可響應的命令(這里只處理QUIT)
if (command == "QUIT")
{
// 關閉TCP連接并釋放與其關聯的所有資源
user.commandSession.Close();
return;
}
else
{
switch (user.loginOK)
{
// 等待用戶輸入用戶名:
case 0:
CommandUser(user, command, param);
break;
// 等待用戶輸入密碼
case 1:
CommandPassword(user, command, param);
break;
// 用戶名和密碼驗證正確后登陸
case 2:
switch (command)
{
case "CWD":
CommandCWD(user, param);
break;
case "PWD":
CommandPWD(user);
break;
case "PASV":
CommandPASV(user);
break;
case "PORT":
CommandPORT(user, param);
break;
case "LIST":
CommandLIST(user, param);
break;
case "NLIST":
CommandLIST(user, param);
break;
// 處理下載文件命令
case "RETR":
CommandRETR(user, param);
break;
// 處理上傳文件命令
case "STOR":
CommandSTOR(user, param);
break;
// 處理刪除命令
case "DELE":
CommandDELE(user, param);
break;
// 使用Type命令在ASCII和二進制模式進行變換
case "TYPE":
CommandTYPE(user, param);
break;
default:
sendString = "502 command is not implemented.";
RepleyCommandToUser(user, sendString);
break;
}
break;
}
}
}
}
```
程序演示截圖:
首先在F:\盤下新建文件夾MyFtpServerRoot,在其中創建目錄結構并放一些文件資源,例如圖片,文檔等,程序中演示的目錄結構如下圖:

這樣,本地的FTP服務站點就已經建好了,運行FTP服務器程序,然后點擊“啟動”按鈕后就啟動了FTP服務器,運行結果如下圖所示:

然后配合上個專題中實現的FTP客戶端來完成與FTP服務器的“聊天”演示,因為FTP服務器程序中已經初始化用戶名和密碼(都為admin),所以FTP客戶端中取消選擇“匿名復選框”,直接輸入用戶名和密碼為admin后點擊“登錄”按鈕后就完成了用戶驗證的過程,并與FTP服務器建立了控制連接和數據連接。運行結果如下圖:

當然用戶可以通過"上傳"、“下載”和刪除按鈕來對FTP服務器上的文件進行操作,這里就不貼出運行圖片了, 大家可以下載源碼來測試下的。
**六、內容的結尾,說說后面的計劃吧**
這個專題介紹完后,我這個C#網絡編程系列也就介紹完了,這個系列中主要介紹網絡編程的一些入門知識,對于朋友在留言中經常提到的“打洞”技術以及一些網絡編程中一些更難的內容還大家一起努力來學習的,同時我也會在后面和大家分享下一些實際開發過程中的網絡編程的內容(在后面的文章打算和大家分享一個下載器的實現),最后,希望這個系列可以讓大家對網絡協議有一個最初的入門,這樣在實際的開發過程中才知道這些實現背后的原理。之后我總結下我這個系列的所有文章的索引,以便讓大家更好的閱讀和查找關于這個系列的所有文章。
源碼下載:[http://files.cnblogs.com/zhili/FtpServer.zip](http://files.cnblogs.com/zhili/FtpServer.zip),大家如果覺得不錯的話,還請大家推薦下,謝謝大家的支持
用來演示的服務器目錄:[http://files.cnblogs.com/zhili/MyFtpServerRoot.zip](http://files.cnblogs.com/zhili/MyFtpServerRoot.zip)
上個專題FTP文件上傳下載器源碼:[http://files.cnblogs.com/zhili/FTPUpDownloader.zip](http://files.cnblogs.com/zhili/FTPUpDownloader.zip)
- C# 基礎知識系列
- C# 基礎知識系列 專題一:深入解析委托——C#中為什么要引入委托
- C# 基礎知識系列 專題二:委托的本質論
- C# 基礎知識系列 專題三:如何用委托包裝多個方法——委托鏈
- C# 基礎知識系列 專題四:事件揭秘
- C# 基礎知識系列 專題五:當點擊按鈕時觸發Click事件背后發生的事情
- C# 基礎知識系列 專題六:泛型基礎篇——為什么引入泛型
- C# 基礎知識系列 專題七: 泛型深入理解(一)
- C# 基礎知識系列 專題八: 深入理解泛型(二)
- C# 基礎知識系列 專題九: 深入理解泛型可變性
- C#基礎知識系列 專題十:全面解析可空類型
- C# 基礎知識系列 專題十一:匿名方法解析
- C#基礎知識系列 專題十二:迭代器
- C#基礎知識 專題十三:全面解析對象集合初始化器、匿名類型和隱式類型
- C# 基礎知識系列 專題十四:深入理解Lambda表達式
- C# 基礎知識系列 專題十五:全面解析擴展方法
- C# 基礎知識系列 專題十六:Linq介紹
- C#基礎知識系列 專題十七:深入理解動態類型
- 你必須知道的異步編程 C# 5.0 新特性——Async和Await使異步編程更簡單
- 全面解析C#中參數傳遞
- C#基礎知識系列 全面解析C#中靜態與非靜態
- C# 基礎知識系列 C#中易混淆的知識點
- C#進階系列
- C#進階系列 專題一:深入解析深拷貝和淺拷貝
- C#進階系列 專題二:你知道Dictionary查找速度為什么快嗎?
- C# 開發技巧系列
- C# 開發技巧系列 使用C#操作Word和Excel程序
- C# 開發技巧系列 使用C#操作幻燈片
- C# 開發技巧系列 如何動態設置屏幕分辨率
- C# 開發技巧系列 C#如何實現圖片查看器
- C# 開發技巧 如何防止程序多次運行
- C# 開發技巧 實現屬于自己的截圖工具
- C# 開發技巧 如何使不符合要求的元素等于離它最近的一個元素
- C# 線程處理系列
- C# 線程處理系列 專題一:線程基礎
- C# 線程處理系列 專題二:線程池中的工作者線程
- C# 線程處理系列 專題三:線程池中的I/O線程
- C# 線程處理系列 專題四:線程同步
- C# 線程處理系列 專題五:線程同步——事件構造
- C# 線程處理系列 專題六:線程同步——信號量和互斥體
- C# 多線程處理系列專題七——對多線程的補充
- C#網絡編程系列
- C# 網絡編程系列 專題一:網絡協議簡介
- C# 網絡編程系列 專題二:HTTP協議詳解
- C# 網絡編程系列 專題三:自定義Web服務器
- C# 網絡編程系列 專題四:自定義Web瀏覽器
- C# 網絡編程系列 專題五:TCP編程
- C# 網絡編程系列 專題六:UDP編程
- C# 網絡編程系列 專題七:UDP編程補充——UDP廣播程序的實現
- C# 網絡編程系列 專題八:P2P編程
- C# 網絡編程系列 專題九:實現類似QQ的即時通信程序
- C# 網絡編程系列 專題十:實現簡單的郵件收發器
- C# 網絡編程系列 專題十一:實現一個基于FTP協議的程序——文件上傳下載器
- C# 網絡編程系列 專題十二:實現一個簡單的FTP服務器
- C# 互操作性入門系列
- C# 互操作性入門系列(一):C#中互操作性介紹
- C# 互操作性入門系列(二):使用平臺調用調用Win32 函數
- C# 互操作性入門系列(三):平臺調用中的數據封送處理
- C# 互操作性入門系列(四):在C# 中調用COM組件
- CLR
- 談談: String 和StringBuilder區別和選擇
- 談談:程序集加載和反射
- 利用反射獲得委托和事件以及創建委托實例和添加事件處理程序
- 談談:.Net中的序列化和反序列化
- C#設計模式
- UML類圖符號 各種關系說明以及舉例
- C#設計模式(1)——單例模式
- C#設計模式(2)——簡單工廠模式
- C#設計模式(3)——工廠方法模式
- C#設計模式(4)——抽象工廠模式
- C#設計模式(5)——建造者模式(Builder Pattern)
- C#設計模式(6)——原型模式(Prototype Pattern)
- C#設計模式(7)——適配器模式(Adapter Pattern)
- C#設計模式(8)——橋接模式(Bridge Pattern)
- C#設計模式(9)——裝飾者模式(Decorator Pattern)
- C#設計模式(10)——組合模式(Composite Pattern)
- C#設計模式(11)——外觀模式(Facade Pattern)
- C#設計模式(12)——享元模式(Flyweight Pattern)
- C#設計模式(13)——代理模式(Proxy Pattern)
- C#設計模式(14)——模板方法模式(Template Method)
- C#設計模式(15)——命令模式(Command Pattern)
- C#設計模式(16)——迭代器模式(Iterator Pattern)
- C#設計模式(17)——觀察者模式(Observer Pattern)
- C#設計模式(18)——中介者模式(Mediator Pattern)
- C#設計模式(19)——狀態者模式(State Pattern)
- C#設計模式(20)——策略者模式(Stragety Pattern)
- C#設計模式(21)——責任鏈模式
- C#設計模式(22)——訪問者模式(Vistor Pattern)
- C#設計模式(23)——備忘錄模式(Memento Pattern)
- C#設計模式總結
- WPF快速入門系列
- WPF快速入門系列(1)——WPF布局概覽
- WPF快速入門系列(2)——深入解析依賴屬性
- WPF快速入門系列(3)——深入解析WPF事件機制
- WPF快速入門系列(4)——深入解析WPF綁定
- WPF快速入門系列(5)——深入解析WPF命令
- WPF快速入門系列(6)——WPF資源和樣式
- WPF快速入門系列(7)——深入解析WPF模板
- WPF快速入門系列(8)——MVVM快速入門
- WPF快速入門系列(9)——WPF任務管理工具實現
- ASP.NET 開發
- ASP.NET 開發必備知識點(1):如何讓Asp.net網站運行在自定義的Web服務器上
- ASP.NET 開發必備知識點(2):那些年追過的ASP.NET權限管理
- ASP.NET中實現回調
- 跟我一起學WCF
- 跟我一起學WCF(1)——MSMQ消息隊列
- 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(4)——第一個WCF程序
- 跟我一起學WCF(5)——深入解析服務契約 上篇
- 跟我一起學WCF(6)——深入解析服務契約 下篇
- 跟我一起學WCF(7)——WCF數據契約與序列化詳解
- 跟我一起學WCF(8)——WCF中Session、實例管理詳解
- 跟我一起學WCF(9)——WCF回調操作的實現
- 跟我一起學WCF(10)——WCF中事務處理
- 跟我一起學WCF(11)——WCF中隊列服務詳解
- 跟我一起學WCF(12)——WCF中Rest服務入門
- 跟我一起學WCF(13)——WCF系列總結
- .NET領域驅動設計實戰系列
- .NET領域驅動設計實戰系列 專題一:前期準備之EF CodeFirst
- .NET領域驅動設計實戰系列 專題二:結合領域驅動設計的面向服務架構來搭建網上書店
- .NET領域驅動設計實戰系列 專題三:前期準備之規約模式(Specification Pattern)
- .NET領域驅動設計實戰系列 專題四:前期準備之工作單元模式(Unit Of Work)
- .NET領域驅動設計實戰系列 專題五:網上書店規約模式、工作單元模式的引入以及購物車的實現
- .NET領域驅動設計實戰系列 專題六:DDD實踐案例:網上書店訂單功能的實現
- .NET領域驅動設計實戰系列 專題七:DDD實踐案例:引入事件驅動與中間件機制來實現后臺管理功能
- .NET領域驅動設計實戰系列 專題八:DDD案例:網上書店分布式消息隊列和分布式緩存的實現
- .NET領域驅動設計實戰系列 專題九:DDD案例:網上書店AOP和站點地圖的實現
- .NET領域驅動設計實戰系列 專題十:DDD擴展內容:全面剖析CQRS模式實現
- .NET領域驅動設計實戰系列 專題十一:.NET 領域驅動設計實戰系列總結