# [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 }
```
程序的运行结果:
首先先运行服务器窗口,在服务器窗口点击“启动”按钮来启动服务器,然后客户端首先指定服务器的端口号,修改用户名(这里也可以不修改,使用默认的也可以),然后点击“登录”按钮来登陆服务器(也就是告诉服务器本地的客户端地址),然后从服务器端获得在线用户列表,界面演示如下:
![](https://box.kancloud.cn/2016-01-23_56a2eb37a3e31.png)
然后用户可以双击在线用户进行聊天(此程序支持与多人进行聊天),下面是功能的演示图片:
![](https://box.kancloud.cn/2016-01-23_56a2eb37bb89c.png)
双方进行聊天时,这里没有实现像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 领域驱动设计实战系列总结