# [C# 網絡編程系列]專題九:實現類似QQ的即時通信程序
引言:
前面專題中介紹了UDP、TCP和P2P編程,并且通過一些小的示例來讓大家更好的理解它們的工作原理以及怎樣.Net類庫去實現它們的。為了讓大家更好的理解我們平常中常見的軟件QQ的工作原理,所以在本專題中將利用前面專題介紹的知識來實現一個類似QQ的聊天程序。
**一、即時通信系統**
在我們的生活中經常使用即時通信的軟件,我們經常接觸到的有:QQ、阿里旺旺、MSN等等。這些都是屬于即時通信(Instant Messenger,IM)軟件,IM是指所有能夠即時發送和接收互聯網消息的軟件。
在前面專題P2P編程中介紹過P2P系統分兩種類型——單純型P2P和混合型P2P(QQ就是屬于混合型的應用),混合型P2P系統中的服務器(也叫索引服務器)起到協調的作用。在文件共享類應用中,如果采用混合型P2P技術的話,索引服務器就保存著文件信息,這樣就可能會造成版權的問題,然而在即時通信類的軟件中, 因為客戶端傳遞的都是簡單的聊天文本而不是網絡媒體資源,這樣就不存在版權問題了,在這種情況下,就可以采用混合型P2P技術來實現我們的即時通信軟件。前面已經講了,騰訊的QQ就是屬于混合型P2P的軟件。
因此本專題要實現一個類似QQ的聊天程序,其中用到的P2P技術是屬于混合型P2P,而不是前一專題中的采用的單純型P2P技術,同時本程序的實現也會用到TCP、UDP編程技術。具體的相關內容大家可以查看本系列的相關專題的。
**二、程序實現的詳細設計**
本程序采用P2P方式,各個客戶端之間直接發消息進行聊天,服務器在其中只是起到協調的作用,下面先理清下程序的流程:
**2.1 程序流程設計**
當一個新用戶通過客戶端登陸系統后,從服務器獲取當在線的用戶信息列表,列表信息包括系統中每個用戶的地址,然后用戶就可以單獨向其他發消息。如果有用戶加入或者在線用戶退出時,服務器就會及時發消息通知系統中的所有其他客戶端,達到它們即時地更新用戶信息列表。
根據上面大致的描述,我們可以把系統的流程分為下面幾步來更好的理解(大家可以參考QQ程序將會更好的理解本程序的流程):
1. 用戶通過客戶端進入系統,向服務器發出消息,請求登陸
2. 服務器收到請求后,向客戶端返回回應消息,表示同意接受該用戶加入,并把自己(指的是服務器)所在監聽的端口發送給客戶端
3. 客戶端根據服務器發送過來的端口號和服務器建立連接
4. 服務器通過該連接 把在線用戶的列表信息發送給新加入的客戶端。
5. 客戶端獲得了在線用戶列表后就可以自己選擇在線用戶聊天。(程序中另外設計一個類似QQ的聊天窗口來進行聊天)
6. 當用戶退出系統時也要及時通知服務器,服務器再把這個消息轉發給每個在線的用戶,使客戶端及時更新本地的用戶信息列表。
**2.2 通信協議設計**
所謂協議就是約定,即服務器和客戶端之間會話信息的內容格式進行約定,使雙方都可以識別,達到更好的通信。
下面就具體介紹下協議的設計:
**1\. 客戶端和服務器之間的對話**
(1)登陸過程
① 客戶端用匿名UDP的方式向服務器發出下面的信息:
_ login, username, localIPEndPoint_
消息內容包括三個字段,每個字段用 “,”分割,login表示的是請求登陸;username表示用戶名;localIPEndPint表示客戶端本地地址。
② 服務器收到后以匿名UDP返回下面的回應:
_Accept, port_
其中Accept表示服務器接受請求,port表示服務器所在的端口號,服務器監聽著這個端口的客戶端連接
③ 連接服務器,獲取用戶列表
客戶端從上一步獲得了端口號,然后向該端口發起TCP連接,向服務器索取在線用戶列表,服務器接受連接后將用戶列表傳輸到客戶端。用戶列表信息格式如下:
_username1,IPEndPoint1;username2,IPEndPoint2;...;end_
username1、username2表示用戶名,IPEndPoint1,IPEndPoint2表示對應的端點,每個用戶信息都是由"用戶名+端點"組成,用戶信息以“;”隔開,整個用戶列表以“end”結尾。
(2)注銷過程
用戶退出時,向服務器發送如下消息:
_logout,username,localIPEndPoint_
這條消息看字面意思大家都知道就是告訴服務器 username+localIPEndPoint這個用戶要退出了。
**2\. 服務器管理用戶**
(1)新用戶加入通知
因為系統中在線的每個用戶都有一份當前在線用戶表,因此當有新用戶登錄時,服務器不需要重復地給系統中的每個用戶再發送所有用戶信息,只需要將新加入用戶的信息通知其他用戶,其他用戶再更新自己的用戶列表。
服務器向系統中每個用戶廣播如下信息:
_login,username,remoteIPEndPoint_
在這個過程中服務器只是負責將收到的"login"信息轉發出去。
(2)用戶退出
與新用戶加入一樣,服務器將用戶退出的消息進行廣播轉發:
_logout,username,remoteIPEndPoint_
**3\. 客戶端之間聊天**
用戶進行聊天時,各自的客戶端之間是以P2P方式進行工作的,不與服務器有直接聯系,這也是P2P技術的特點。
聊天發送的消息格式如下:
_talk, longtime, selfUserName, message_
其中,talk表明這是聊天內容的消息;longtime是長時間格式的當前系統時間;selfUserName為發送發的用戶名;message表示消息的內容。
協議設計介紹完后,下面就進入本程序的具體實現的介紹的。
注:協議是本程序的核心,也是所有軟件的核心,每個軟件產品的協議都是不一樣的,QQ有自己的一套協議,MSN又有另一套協議,所以使用的QQ的用戶無法和用MSN的朋友進行聊天。
**三、程序的實現**
服務器端核心代碼:
View Code
```
1 // 啟動服務器
2 // 根據博客中協議的設計部分
3 // 客戶端先向服務器發送登錄請求,然后通過服務器返回的端口號
4 // 再與服務器建立連接
5 // 所以啟動服務按鈕事件中有兩個套接字:一個是接收客戶端信息套接字和
6 // 監聽客戶端連接套接字
7 private void btnStart_Click(object sender, EventArgs e)
8 {
9 // 創建接收套接字
10 serverIp = IPAddress.Parse(txbServerIP.Text);
11 serverIPEndPoint = new IPEndPoint(serverIp, int.Parse(txbServerport.Text));
12 receiveUdpClient = new UdpClient(serverIPEndPoint);
13 // 啟動接收線程
14 Thread receiveThread = new Thread(ReceiveMessage);
15 receiveThread.Start();
16 btnStart.Enabled = false;
17 btnStop.Enabled = true;
18
19 // 隨機指定監聽端口
20 Random random = new Random();
21 tcpPort = random.Next(port + 1, 65536);
22
23 // 創建監聽套接字
24 tcpListener = new TcpListener(serverIp, tcpPort);
25 tcpListener.Start();
26
27 // 啟動監聽線程
28 Thread listenThread = new Thread(ListenClientConnect);
29 listenThread.Start();
30 AddItemToListBox(string.Format("服務器線程{0}啟動,監聽端口{1}",serverIPEndPoint,tcpPort));
31 }
32
33 // 接收客戶端發來的信息
34 private void ReceiveMessage()
35 {
36 IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
37 while (true)
38 {
39 try
40 {
41 // 關閉receiveUdpClient時下面一行代碼會產生異常
42 byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
43 string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
44
45 // 顯示消息內容
46 AddItemToListBox(string.Format("{0}:{1}",remoteIPEndPoint,message));
47
48 // 處理消息數據
49 // 根據協議的設計部分,從客戶端發送來的消息是具有一定格式的
50 // 服務器接收消息后要對消息做處理
51 string[] splitstring = message.Split(',');
52 // 解析用戶端地址
53 string[] splitsubstring = splitstring[2].Split(':');
54 IPEndPoint clientIPEndPoint = new IPEndPoint(IPAddress.Parse(splitsubstring[0]), int.Parse(splitsubstring[1]));
55 switch (splitstring[0])
56 {
57 // 如果是登錄信息,向客戶端發送應答消息和廣播有新用戶登錄消息
58 case "login":
59 User user = new User(splitstring[1], clientIPEndPoint);
60 // 往在線的用戶列表添加新成員
61 userList.Add(user);
62 AddItemToListBox(string.Format("用戶{0}({1})加入", user.GetName(), user.GetIPEndPoint()));
63 string sendString = "Accept," + tcpPort.ToString();
64 // 向客戶端發送應答消息
65 SendtoClient(user, sendString);
66 AddItemToListBox(string.Format("向{0}({1})發出:[{2}]", user.GetName(), user.GetIPEndPoint(), sendString));
67 for (int i = 0; i < userList.Count; i++)
68 {
69 if (userList[i].GetName() != user.GetName())
70 {
71 // 給在線的其他用戶發送廣播消息
72 // 通知有新用戶加入
73 SendtoClient(userList[i], message);
74 }
75 }
76
77 AddItemToListBox(string.Format("廣播:[{0}]", message));
78 break;
79 case "logout":
80 for (int i = 0; i < userList.Count; i++)
81 {
82 if (userList[i].GetName() == splitstring[1])
83 {
84 AddItemToListBox(string.Format("用戶{0}({1})退出",userList[i].GetName(),userList[i].GetIPEndPoint()));
85 userList.RemoveAt(i); // 移除用戶
86 }
87 }
88 for (int i = 0; i < userList.Count; i++)
89 {
90 // 廣播注銷消息
91 SendtoClient(userList[i], message);
92 }
93 AddItemToListBox(string.Format("廣播:[{0}]", message));
94 break;
95 }
96 }
97 catch
98 {
99 // 發送異常退出循環
100 break;
101 }
102 }
103 AddItemToListBox(string.Format("服務線程{0}終止", serverIPEndPoint));
104 }
105
106 // 向客戶端發送消息
107 private void SendtoClient(User user, string message)
108 {
109 // 匿名方式發送
110 sendUdpClient = new UdpClient(0);
111 byte[] sendBytes = Encoding.Unicode.GetBytes(message);
112 IPEndPoint remoteIPEndPoint =user.GetIPEndPoint();
113 sendUdpClient.Send(sendBytes,sendBytes.Length,remoteIPEndPoint);
114 sendUdpClient.Close();
115 }
116
117 // 接受客戶端的連接
118 private void ListenClientConnect()
119 {
120 TcpClient newClient = null;
121 while (true)
122 {
123 try
124 {
125 newClient = tcpListener.AcceptTcpClient();
126 AddItemToListBox(string.Format("接受客戶端{0}的TCP請求",newClient.Client.RemoteEndPoint));
127 }
128 catch
129 {
130 AddItemToListBox(string.Format("監聽線程({0}:{1})", serverIp, tcpPort));
131 break;
132 }
133
134 Thread sendThread = new Thread(SendData);
135 sendThread.Start(newClient);
136 }
137 }
138
139 // 向客戶端發送在線用戶列表信息
140 // 服務器通過TCP連接把在線用戶列表信息發送給客戶端
141 private void SendData(object userClient)
142 {
143 TcpClient newUserClient = (TcpClient)userClient;
144 userListstring = null;
145 for (int i = 0; i < userList.Count; i++)
146 {
147 userListstring += userList[i].GetName() + ","
148 + userList[i].GetIPEndPoint().ToString() + ";";
149 }
150
151 userListstring += "end";
152 networkStream = newUserClient.GetStream();
153 binaryWriter = new BinaryWriter(networkStream);
154 binaryWriter.Write(userListstring);
155 binaryWriter.Flush();
156 AddItemToListBox(string.Format("向{0}發送[{1}]", newUserClient.Client.RemoteEndPoint, userListstring));
157 binaryWriter.Close();
158 newUserClient.Close();
159 }
```
客戶端核心代碼:
View Code
```
1 // 登錄服務器
2 private void btnlogin_Click(object sender, EventArgs e)
3 {
4 // 創建接受套接字
5 IPAddress clientIP = IPAddress.Parse(txtLocalIP.Text);
6 clientIPEndPoint = new IPEndPoint(clientIP, int.Parse(txtlocalport.Text));
7 receiveUdpClient = new UdpClient(clientIPEndPoint);
8 // 啟動接收線程
9 Thread receiveThread = new Thread(ReceiveMessage);
10 receiveThread.Start();
11
12 // 匿名發送
13 sendUdpClient = new UdpClient(0);
14 // 啟動發送線程
15 Thread sendThread = new Thread(SendMessage);
16 sendThread.Start(string.Format("login,{0},{1}", txtusername.Text, clientIPEndPoint));
17
18 btnlogin.Enabled = false;
19 btnLogout.Enabled = true;
20 this.Text = txtusername.Text;
21 }
22
23 // 客戶端接受服務器回應消息
24 private void ReceiveMessage()
25 {
26 IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any,0);
27 while (true)
28 {
29 try
30 {
31 // 關閉receiveUdpClient時會產生異常
32 byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
33 string message = Encoding.Unicode.GetString(receiveBytes,0,receiveBytes.Length);
34
35 // 處理消息
36 string[] splitstring = message.Split(',');
37
38 switch (splitstring[0])
39 {
40 case "Accept":
41 try
42 {
43 tcpClient = new TcpClient();
44 tcpClient.Connect(remoteIPEndPoint.Address, int.Parse(splitstring[1]));
45 if (tcpClient != null)
46 {
47 // 表示連接成功
48 networkStream = tcpClient.GetStream();
49 binaryReader = new BinaryReader(networkStream);
50 }
51 }
52 catch
53 {
54 MessageBox.Show("連接失敗", "異常");
55 }
56
57 Thread getUserListThread = new Thread(GetUserList);
58 getUserListThread.Start();
59 break;
60 case "login":
61 string userItem = splitstring[1] + "," + splitstring[2];
62 AddItemToListView(userItem);
63 break;
64 case "logout":
65 RemoveItemFromListView(splitstring[1]);
66 break;
67 case "talk":
68 for (int i = 0; i < chatFormList.Count; i++)
69 {
70 if (chatFormList[i].Text == splitstring[2])
71 {
72 chatFormList[i].ShowTalkInfo(splitstring[2], splitstring[1], splitstring[3]);
73 }
74 }
75
76 break;
77 }
78 }
79 catch
80 {
81 break;
82 }
83 }
84 }
85
86 // 從服務器獲取在線用戶列表
87 private void GetUserList()
88 {
89 while (true)
90 {
91 userListstring = null;
92 try
93 {
94 userListstring = binaryReader.ReadString();
95 if (userListstring.EndsWith("end"))
96 {
97 string[] splitstring = userListstring.Split(';');
98 for (int i = 0; i < splitstring.Length - 1; i++)
99 {
100 AddItemToListView(splitstring[i]);
101 }
102
103 binaryReader.Close();
104 tcpClient.Close();
105 break;
106 }
107 }
108 catch
109 {
110 break;
111 }
112 }
113 }
114 // 發送登錄請求
115 private void SendMessage(object obj)
116 {
117 string message = (string)obj;
118 byte[] sendbytes = Encoding.Unicode.GetBytes(message);
119 IPAddress remoteIp = IPAddress.Parse(txtserverIP.Text);
120 IPEndPoint remoteIPEndPoint = new IPEndPoint(remoteIp, int.Parse(txtServerport.Text));
121 sendUdpClient.Send(sendbytes, sendbytes.Length, remoteIPEndPoint);
122 sendUdpClient.Close();
123 }
```
程序的運行結果:
首先先運行服務器窗口,在服務器窗口點擊“啟動”按鈕來啟動服務器,然后客戶端首先指定服務器的端口號,修改用戶名(這里也可以不修改,使用默認的也可以),然后點擊“登錄”按鈕來登陸服務器(也就是告訴服務器本地的客戶端地址),然后從服務器端獲得在線用戶列表,界面演示如下:

