C#编写简单的聊天程序.docx
- 文档编号:8069488
- 上传时间:2023-01-28
- 格式:DOCX
- 页数:17
- 大小:53.48KB
C#编写简单的聊天程序.docx
《C#编写简单的聊天程序.docx》由会员分享,可在线阅读,更多相关《C#编写简单的聊天程序.docx(17页珍藏版)》请在冰豆网上搜索。
C#编写简单的聊天程序
窗体顶端
C#编写简单的聊天程序
引言
这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考。
文章大体分为四个部分:
程序的分析与设计、C#网络编程基础(篇外篇)、聊天程序的实现模式、程序实现。
程序的分析与设计
1.明确程序功能
如果大家现在已经参加了工作,你的经理或者老板告诉你,“小王,我需要你开发一个聊天程序”。
那么接下来该怎么做呢?
你是不是在脑子里有个雏形,然后就直接打开VS2005开始设计窗体,编写代码了呢?
在开始之前,我们首先需要进行软件的分析与设计。
就拿本例来说,如果只有这么一句话“一个聊天程序”,恐怕现在大家对这个“聊天程序”的概念就很模糊,它可以是像QQ那样的非常复杂的一个程序,也可以是很简单的聊天程序;它可能只有在对方在线的时候才可以进行聊天,也可能进行留言;它可能每次将消息只能发往一个人,也可能允许发往多个人。
它还可能有一些高级功能,比如向对方传送文件等。
所以我们首先需要进行分析,而不是一上手就开始做,而分析的第一步,就是搞清楚程序的功能是什么,它能够做些什么。
在这一步,我们的任务是了解程序需要做什么,而不是如何去做。
了解程序需要做什么,我们可以从两方面入手,接下来我们分别讨论。
1.1请求客户提供更详细信息
我们可以做的第一件事就是请求客户提供更加详细的信息。
尽管你的经理或老板是你的上司,但在这个例子中,他就是你的客户(当然通常情况下,客户是公司外部委托公司开发软件的人或单位)。
当遇到上面这种情况,我们只有少得可怜的一条信息“一个聊天程序”,首先可以做的,就是请求客户提供更加确切的信息。
比如,你问经理“对这个程序的功能能不能提供一些更具体的信息?
”。
他可能会像这样回答:
“哦,很简单,可以登录聊天程序,登录的时候能够通知其他在线用户,然后与在线的用户进行对话,如果不想对话了,就注销或者直接关闭,就这些吧。
”
有了上面这段话,我们就又可以得出下面几个需求:
1.程序可以进行登录。
2.登录后可以通知其他在线用户。
3.可以与其他用户进行对话。
4.可以注销或者关闭。
1.2对于用户需求进行提问,并进行总结
经常会有这样的情况:
可能客户给出的需求仍然不够细致,或者客户自己本身对于需求就很模糊,此时我们需要做的就是针对用户上面给出的信息进行提问。
接下来我就看看如何对上面的需求进行提问,我们至少可以向经理提出以下问题:
NOTE:
这里我穿插一个我在见到的一个印象比较深刻的例子:
客户往往向你表达了强烈的意愿他多么多么想拥有一个属于自己的网站,但是,他却没有告诉你网站都有哪些内容、栏目,可以做什么。
而作为开发者,我们显然关心的是后者。
1.登录时需要提供哪些内容?
需不需要提供密码?
2.允许多少人同时在线聊天?
3.与在线用户聊天时,可以将一条消息发给一个用户,还是可以一次将消息发给多个用户?
4.聊天时发送的消息包括哪些内容?
5.注销和关闭有什么区别?
6.注销和关闭对对方需不需要给对方提示?
由于这是一个范例程序,而我在为大家讲述,所以我只能再充当一下客户的角色,来回答上面的问题:
1.登录时只需要提供用户名称就可以了,不需要输入密码。
2.允许两个人在线聊天。
(这里我们只讲述这种简单情况,允许多人聊天需要使用多线程)
3.因为只有两个人,那么自然是只能发给一个用户了。
4.聊天发送的消息包括:
用户名称、发送时间还有正文。
5.注销并不关闭程序,只是离开了对话,可以再次进行连接。
关闭则是退出整个应用程序。
6.注销和关闭均需要给对方提示。
好了,有了上面这些信息我们基本上就掌握了程序需要完成的功能,那么接下来做什么?
开始编码了么?
上面的这些属于业务流程,除非你对它已经非常熟悉,或者程序非常的小,那么可以对它进行编码,但是实际中,我们最好再编写一些用例,这样会使程序的流程更加的清楚。
1.3编写用例
通常一个用例对应一个功能或者叫需求,它是程序的一个执行路径或者执行流程。
编写用例的思路是:
假设你已经有了这样一个聊天程序,那么你应该如何使用它?
我们的使用步骤,就是一个用例。
用例的特点就每次只针对程序的一个功能编写,最后根据用例编写代码,最终完成程序的开发。
我们这里的需求只有简单的几个:
登录,发送消息,接收消息,注销或关闭,上面的分析是对这几点功能的一个明确。
接下来我们首先编写第一个用例:
登录。
在开始之前,我们先明确一个概念:
客户端,服务端。
因为这个程序只是在两个人(机器)之间聊天,那么我们大致可以绘出这样一个图来:
我们期望用户A和用户B进行对话,那么我们就需要在它们之间建立起连接。
尽管“用户A”和“用户B”的地位是对等的,但按照约定俗称的说法:
我们将发起连接请求的一方称为客户端(或叫本地),另一端称为服务端(或叫远程)。
所以我们的登录过程,就是“用户A”连接到“用户B”的过程,或者说客户端(本地)连接到服务端(远程)的过程。
在分析这个程序的过程中,我们总是将其分为两部分,一部分为发起连接、发送消息的一方(本地),一方为接受连接、接收消息的一方(远程)。
登录和连接(本地)
主路径
可选路径
1.打开应用程序,显示登录窗口
2.输入用户名
3.点击“登录”按钮,登录成功
3.“登录”失败
如果用户名为空,重新进入第2步。
4.显示主窗口,显示登录的用户名称
5.点击“连接”,连接至远程
6.连接成功
6.1提示用户,连接已经成功。
6.连接失败
6.1提示用户,连接不成功
5.在用户界面变更控件状态
5.2连接为灰色,表示已经连接
5.3注销为亮色,表示可以注销
5.4发送为亮色,表示可以发消息
这里我们的用例名称为登录和连接,但是后面我们又打了一个括号,写着“本地”,它的意思是说,登录和连接是客户端,也就是发起连接的一方采取的动作。
同样,我们需要写下当客户端连接至服务端时,服务端采取的动作。
登录和连接(远程)
主路径
可选路径
1-4同客户端
5.等待连接
6.如果有连接,自动在用户界面显示“远程主机连接成功”
接下来我们来看发送消息。
在发送消息时,已经是登录了的,也就是“用户A”、“用户B”已经做好了连接,所以我们现在就可以只关注发送这一过程:
发送消息(本地)
主路径
可选路径
1.输入消息
2.点击发送按钮
2.没有输入消息,重新回到第1步
3.在用户界面上显示发出的消息
3.服务端已经断开连接或者关闭
3.1在客户端用户界面上显示错误消息
然后我们看一下接收消息,此时我们只关心接收消息这一部分。
接收消息(远程)
主路径
可选路径
1.侦听到客户端发来的消息,自动显示在用户界面上。
注意到这样一点:
当远程主机向本地返回消息时,它的用例又变为了上面的用例“发送消息(本地)”。
因为它们的角色已经互换了。
最后看一下注销,我们这里研究的是当我们在本地机器点击“注销”后,双方采取的动作:
注销(本地主动)
主路径
可选路径
1.点击注销按钮,断开与远程的连接
2.在用户界面显示已经注销
3.更改控件状态
3.1注销为灰色,表示已经注销
3.2连接为亮色,表示可以连接
3.3发送为灰色,表示无法发送
与此对应,服务端应该作出反应:
注销(远程被动)
主路径
可选路径
1.自动显示远程用户已经断开连接。
注意到一点:
当远程主动注销时,它采取的动作为上面的“本地主动”,本地采取的动作则为这里的“远程被动”。
至此,应用程序的功能分析和用例编写就告一段落了,通过上面这些表格,之后再继续编写程序变得容易了许多。
另外还需要记得,用例只能为你提供一个操作步骤的指导,在实现的过程中,因为技术等方面的原因,可能还会有少量的修改。
如果修改量很大,可以重新修改用例;如果修改量不大,那么就可以直接编码。
这是一个迭代的过程,也没有一定的标准,总之是以高效和合适为标准。
2.分析与设计
我们已经很清楚地知道了程序需要做些什么,尽管现在还不知道该如何去做。
我们甚至可以编写出这个程序所需要的接口,以后编写代码的时候,我们只要去实现这些接口就可以了。
这也符合面向接口编程的原则。
另外我们注意到,尽管这是一个聊天程序,但是却可以明确地划分为两部分,一部分发送消息,一部分接收消息。
另外注意上面标识为自动的语句,它们暗示这个操作需要通过事件的通知机制来完成。
关于委托和事件,可以参考这两篇文章:
∙C#中的委托和事件-委托和事件的入门文章,同时捎带讲述了Observer设计模式和.NET的事件模型
∙C#中的委托和事件(续)-委托和事件更深入的一些问题,包括异常、超时的处理,以及使用委托来异步调用方法。
2.1消息Message
首先我们可以定义消息,前面我们已经明确了消息包含三个部分:
用户名、时间、内容,所以我们可以定义一个结构来表示这个消息:
publicstructMessage{
privatereadonlystringuserName;
privatereadonlystringcontent;
privatereadonlyDateTimepostDate;
publicMessage(stringuserName,stringcontent){
this.userName=userName;
this.content=content;
this.postDate=DateTime.Now;
}
publicMessage(stringcontent):
this("System",content){}
publicstringUserName{
get{returnuserName;}
}
publicstringContent{
get{returncontent;}
}
publicDateTimePostDate{
get{returnpostDate;}
}
publicoverridestringToString(){
returnString.Format("{0}[{1}]:
\r\n{2}\r\n",userName,postDate,content);
}
}
2.2消息发送方IMessageSender
从上面我们可以看出,消息发送方主要包含这样几个功能:
登录、连接、发送消息、注销。
另外在连接成功或失败时还要通知用户界面,发送消息成功或失败时也需要通知用户界面,因此,我们可以让连接和发送消息返回一个布尔类型的值,当它为真时表示连接或发送成功,反之则为失败。
因为登录没有任何的业务逻辑,仅仅是记录控件的值并进行显示,所以我不打算将它写到接口中。
因此我们可以得出它的接口大致如下:
publicinterfaceIMessageSender{
boolConnect(IPAddressip,intport); //连接到服务端
boolSendMessage(Messagemsg); //发送用户
voidSignOut(); //注销系统
}
2.3消息接收方IMessageReceiver
而对于消息接收方,从上面我们可以看出,它的操作全是被动的:
客户端连接时自动提示,客户端连接丢失时显示自动提示,侦听到消息时自动提示。
注意到上面三个词都用了“自动”来修饰,在C#中,可以定义委托和事件,用于当程序中某种情况发生时,通知另外一个对象。
在这里,程序即是我们的IMessageReceiver,某种情况就是上面的三种情况,而另外一个对象则为我们的用户界面。
因此,我们现在首先需要定义三个委托:
publicdelegatevoidMessageReceivedEventHandler(stringmsg);
publicdelegatevoidClientConnectedEventHandler(IPEndPointendPoint);
publicdelegatevoidConnectionLostEventHandler(stringinfo);
接下来,我们注意到接收方需要侦听消息,因此我们需要在接口中定义的方法是StartListen()和StopListen()方法,这两个方法是典型的技术相关,而不是业务相关,所以从用例中是看不出来的,可能大家现在对这两个方法是做什么的还不清楚,没有关系,我们现在并不写实现,而定义接口并不需要什么成本,我们写下IMessageReceiver的接口定义:
publicinterfaceIMessageReceiver{
eventMessageReceivedEventHandlerMessageReceived;//接收到发来的消息
eventConnectionLostEventHandlerClientLost; //远程主动断开连接
eventClientConnectedEventHandlerClientConnected; //远程连接到了本地
voidStartListen(); //开始侦听端口
voidStopListen(); //停止侦听端口
}
我记得曾经看过有篇文章说过,最好不要在接口中定义事件,但是我忘了他的理由了,所以本文还是将事件定义在了接口中。
2.4主程序Talker
而我们的主程序是既可以发送,又可以接收,一般来说,如果一个类像获得其他类的能力,以采用两种方法:
继承和复合。
因为C#中没有多重继承,所以我们无法同时继承实现了IMessageReceiver和IMessageSender的类。
那么我们可以采用复合,将它们作为类成员包含在Talker内部:
publicclassTalker{
privateIMessageReceiverreceiver;
privateIMessageSendersender;
publicTalker(IMessageReceiverreceiver,IMessageSendersender){
this.receiver=receiver;
this.sender=sender;
}
}
现在,我们的程序大体框架已经完成,接下来要关注的就是如何实现它,现在让我们由设计走入实现,看看实现一个网络聊天程序,我们需要掌握的技术吧。
C#网络编程基础(篇外篇)
这部分的内容请参考C#网络编程系列文章,共5个部分较为详细的讲述了基于Socket的网络编程的初步内容。
编写程序代码
如果你已经看完了上面一节C#网络编程,那么本章完全没有讲解的必要了,所以我只列出代码,对个别值得注意的地方稍微地讲述一下。
首先需要了解的就是,我们采用的是三个模式中开发起来难度较大的一种,无服务器参与的模式。
还有就是我们没有使用广播消息,所以需要提前知道连接到的远程主机的地址和端口号。
1.实现IMessageSender接口
publicclassMessageSender:
IMessageSender{
TcpClientclient;
StreamstreamToServer;
//连接至远程
publicboolConnect(IPAddressip,intport){
try{
client=newTcpClient();
client.Connect(ip,port);
streamToServer=client.GetStream(); //获取连接至远程的流
returntrue;
}catch{
returnfalse;
}
}
//发送消息
publicboolSendMessage(Messagemsg){
try{
lock(streamToServer){
byte[]buffer=Encoding.Unicode.GetBytes(msg.ToString());
streamToServer.Write(buffer,0,buffer.Length);
returntrue;
}
}catch{
returnfalse;
}
}
//注销
publicvoidSignOut(){
if(streamToServer!
=null)
streamToServer.Dispose();
if(client!
=null)
client.Close();
}
}
这段代码可以用朴实无华来形容,所以我们直接看下一段。
2.实现IMessageReceiver接口
publicdelegatevoidPortNumberReadyEventHandler(intportNumber);
publicclassMessageReceiver:
IMessageReceiver{
publiceventMessageReceivedEventHandlerMessageReceived;
publiceventConnectionLostEventHandlerClientLost;
publiceventClientConnectedEventHandlerClientConnected;
//当端口号Ok的时候调用--需要告诉用户界面使用了哪个端口号在侦听
//这里是业务上体现不出来,在实现中才能体现出来的
publiceventPortNumberReadyEventHandlerPortNumberReady;
privateThreadworkerThread;
privateTcpListenerlistener;
publicMessageReceiver(){
((IMessageReceiver)this).StartListen();
}
//开始侦听:
显示实现接口
voidIMessageReceiver.StartListen(){
ThreadStartstart=newThreadStart(ListenThreadMethod);
workerThread=newThread(start);
workerThread.IsBackground=true;
workerThread.Start();
}
//线程入口方法
privatevoidListenThreadMethod(){
IPAddresslocalIp=IPAddress.Parse("127.0.0.1");
listener=newTcpListener(localIp,0);
listener.Start();
//获取端口号
IPEndPointendPoint=listener.LocalEndpointasIPEndPoint;
intportNumber=endPoint.Port;
if(PortNumberReady!
=null){
PortNumberReady(portNumber); //端口号已经OK,通知用户界面
}
while(true){
TcpClientremoteClient;
try{
remoteClient=listener.AcceptTcpClient();
}catch{
break;
}
if(ClientConnected!
=null){
//连接至本机的远程端口
endPoint=remoteClient.Client.RemoteEndPointasIPEndPoint;
ClientConnected(endPoint); //通知用户界面远程客户连接
}
StreamstreamToClient=remoteClient.GetStream();
byte[]buffer=newbyte[8192];
while(true){
try{
intbytesRead=streamToClient.Read(buffer,0,8192);
if(bytesRead==0){
thrownewException("客户端已断开连接");
}
stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);
if(MessageReceived!
=null){
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 编写 简单 聊天 程序