然后用戶可以雙擊在線用戶進行聊天(此程序支持與多人進行聊天),下面是功能的演示圖片:

雙方進行聊天時,這里沒有實現像QQ一樣,有人發信息來在對應的客戶端就有消息提醒的功能的, 所以雙方進行聊天的過程中,每個客戶端都需要在在線用戶列表中點擊聊天的對象來激活聊天對話框(意思就是從圖片中可以看出“天涯”客戶端想和劍癡聊天的話,就在“在線用戶”列表雙擊劍癡來激活聊天窗口,同時“劍癡”客戶端也必須雙擊“天涯”來激活聊天窗口,這樣雙方就看到對方發來的信息了,(不激活窗口,也是發送了信息,只是沒有一個窗口來進行顯示)),而且從圖片中也可以看出——此程序支持與多人聊天,即天涯同時與“劍癡”和"大地"同時聊天。
**四、總結**
本專題介紹了如何去實現一個類似QQ的聊天程序,一方面讓大家可以鞏固前面專題的內容,另一方面讓大家更好的理解即時通信軟件(騰訊QQ)的工作原理和軟件協議的設計。
后面一專題將介紹如何去實現郵件系統中常用的功能——實現一個簡單的郵件應用。
本程序的源代碼鏈接:[http://files.cnblogs.com/zhili/IM.zip](http://files.cnblogs.com/zhili/IM.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 領域驅動設計實戰系列總結