• 429.50 KB
  • 2022-04-22 13:33:04 发布

语音聊天软件设计毕业论文.doc

  • 48页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档共5页,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,可选择认领,认领后既往收益都归您。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细先通过免费阅读内容等途径辨别内容交易风险。如存在严重挂羊头卖狗肉之情形,可联系本站下载客服投诉处理。
  4. 文档侵权举报电话:19940600175。
'语音聊天软件设计毕业论文目录局域网聊天工具设计1——文本聊天、语音聊天、好友列表及用户信息管理1LANChatToolDesign2——Textchat,filetransferandusermanagement2第1章引言31.1系统开发的背景31.2聊天工具开发的目标31.3聊天工具开发的现实意义3第2章开发平台与相关知识简介52.1系统综合要求52.1.1硬件要求:52.1.2程序运行环境:52.2C#简介52.3MicroSoft.NETFramework简介52.4MicrosoftDirectXSDK简介62.5.NET与多线程技术62.6C/S结构与WINDOWSSOCKETS网络编程62.7XML简介9第3章需求分析103.1用户需求103.2系统功能需求103.3性能要求103.4测试环境规定113.5可行性研究11第4章系统设计124.1系统目标124.2系统功能结构124.3业务流程图134.4系统模块划分13第5章系统模块详细设计145.1文本聊天模块实现145.1.1文本聊天模块概述145.1.2文本聊天服务器工作原理145.1.3文本聊天客户端工作原理155.1.4P2P文本聊天165.2语音聊天模块实现195.2.1文件传输模块概述195.2.2文件传输接收端工作原理195.2.3文件传输发送端工作原理205.2.4P2P语音聊天2147 5.3用户管理模块实现245.3.1用户管理模块概述245.3.2用户数据模型245.3.3XML文档结构设计255.3.4用户信息的保存255.3.5用户数据的遍历26第6章系统测试286.1系统测试概述286.2白盒测试286.2.1测试环境配置286.2.2正确性测试286.3黑盒测试296.3.1测试环境配置296.3.2功能性测试296.3.3容错性测试296.3.4性能与效率测试306.3.5易用性测试316.3.6文档测试31第7章主要问题及解决327.1多线程问题327.2套接字异常327.3网络流异常32参考文献33附录3447 局域网聊天工具设计——文本聊天、语音聊天、好友列表及用户信息管理摘要:随着计算机网络技术的发展,各种各样基于网络的应用也随之诞生,比如基于互联网的信息发布,通信,数据共享等等。局域网的发展也同样迅速。很多政府机构,企业,学校,都是先以一个统一的局域网联结在一起,再分别接入INTERNET。因此基于局域网的即时通信工具,就这样应运而生了。本文提出了一个局域网聊天工具的设计,并在WINDOWS平台上加以了实现。本设计将语音聊天、文本聊天和文件传输等功能综合在一个客户端程序之内,使用C#语言进行网络编程,用多线程实现不同的并行任务,使用可扩展标记语言xml进行简单的数据存储和管理,并进行了人性化的界面设计,使用起来简单方便,并且功能十分合理,又易于扩展以及个性化定制。关键词:局域网;文本聊天;可扩展标记语言;多线程LANChatToolDesign——Textchat,filetransferandusermanagementAbstract:Alongwiththehigh-speeddevelopmentofthecomputernetworktechnology,variousofapplicationswhicharebasedonnetworkwereborn,suchasInternet-basedinformationreleasing,communications,datasharingandsoon.ThedevelopmentofLANisthesamefast.BecauseofSomegovernmentinstitutions,enterprisesandschoolsconstituteaLANfirst,thenjoinintoINTERNET,thereal-timeLAN-basedcommunicationtoolsemerged.thispaperproposedaLANchattooldesigning,andthenimplementitonWINDOWSplatform.Thedesignintegratedvoice-chat,text-chatandfile-transferandotherfunctionsinaclientprocedure.ItusedC#languagefornetworkprogrammingwithmulti-threadedparalleltoachievedifferenttasks,anditusedxmltomakesimpledatastorageandmanagementcometrue,anddesignedauser-friendlyandeasytouseinterface,andit"sfunctionsarereasonableandeasytoextend,aswellascustomization.Keywords:LAN;text-chat;file-transfer;xml;multi-threaded47 第1章引言1.1系统开发的背景当今世界正处于信息时代,计算机和通信网络是这一时代所谓“信息基础设施”。随着网络的普及化,网络作为人们相互间沟通与交流联系的现代化工具日益重要,可以说现在人们的生活已经离不开网络。在现实生活中人们可以通过多种手段与家人亲戚朋友交流通讯,像电话,电子邮件等。但这些手段都有些缺点,要么费用太高,要么实时性不强,要么1次只能和1位好友进行交流。于是这就需要1种便宜,快速,能同时与多个好友进行通讯的网络工具的出现,而网络聊天工具就满足了这些需求,填补了这个空白。在互联网相当普及的今天,在互联网上聊天对很多“网虫”来说已经是家常便饭了。聊天室程序可以说是网上最简单的多点通信程序。一个简单的聊天室,从程序员的观点来看就是在多个I/O端点之间实现多对多的通信。广域网上的即时通信工具,如今一般采用UDP或者TCP协议体系来实现,开发技术已经比较成熟,这些软件,在使用方面各有特色,在实现方面也各有所长,但基于这些产品正在商业运营阶段,其实现方式属于商业机密,具体细节不可能得知,但是它在大的方面无非就是利用各种平台上的网络通信接口,建构基于TCP/IP,或者UDP协议的软件产品。网络的飞速发展使网络应用程序的开发地位显的越来越重要,而网络应用程序的开发和传统应用程序的开发在思想和实现上有很大的区别,随着网络技术和数据库管理系统的发展,C/S体系结构在软件的开发中越来越流行。因为这种结构的应用软件可以充分发挥网络的作用和数据库系统的优势,以满足人们各种应用的需求。1.2聊天工具开发的目标本设计的主要工作是设计一个基于WINDOWS平台的局域网即时聊天工具,具体是采用P2P模式实现用户之间的通信,然后阐述本软件的功能、特点及使用方法,并详细阐述开发本软件所用的相关技术,具体分析本软件的各个模块的功能及实现方法,说明本软件的设计思想及方法。1.3聊天工具开发的现实意义局域网聊天工具,是在局域网内部使用的,用户之间用来交流的一个工具,一般都具有文本聊天和文件传输功能。局域网聊天软件因其使用简单,系统资源消耗少等优点,成为各企事业单位等的局域网内广泛应用的软件之一。47 当前的局域网聊天工具有很多,最有名的算IPMSG(飞鸽传书)。Ipmsg能够实现局域网内消息、文件传递。但是,ipmsg在某些时候,在同一vlan下的用户,能够彼此看见对方,却不能通信,因为其采用的是udp协议作为消息传递协议,而这种传输是不稳定的,所以有时候能看到对方的用户之间不能实现互相通信,怎么办呢?一种解决方法就是,采用tcp协议,面向连接的传输层协议作为消息传递协议,开发出相应的网络应用程序就能够实现彼此连接的双方进行信息的传递。随着互联网的不断发展,“互联网要担当起通讯大任”的声音不绝于耳。未来的电信业务将呈现多元化格局。同样是话音业务,可能是PSTN网络(传统电话网)提供的,可能是Internet提供的,还可能是有线电视网络,甚至电力网、煤气管道网提供的。而用户的选择也将包括电脑与电脑、电脑与电话、电话与电话、电话与(智能)手机等通话方式。这一切,都是以IP为基础的通讯网络,而非传统通讯模式的电信服务。所以,VOIP将是聊天工具的一个发展方向。现有的局域网聊天工具,一般都不具备语音聊天的功能,本软件集成了语音聊天模块,用户可以通过此软件实现语音互通,提高局域网用户之间信息传递效率以及交流质量。通过开发本软件,可以了解当前流行的voip技术,练习socket编程,扩展知识面,锻炼编程的能力等,所以极具研究价值。47 第2章开发平台与相关知识简介2.1系统综合要求2.1.1硬件要求:PC机:2台或者2台以上,RAM256MB以上,CPU400MHZ以上,硬盘40GB以上、耳麦各2副局域网2.1.2程序运行环境:系统开发平台:MicrosoftVisualStrdio2008MicrosoftDirectXSDK(November2008)系统开发语言:C#运行平台:Window2000,WindowsXp,WindowsServer2003运行环境:Microsoft.NetFreamwork2.0分辨率:最佳效果1024×7682.2C#简介C#(发音为CSharp)是由微软公司所开发的一种面向对象,且运行于.NETFramework之上的高级程序设计语言。C#的设计目的是简化网络应用。使用C#语言能够迅速地架构基于Windows和Internet的应用程序和组件,如标准的Windows应用程序和控制台应用程序,编译后生成的文件扩展名为EXE;程序库应用程序,编译后生成的文件扩展名为DLL,主要用来共享程序代码。2.3MicroSoft.NETFramework简介.NETFramework是由微软开发,一个致力于敏捷软件开发(Agilesoftwaredevelopment)、快速应用开发(Rapidapplicationdevelopment)、平台无关性和网络透明化的软件开发平台。.NET是微软为下一个十年对服务器和桌上型软件工程迈出的第一步。NET包含许多有助于Internet和Intranet应用迅捷开发的技术。.NET也为编程界面(API)提供了新功能和开发工具。这些革新使得程序设计员可以同时进行Windows应用软件和网络应用软件以及元件和服务(webservice)的开发。.NET提供了一个新的反射性的且面向对象程序设计编程界面。.NET47 设计得足够通用化从而使许多不同高级语言都得以被汇集。2.4MicrosoftDirectXSDK简介SDK是SoftwareDevelopmentKit的缩写,中文意思就是“软件开发工具包”。这是一个覆盖面相当广泛的名词,可以这么说:辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做“SDK”。具体到我们这个系列教程,我们后面只讨论广义SDK的一个子集——即开发Windows平台下的应用程序所使用的SDK。SDK提供了一整套开发Windows应用程序所需的相关文件、范例和工具的“工具包”。由于SDK包含了使用API的必需资料,所以人们也常把仅使用API来编写Windows应用程序的开发方式叫做“SDK编程”。而API和SDK是开发Windows应用程序所必需的东西,所以其它编程框架和类库都是建立在它们之上的,比如VCL和MFC,虽然他们比起“SDK编程”来有着更高的抽象度,但这丝毫不妨碍它们在需要的时候随时直接调用API函数。2.5.NET与多线程技术多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。在本质上和结构来说,.NET是一个多线程的环境。有两种主要的多线程方法是.NET所提倡的:使用ThreadStart来开始你自己的进程,直接的(使用ThreadPool.QueueUserWorkItem)或者间接的(比如Stream.BeginRead,或者调用BeginInvoke)使用ThreadPool类。线程的基类是System.Threading。所有线程通过CLI来进行管理。2.6C/S结构与WINDOWSSOCKETS网络编程47 通常的通信工具,都采用客户机/服务器(C/S)体系结构,C/S结构是这样的一种结构:它包括一个客户机(或前端),一个服务器(或称后端),客户机的作用是访问和处理远程服务器上的数据,服务器的作用是接收和处理客户机的数据请求。有时,可能有多个客户向同一个服务器同时请求服务,这就需要服务器决定怎样处理这些请求。Client/Server结构是当前数据库应用程序中极为流行的一种方式。尤其是网络技术的发展,使得当前很多系统都采用这种方式进行构造,其最大的优点是将计算机工作任务分别由客户端和服务器端来共同完成,这样有利于充分合理的利用系统资源。另外它的服务器端还可以将信息集中起来,任何客户机都可以通过访问服务器而获得所需的信息。Client/Server模型最终可归结为一种“请求/应答”关系。一个请求总是首先被客户发出,然后服务器总是被动地接收请求,返回客户需要的结果。在客户发出一个请求之前,服务进程一直处于休眠状态。一个客户提出请求后,服务进程被“唤醒”并且为客户提供服务,对客户的请求做出所需要的应答,如图2-1所示。图2-1客户机/服务器通信结构示图为了方便Client/Server模型的网络编程,90年代初,由Microsoft联合了其他几家公司共同制定了一套WINDOWS下的网络编程接口,即WindowsSockets规范,它不是一种网络协议,而是一套开放的、支持多种协议的Windows下的网络编程接口。现在的Winsock已经基本上实现了与协议无关,你可以使用Winsock来调用多种协议的功能,但较常使用的是TCP/IP协议。在IP连接领域有两种通信类型:面向连接的(Connection-oriented)和无连接的(Connectionless)。在面向连接的套接字中,使用TCP协议来建立两个IP地址端点之间的会话。一旦建立了这种连接,就可以在设备之间可靠地传输数据。为了建立面向连接的套接字,服务器端和客户端必须分别进行编程。在System.Net.Sockets名空间下,有两个专门用于TCP协议编程的类:TcpClient类和TcpListener类。这两个类提供了直观的易于使用的属性和方法,从而降低了TCP协议编程的难度。一般情况下,使用TcpClient连接、发送和接收数据,使用TcpListener监听是否有传入的连接请求,使用Close()方法释放与TcpClient关联的所有资源。C/S模式下Windowssocket编程示意图,如图2-2所示。47 图2-2C/S模式下socket编程具体到这个项目,服务器只是用来验证用户的登录,以及广播用户的登录状态等。具体的通信,采用P2P模式,每两个客户端要进行连接通信时都直接连通而不用通过服务器来进行信息的中转,这样设计的优点是既可以对用户进行管理,又不会造成服务器的通信负担。所以,系统结构将设计成如图2-3的方式。图2-3客户机直接通信结构示图47 2.7XML简介XML(ExtensibleMarkupLanguage)即可扩展标记语言,它与HTML一样,都是SGML(StandardGeneralizedMarkupLanguage,标准通用标记语言)。Xml是Internet环境中跨平台的,依赖于内容的技术,是当前处理结构化文档信息的有力工具。扩展标记语言XML是一种简单的数据存储语言,使用一系列简单的标记描述数据,而这些标记可以用方便的方式建立,虽然XML占用的空间比二进制数据要占用更多的空间,但XML极其简单易于掌握和使用。XML与Access,Oracle和SQLServer等数据库不同,数据库提供了更强有力的数据存储和分析能力,例如:数据索引、排序、查找、相关一致性等,XML仅仅是展示数据。事实上XML与其他数据表现形式最大的不同是:他极其简单。这是一个看上去有点琐细的优点,但正是这点使XML与众不同。XML的简单使其易于在任何应用程序中读写数据,这使XML很快成为数据交换的唯一公共语言,虽然不同的应用软件也支持其它的数据交换格式,但不久之后他们都将支持XML,那就意味着程序可以更容易的与Windows、MacOS,Linux以及其他平台下产生的信息结合,然后可以很容易加载XML数据到程序中并分析他,并以XML格式输出结果。XML的主要作用有:(1)丰富文件(RichDocuments)-自定文件描述并使其更丰富(2)属于文件为主的XML技术应用(3)标记是用来定义一块数据应该如何呈现(4)解释数据(Metadata)-描述其它文件或在线信息(5)属于数据为主的XML技术应用(6)标记是用来说明一块资料的意义(7)组态档案(ConfigurationFiles)-描述软件的组态参数47 第3章需求分析3.1用户需求随着中小型企事业的不断发展,在企业内部实现局域网通信是必不可少的。局域聊工具就是一个非常好的局域网通信软件,它可以在职工不移动位置的情况下进行在线聊天、文件传递、语音聊天。这样,可以大大提高企业的工作效率,便于企业内部职员的交流。根据用户对聊天工具的要求,可分为以下几点:1、操作简单方便、界面简洁美观。2、客户端能够注册新用户3、可以在局域网实现文字和文件的传输4、可以进行聊天记录保存5、可以实现局域网语音聊天6、系统运行稳定、安全可靠3.2系统功能需求这个程序要实现的功能如下:(1)程序启动之后就能看到当前哪些机器在线,哪些可以与之进行对等通信。(2)一旦有某个网内的机器上线了,要有即时通知,并能及时更新用户界面中的用户列表。(3)当双击某个列表项的时候,要弹出聊天对话框,可以在其中编辑要发送的聊天信息,并进行发送。(4)聊天界面要人性化,下面是发送框,上面有已有聊天记录,并借助滚动条看到当次所有的聊天记录。(5)当有远程用户向本机发送文件的时候,要弹出一个消息提示框,提示本机用户,可以选择接收或者拒绝。(6)当用户觉得有必要保存聊天记录时,提供一个简单的聊天记录保存功能。(7)文件传输过程中,应该有当前传输状态提示,并能够对传输耗时等进行计算,作为当前网络状态的一种反馈。3.3性能要求首先要求程序要完全可靠,可以应付各种由于系统问题产生的错误,比如初始网络失败,对方突然下线等。要求提前设想到类似的尽可能多的可能发生的事件,做出相应的应对措施,并向用户提交简单易懂清晰明白的提示信息。47 程序要有良好的容错性,当用户进行非法操作时或者系统本身出现问题时要能以最好的方式退出程序,避免发生程序假死现象。开发文档要有好的易理解性,如果系统又要交由别人接手开发,或者自己由于种种原因需要进行二次开发,那么要保证以后能够清晰的理解整个系统的设计思路以及实现细节。要求程序对所运行之系统的硬件条件要求尽可能低,运行时内存占用尽可能小,响应速度要尽可能快。并且不发生内存泄漏之类影响系统运行的错误事件。并且要求易于维护及扩展。所以应该采用模块化开发,各个模块之间不要有太多的耦合,以免维护困难。3.4测试环境规定在开发过程中由开发人员自己,也就是我和搭档莫景顺随时进行内部部分白盒测试,在开发完成以后,自己进行一个全面的白盒测试,然后请同学帮忙进行黑盒测试。3.5可行性研究(1)成本可行性分析本软件只做开发学习使用,所以暂且不考虑经济成本及盈利问题。(2)技术可行性分析开发所需要的软硬件平台已经搭建好,并进行了合理而完善的需求分析,做好了充分的前期准备工作,其次因为本程序的平台将基于WINDOWS,将要使用网络通信技术,而WINDOWS有完善成熟的网络通信接口,以及与C#开发环境的严密契合能力,加之相类似的更大规模的INTERNET通信工具产品也已有例在先,所以这个程序的开发在技术上是完全可行的。47 第4章系统设计4.1系统目标根据用户对聊天系统的要求,本聊天软件可以实现以下目标:(1)用户管理(2)登录(3)当有用户登录时会在客户端在线用户中添加显示出来(4)当有用户下线时会从客户端在线用户中删除其信息(5)实现客户端间的文本聊天(6)实现客户端间的语音聊天(7)实现客户端间的文件传输(8)实现聊天记录的保存4.2系统功能结构本系统中主要包括两个部分:服务器端和客户端。服务器端的功能包括:验证客户注册、添加新用户、验证客户登录、广播登录用户信息、广播下线用户信息。客户端的功能包括:注册新用户、用户登录、文本聊天、语音聊天、文件传输。客户端功能结构如图4-1所示。局域网聊天工具客户端用户验证文件传输文字聊天语音聊天连接用户注册用户登录发送文件接收文件接收消息消息记录发送消息语音传输语音播放语音采集发起连接接受连接图4-1客户端功能结构47 4.3业务流程图局域网聊天系统的业务流程图如图4-2所示。客户端已注册?登录登录验证主窗体对话框窗体服务器端服务器(记录所有注册用户和显示所有当前在线用户)注册发消息发消息NYY服务器(记录所有注册用户和显示所有当前在线用户)N客户端主窗体发消息对话框窗体发消息TCP/UDP协议图4-2系统业务流程图4.4系统模块划分由图4-1我们可以看出系统主要分为服务器端和客户端两大部分,经过商讨,我们决定把系统分为六个模块:服务器模块、客户端模块、语音聊天模块、文本聊天模块、文件传输模块、用户管理模块。此系统由2个成员共同完成,各成员分工情况如表4-1所示。表4-1小组成员分工情况表学号成员姓名负责模块xxxxxxxxx服务器模块、客户端模块、语音聊天模块*********文本聊天模块、文件传输模块、用户管理模块47 第5章系统模块详细设计在本次开发过程中,我主要负责文本聊天、文件传输和用户管理模块的设计和实现,现详细地介绍这三个模块的实现。5.1文本聊天模块实现5.1.1文本聊天模块概述凡是聊天工具,都少不了文本聊天功能,这是个基础功能。通过文本聊天,局域网内用户能够发送和接收文字信息,并在程序窗体上显示出来,相互之间进行在线交流,并能根据用户需要,对聊天记录进行简单的保存。文本聊天的信息传输协议主要有面向连接的TCP和无连接的UDP。TCP协议是互联网中最重的协议之一,主要特点是:保证数据包的准确到达;保证各数据包到达的顺序和数据包发出的顺序相同。UDP协议提供了快速但不一定可靠的传输服务,UDP协议采用无连接的套接字,不需要在网络设备之间发送连接信息,和TCP相比,UDP缺乏双方的握手信号,因此不保证数据包一定到达目的地,可靠性不如TCP,而且,由于UDP没有任何对双方会话的支持,不能保证各数据包到达的顺序与数据包发出的顺序相同。所以,在文本聊天模块中,采用TCP协议来传输文本信息。5.1.2文本聊天服务器工作原理开启服务器,文本聊天窗体加载成功后,将启动监听线程,服务器主要是负责监听局域网内的用户的连接请求。收到客户发起的连接请求后,若服务器处于空闲状态,弹出消息提示框,对请求进行处理,服务器可以选择接受或者拒绝客户端的连接。若同意连接,则返回给客户端同意信号,开启服务器的接收信息线程,并置消息发送事件为Enable。客户端收到服务器返回的同意信号后,也开启接收信息线程,并置消息发送事件为Enable。通信双方就可以进行文字聊天了。若服务器端拒绝客户端的连接,则关闭tcpconnect,并关闭网络流,继续执行监听。客户端得知服务器端关闭tcpconnect并关闭网络流之后,则释放本次socket和网络流。若已建立连接,当断开连接时,将向对方发送断开信号,然后将释放Socket和网络流,不影响到下一次连接的接入。文本聊天服务器工作流程如图5-1所示。47 开启监听线程,监听端口否是开始同意连接请求?发送拒绝信号建立连接,允许发送消息事件,启动接受信息线程进行文字消息通信是否是否断开连接?发送断开信号,关闭文本聊天是终止否保存记录?保存聊天记录图5-1文本聊天服务器程序流程图5.1.3文本聊天客户端工作原理客户端用来向服务器端发起一个连接,等待服务器的允许接入确认。若服务器端同意连接请求,接收到服务器的同意连接信号后,开启接收信息线程,并置消息发送事件为Enable,通信双方就可以进行文本聊天了。若服务器端拒绝连接,则释放Socket连接并关闭网络流。若已建立连接,当断开连接时,将向对方发送断开信号,然后将释放Socket和网络流,不影响到下一次连接的发起。文本聊天客户端工作流程图如图5-2所示。47 连接服务器服务器同意连接?否是建立连接,允许发送消息事件,启动接受信息线程进行文字消息通信开始是是否断开连接?发送断开信号,关闭文本聊天否终止保存聊天记录保存记录?是否图5-2文本聊天客户端程序流程图5.1.4P2P文本聊天1)功能概述将文本聊天客户端和服务器模块,通过多线程技术,集成在一个聊天窗体里面,窗体加载时进行监听,有连接请求到达,则为服务器端;试图连接远程用户,则为客户端。它是建立在TcpListener以及TcpClient这两个类基础上的。程序实现的原理也比较简单,但是用到了P2P技术"非中心化"的基本原则。简言之,用这个程序可以在网络中发送、接受消息,任何一台计算机既可以作为服务器端,又可以作为客户端。47 2)集成文本聊天工作流程图集成主窗体创建后,加载监听线程。当有接入连接请求,同意或拒绝接入请求,若同意,则按照文本聊天服务器模块工作流程执行。当无接入请求,则判断是否有尝试连接远程服务端事件,若有,则按照文本聊天客户端模块工作流程执行,如图5-3所示。执行监听线程,监听端口是否开始有接入连接请求是是否否连接服务器?是否是否断开连接?连接双方进行消息通信是终止保存聊天记录否保存记录?发送断开信号,关闭文本聊天是本地或远程服务端允许?建立连接,允许发送消息事件,启动接受信息线程图5-3P2P文本聊天程序流程图3)集成主窗体设计47 主窗体是用来与用户交互用的,所以加载时间不能太长,响应速度应足够快,操作简单,设计人性化。所以,设计的时候,采用了一些通俗易懂的图片和按钮。文本输入框设计了Alt+S和Ctrl+Enter热键功能,方便用户快速的发送消息。当用户未输入文字而尝试发送时,弹出消息提示框,提示不能发送空信息,节省系统和网络资源。主窗体界面如图5-4所示。图5-4集成主窗体界面文本聊天窗体主要控件属性,见表5-1。表5-1文本聊天窗体主要控件属性表控件属性值GroupBoxNamegroupbox1Text信息显示RichTextBoxNamerichTxtInfosReadonlyTRUERichTextBoxNamerichTxtSentReadonlyFALSEActiveKeyDownButtonNamesendText发送消息ButtonNameclearboxText清除屏幕ButtonNamesavelogText消息记录4)具体实现1、定义相关变量。2、集成主窗体创建后,创建一个tcpListener的对象,端口为5000,IP为任意。创建监听线程wait,wait线程执行的是waitconn()方法,启动监听线程。调用getMyIP()方法用于获取本地IP地址,并在连接建立时传给对方。47 当wait线程监听到远程用户的接入连接请求,弹出消息提示框,用户可以选择同意或拒绝接入请求。若同意,则创建接收信息线程th,并启动th。若用户拒绝远程接入请求,则关闭网络流nss,关闭tcps,并调用disconnect()重新初始化相关变量。3、接收信息时,尝试读取网络流,采用的是Unicode编码。读取到的消息如果不为断开连接信号,则在聊天记录框里面显示出来;若为断开信号,则执行disconnect()方法关闭当前连接。4、当无接入请求,则判断用户是否有尝试连接远程服务端事件。若有,创建TcpClient的对象tcpc,并尝试连接远程主机。5、连接建立后,用户单击“发送消息”按钮或者在输入文本框内按热键“Alt+S”或“Ctrl+Enter”,触发send_Click事件。send_Click事件主要是将用户的输入通过Socket,采用网络流的形式传输给远程用户,编码采用Unicode,同时在聊天记录框里面显示出来。6、断开连接或关闭窗体,需要将Socket、网络流以及接收信息线程th进行关闭,重置各按钮的可用状态。若是关闭窗体,还需将wait线程关闭。5.2文件传输模块实现5.2.1文件传输模块概述除了文本聊天以外,局域网内另外一个应用是文件的共享。通过文件共享,企业能提高信息交换的速度,从而提高工作效率。文件共享的实现离不开文件传输。由于TCP协议能保证数据包的准确到达以及各数据包到达的顺序和数据包发出的顺序相同,出于可靠性和效率考虑,本模块也采用TCP协议来进行用户之间文件的传输。5.2.2文件传输接收端工作原理开启窗体后,接收端执行监听线程。当接收到客户发起的文件传入请求后,弹出消息提示对话框,提示用户对请求进行处理,同意或者拒绝接收。若同意接收,则弹出文件保存对话框,用户选择文件保存路径并确认后,发送接收信号给文件发送端,启动计时器开始接收文件流,将远程文件保存在本地,并在文件传输过程中对文件传输状态进行反馈,文件接收完毕后,关闭计时器,关闭文件流,并在状态栏上显示出完毕状态。若拒绝接收,则向发送端返回拒绝信号,继续执行监听线程,等待下一次的文件传入请求。文件传输接收端工作流程图如图5-5所示。47 执行监听线程同意接收文件?否是发送同意接收信号,启动计时器,接收文件流,反馈状态终止接收发送端的文件传入请求关闭计时器,关闭文件流将远程文件存储至本地文件开始发送拒绝信号图5-5文件传输接收端程序流程图5.2.3文件传输发送端工作原理开启窗体后,发送端选择用户需要发送的文件,向接收端发起的文件传输请求,若接收端端同意接收,开启计时器,将本地文件通过网络发送给接收端,并在文件传输过程中对文件传输状态进行反馈,文件发送完毕,关闭计时器,关闭文件流,并在状态栏上显示出完毕状态。若接收端拒绝接受文件,则取消本次文件发送,等待用户的下一次文件发送事件。文件传输发送端工作流程图如图5-6所示。47 添加待传送的文件接收端同意接收文件?否是启动计时器,发送文件流,反馈状态终止向接收端发送文件传输请求关闭计时器,关闭文件流将本地文件发送至接收端开始图5-6文件传输发送端程序流程图5.2.4P2P文件传输1)功能概述将文件发送端和接收端模块,通过多线程技术,集成在聊天窗体里面,窗体加载时进行监听,有文件传输请求到达,则为接收端,执行接收端工作流程;若试图发送文件到远程用户,则为发送端,执行发送端工作流程。这样就实现了P2P模式对等传输。2)P2P文件传输工作流程图负责接收文件的监听线程集成在聊天窗体内,当聊天窗体加载时,将开启此线程。当有文件传入请求,用户可以同意或者拒绝接收,若同意接收,则按照文件传输接收端模块工作流程执行。若在聊天窗体生存周期内,无接入连接请求,则判断是否有用户尝试发送文件事件,若有,则按照文件传输发送端模块工作流程执行。P2P文件传输工作流程图如图5-7所示。47 否执行监听线程,监听端口是否启动计时器,接收文件流,显示文件接收状态信息终止保存文件开始是是否接收端允许?启动计时器,发送文件流,显示文件发送状态信息将本地文件发送至接收端发送文件?否是有文件传入请求允许接收?图5-7P2P文件传输程序流程图3)文件传输发送端窗体文件发送主要作用是发送本地文件至远程接收端,所以功能尽可能简单化,方便用户操作,设计人性化。设计的时候,采用了一些简单按钮,布局也和常用的窗体类似。为方便用户快速的选取发送文件,所以文件路径选择框支持拖拽功能。当用户未选取文件前,开始按钮不可用,当用户输入的文件不存在时,弹出消息提示框,提示文件不存在错误。文件接收功能集成在聊天窗体内,文件发送窗体界面如图5-8所示。47 图5-8文件发送窗体界面文件传输窗体主要控件属性见表5-2。表5-2文件传输窗体主要控件属性控件属性值TextBoxNametxtFileButtonNamebtnBrowseText浏览...ButtonNamebtnStartText开始ButtonNamebtnCancelText取消LabelNamelblStatusOpenFileDialogNamedlgOpenFile4)具体实现1、定义相关变量。2、主窗体加载时,创建文件接收监听线程ReceiverWorker,并启动ReceiverWorker线程。ReceiverWorker线程执行的是ReceiverStart()方法。在ReceiverStart()中,创建一个TcpListener的实例tcpListener,其监听局域网内的任意IP地址在8088端口号上是否有传入的连接尝试。3、当ReceiverWorker线程监听到远程用户的文件传入请求,弹出消息提示框,用户可以选择同意或拒绝文件传入请求。若同意,用户选择文件保存路径后,向发起方发送同意接受信号,并开启计时器,读取传入的文件流并将其保存到本地文件,文件接收完毕时关闭计时器。若用户拒绝文件传入,则向远程主机发送一个拒绝信号。最后,执行关闭BinaryReader,关闭Socket等操作。4、当用户打开文件发送窗体,主窗体调用其带ip参数的构造方法,实参为当前与之建立连接的远程主机的ip地址,用于发送文件时建立socket连接使用。5、当用户向txtFile文本框内输入所选取的待发送文件后,用户单击“开始”47 按钮,开启用于向远端发送文件的SenderWorker线程。SenderWorker线程执行SenderStart()方法。SenderStart()方法将创建一个TcpClient的实例tcpClient,尝试连接指定的ip地址的8088端口,将文件的信息发送给远程主机并等待远程主机的确认。若接收方同意接收文件,则启动计时器并打开BinaryWriter,将本地文件发送给远程主机,文件传输完毕时关闭计时器。若远程主机拒绝接收,则关闭此次socket连接,等待下一次文件发送事件的触发。5.3用户管理模块实现5.3.1用户管理模块概述不管是任何系统,都必然会有用户这个角色,因此用户管理是每一个应用中必不可少的部分。由于企业内部的信息越来越私密,企业只希望员工通过内部局域网进行沟通与交流,所以在本局域网聊天工具中,设计一个简单用户管理模块,对接入服务器的用户进行管理,能在一定程度上解决安全性问题。本模块实现采用了xml技术,二次开发时,通过一定的修改,能够很好的移植到数据库上。5.3.2用户数据模型本模块实现一个简单的用户管理,所管理的目标是局域网内客户端用户。模块中只涉及到用户一个模型,涉及到的属性项目有"用户ID"、"用户名(姓名)"、"密码"、"注册时间"等,其E-R图如图5-9所示。图5-9用户信息E-R图47 5.3.3XML文档结构设计XML分为文件序言(Prolog)和文件主体两个大的部分。在此文件中的第一行即是文件序言。它主要是告诉XML解析器如何工作。其中,version是标明此XML文件所用的标准的版本号1.0,encoding指明了此XML文件中所使用的字符类型为utf-8。文件的其余部分都是属于文件主体,userInfos.xml文件的内容信息存放在此。文件主体是由开始的和结束的控制标记组成,这个称XML文件的“根元素”;是作为直属于根元素下的“子元素”;在下又有这些子元素。id是元素中的一个“属性”,“1001”、“1002”等则是“属性值”。userInfos.xml文档结构如图5-10所示。图5-10userInfos.xml文档结构图5.3.4用户信息的保存通过服务器监听,接收到客户端的注册请求后,获取注册用户的信息,判断用户是否为第一个注册用户,若是,则创建userInfos.xml文档,进而创建userInfoList根节点,在userInfoList内创建用户user节点,设置id属性,然后在user节点下创建用户名、密码、创建时间等节点,将相应的信息保存到各节点中。若userInfos.xml已存在,遍历userInfoList,查找该注册用户名在userInfos.xml中是否已存在。若用户名不存在,则在userInfoList内创建新的user节点,然后在user节点下创建用户名、密码、创建时间等,将用户信息添加进userInfos.xml文档中。若用户名已被注册,则通过服务器向客户端返回用户名已经被注册的提示信息。保存数据的流程图如图5-11所示。47 是否是第一个注册用户?是获取最后一个用户的ID号并将ID加1作为该user的ID属性值否创建XML文档创建根节点向user节点中添加用户名、密码、注册时间节点在根节点内添加user节点设置user的ID属性值1001用户是否已存在?否是返回用户已存在在根节userInfosList内添加user节点开始结束图5-11保存数据的程序流程图5.3.5用户数据的遍历在新用户注册或者已注册用户登录时,需要在userInfos.xml文档中查找用户名是否存在,这个就涉及到xml文件的遍历。遍历xml文件的主要流程是,先进入根节点,开始对节点进行遍历。若节点不存在,则返回不存在信息,结束遍历;否则进入节点,对其子节点进行对比。若匹配成功,则返回用户名存在信息;若不匹配,则继续遍历下一个节点。工作流程图如图5-12所示。47 返回不存在进入user节点用户名匹配?Y返回存在进入根节点第i个user结点是否存在否是否是i=1i=i+1开始结束图5-12用户数据的遍历程序流程图47 第6章系统测试6.1系统测试概述测试的目的是为了发现功能是否达到,或者是否有更多的缺陷!这里的缺陷是泛称的,它可以指功能的错误,也可以指性能低下,易用性差等。测试总是先假设程序中存在缺陷,再通过执行程序来发现并最终改正缺陷。理解测试的目的是个很重要的意识问题。如果说测试的目的是为了说明程序中没有缺陷,那么测试人员就会向这个目标靠拢,因而下意识地选用一些不易暴露错误的测试示例。这样的测试是虚假的。测试只能证明缺陷存在,而不能证明缺陷不存在。这个真理告诉我们,对于一个复杂的系统而言,无论采取什么样的测试手段都不能证明缺陷已经不复存在。“彻底地测试”只是一种理想。在实践中,测试要考虑时间、费用等限制,不允许无休止地测试。测试有助于提高软件的质量,但是提高软件的质量不能依赖于测试。因为做为一个本科毕业设计,本系统并不会真正发布到广大的用户群中去,所以只能做α测试,而无法完成β测试。关于测试人员的安排,由我本人和莫景顺一起做白盒测试,由同学帮助做黑盒测试。6.2白盒测试6.2.1测试环境配置白盒测试可在开发系统的本机上直接进行,所以不需要进行特殊的配置。6.2.2正确性测试正确性测试检查软件的功能是否符合规格说明。由于正确性是软件最重要的质量因素,所以其测试也最重要。在集成P2P文本聊天时,最开始只能发起一次连接,通信双方断开连接而不退出程序时,再次连接就提示套接字已被占用异常。通过在disconnect时关闭当前的套接字,使之可以进行多次连接。47 当两个客户端在建立连接时,出现过程序假死异常。分析代码发现,线程调度出错,接收消息线程th还未创建和启动,监听线程wait已经被销毁了!修改代码,给出两种解决办法:1、在th线程的执行方法中,将wait线程销毁;在th线程销毁前,重新开启wait线程。2、wait线程不销毁。设置一bool类型变量isconnected,当已经建立连接后,isconnected=true,若再有其他的连接请求到达,自动将其他连接请求拒绝。连接断开后,重置isconnected=false,将处理移交给用户。我采用了第一种方法,调试之后,问题解决。在程序退出时,常常发生错误,异常退出,造成表面上看程序已结束,但却仍然驻留在内存中的现象。经过反复分析代码,最终确定问题出在程序退出处理流程上,通过修改和调试,问题解决。整个开发过程中,共历经200多次相互断开、连接测试,P2P文本聊天模块终于可以正常运行。借助在P2P文本聊天模块开发过程中积累的经验,较顺利的完成了P2P文件传输模块的开发。通过在总集成后的程序的各个流程中添加消息提示框显示程序内部数据、对象的状态,并插入断点进行单步跟踪发现各模块工作正常,数据也未发生异常现象。6.3黑盒测试6.3.1测试环境配置用两台或两台以上机器通过交换机等构成一个局域网,进行正确的网络配置,每台机器上都安装了WINDOWS操作系统、DirectXSDK以及.NET2.0,并安装了本软件的正确拷贝。6.3.2功能性测试先在其中一台机器A上启动服务端软件,开启监听服务。当A机器和B机器启动了客户端软件后,向服务器进行登录操作,服务器端验证了用户的登录后,向局域网广播客户端的登录状态。A客户端收到服务器发来的信息后,更新自己的用户表,然后此时,不管A,B双方,只要双击用户列表中相应的项目,即可启动发信对话框,若B当前为空闲状态,则双方建立TCP连接,开始进行聊天通信,可以进行信息的输入与发送,发起语聊,传输文件等。同时,对方均可进行正确的接收和回复。所以,功能测试为成功。6.3.3容错性测试容错性测试是检查软件在异常条件下的行为。容错性好的软件能确保系统不发生无法意料的事故。47 经检查,当用户输入无效操作时,系统总是能检测到无效操作,对用户进行相应的信息反馈,并合理的做出内部处理。当系统本身发生错误,比如网络初始化失败,绑定套接字失败,数据读取失败等,都会进行相对应的正确处理。例如当文件传输时,用户输入的文件不存在,而单击“开始”时,会有如图6-1所示的错误提示。图6-1用户操作出错信息提示示意图6.3.4性能与效率测试关于性能的问题,在语聊时,未对语音输入设备采集到的信息进行编码,缓冲数也较少,所以当用户所处环境较狭小时,回音将产生啸叫。改用麦克风和耳机之后,一定程度上阻断回音的影响,通话质量基本上能满足要求。文件传输的时候,通过对文件长度以及已传输信息量的相减运算,显示出当前传输状态,需要占用较多的系统计算资源和时间,而且传输速度相对IPMSG等软件,慢太多。这个问题,我通过对文件流缓冲的字节数进行增大,设置为8KB后,计算量减少,传输速度明显加快,通过计时器计时,最后得出的传输速率与IPMSG相比,大致一样。因为对于带宽相对较大,至少都是10Mbps的局域网来说,信息的发送和接收,在局域网内几乎就是瞬间响应,造成速度瓶颈的不是网络传输因素,而是频繁的对磁盘设备进行读写所致,而传输过程中频繁的计算也是造成系统资源消耗过大的一个原因。通过改进算法后,文件传输效率基本上能满足用户要求。另外,此设计中未限制传输文件的大小,经测试,5GB以上的文件也能够通过文件传输模块进行传输。因此,和其他的类似软件相比较,有更大的应用范围。47 6.3.5易用性测试易用性测试没有一个量化的指标,主观性较强。一般认为,如果一个系统可以让用户不查阅手册即可正确使用其所有功能,那么这个系统就具有良好的易用性。本聊天工具使用过程中提示信息详细,界面友好,操作简单,新用户能够很快学会使用,所以具有比较好的易用性。6.3.6文档测试在本系统的开发过程中,因为是本科毕业设计,所以具有完备的开题报告,文献综述,任务书,以及最终的论文做总结阐述,整个文档所以说文档方面也比较完整。47 第7章主要问题及解决7.1多线程问题因为本系统的客户端程序采用P2P模式,每个用户即可以使服务的发起端,又可以使服务的接收端,所以必须在程序加载时对网络进行监听,监听聊天连接请求和文件传输连接请求,并能够对己方发起的事件进行处理,因而必须在此应用程序中使用多线程技术,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于应用程序监听功能,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理其他的新的请求。在两个用户的通信过程中,任何一方断开连接之后,要像对方发送断开信号,而对方接收到传入的断开信号后,需断开Socket、NetStream等,并重新开启监听线程,销毁接收消息线程。在开发的时候,经常因为线程的创建和销毁不当,而造成程序假死或者异常退出,却没有释放其占用的端口等资源。另外一个问题,程序退出的时候,未将线程处理好,同样造成资源占用,并且程序还驻留在内存中。通过查阅资料,掌握了C#中在winform关闭时需要进行的操作:1、接收线程需要关闭;2、监听需要关闭;3、Socket需要关闭;4、网络流需要关闭;5、如果是传文件的话还需要关闭流文件。在后来经过不断的修改和大量的测试,终于将这个bug解决了。通过对这个问题的解决,掌握了C#中的使用多线程时,线程的创建、相互协调和销毁等技术。7.2套接字异常在文本聊天模块测试时,通过在两个用户间连续地连接、断开,出现Socket异常,因为Socket在使用时,一个IPEndpoint只能使用一次,如果本地在使用某个IPEndpoint,远程主机断开连接后,本地并没有释放此Socket,当远程主机再次尝试连接此IPEndpoint时,将出现Socket异常。解决的办法就是在断开连接的时候,向对方发送“断开”信号,对方接收到断开信号后,将其自身的Socket释放掉。7.3网络流异常消息的传递离不开网络流操作,在编写文本聊天模块时,碰到网络流异常。当互相连接的两个用户之间,任意一方在退出的时候,需要向对方发送“断开”信号,并随后关闭其自身的网络流,而对方在接收到“断开”信号的时候,有个处理过程,但是流却已经关闭了,所以产生流异常,“本地主机尝试读取一个不存在的流”,通过修改,对网络流使用try{}catch{}操作后,问题解决。47 参考文献[1]马俊、何欣,C#网络编程及应用[M],机械工业出版社,2008年:194页-240页[2]唐政、房大伟,C#项目开发全程实录[M],清华大学出版社,2008年:195页-239页[3]张跃廷、王小科,C#程序开发范例宝典[M],人民邮电出版社,2007年:619页-635页[4]AnthonyJones,Windows网络编程[M].清华大学出版社,2004年:51页-69页[5]谢希仁,计算机网络(第4版)[M],电子工业出版社,2006年:248页-276页[6]张海藩,软件工程(第二版)[M],人民邮电出版社,2006年:102页-133页[7]RamadasShanmugam、R.Padmini、S.Nivedita,SpecialEditionUsingTCP/IPSecondEdition[M],NIIT,2002年:47页-75页[8]host01.Com,C#中使用DirectSound录音[EB/OL],http://www.host01.com/article/Net/000200011/20060526092527236.htm,2006-5-26[9]王凯明,C#下用P2P技术实现点对点聊天[EB/OL],http://builder.chinabyte.com/20030313/1656782.shtml,2003-03-13[10](美)库柏,C#设计模式[M],电子工业出版社,2003年:210页-305页[11]冉林仓,尹建民,VisualC#.NET入门与进阶[M],清华大学出版社,2007年:1页-450页47 附录privatevoidForm1_Load(objectsender,EventArgse){//portform=newsetPortForm();//portform.ShowDialog();port=5000;tcp1=newTcpListener(IPAddress.Any,port);this.send.Enabled=false;tcp1.Start();//启动等待连接线程myIP=IPAddress.Parse(getMyIP());writemyIP=System.Text.Encoding.Unicode.GetBytes(myIP.ToString());wait=newThread(newThreadStart(waitconn));wait.IsBackground=true;wait.Name="waitconn";wait.Start();#region启动监听文件传输线程setRecStatusDelegate=newSetReceiverStatusDelegate(SetServerStatus);getSaveFileDelegate=newGetSaveFileDelegate(GetSaveFile);//启动监听线程ReceiverWorker=newThread(newThreadStart(ReceiverStart));ReceiverWorker.IsBackground=true;ReceiverWorker.Start();#endregion#regionif(tcpc!=null)tcpc.Close();tcpc=newTcpClient();#endregion}#region服务器监听连接privatevoidwaitconn(){tcps=tcp1.AcceptTcpClient();nss=tcps.GetStream();//stringstr="用户请求连接,接受?";//弹出连接对话筐//stringcmd="连接请求";//DialogResultdiares=MessageBox.Show(str,cmd,MessageBoxButtons.YesNo);//处理回应2个中间变量stringstrresp;byte[]byteresp;if(!isconnecting)47 {strresp="#";byteresp=System.Text.Encoding.ASCII.GetBytes(strresp.ToCharArray());//将数据写入流nss.Write(byteresp,0,byteresp.Length);//改变控制变量isconnecting=true;isclient=false;this.send.Enabled=true;#region接收对方IP地址byte[]readclientIP=newbyte[32];try{nss.Read(readclientIP,0,readclientIP.Length);}catch(System.IO.IOExceptioneio){SetServerStatus(eio.Message);}fileip=System.Text.Encoding.Unicode.GetString(readclientIP);#endregion//启动接受信息线程th=newThread(newThreadStart(acceptmsg));th.Name="server_amsg";th.Start();}//elseif(diares==DialogResult.No)else{nss.Close();tcps.Close();disconnect();}}#endregion//发送消息事件privatevoidsend_Click(objectsender,EventArgse){if(isclient){sendmsg(nsc);}else{47 sendmsg(nss);}}privatevoidsendmsg(NetworkStreamns){stringmsg=myname+"说:n"+this.richTxtSent.Text;stringmsg2=this.richTxtSent.Text;this.richTxtSent.Text=null;byte[]write=newbyte[1024];write=System.Text.Encoding.Unicode.GetBytes(msg.ToCharArray());ns.Write(write,0,write.Length);if(string.Compare(msg,infDisconn)!=0){DateTimenow=DateTime.Now;stringdate=now.ToShortDateString()+""+now.ToLongTimeString();stringmsgline=date+""+"我说:n";this.richTxtInfos.AppendText(msgline);this.richTxtInfos.AppendText(msg2);msg2=null;this.richTxtInfos.AppendText("nn");this.richTxtSent.Focus();}}//获取本机IP地址privatestringgetMyIP(){stringip=Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString();returnip;}//连接事件代码privatevoidconn_Click(objectsender,EventArgse){//建立TCP连接if(tcpc!=null)tcpc.Close();tcpc=newTcpClient();if(!isconnecting){try{tcpc.Connect(clientip,port);}catch(SocketException){47 SetServerStatus("错误:找不到对方,对方可能已下线!");return;}//保存到读取数据的数组byte[]read=newbyte[2];nsc=tcpc.GetStream();intbytes=0;//获取网络流try{bytes=nsc.Read(read,0,read.Length);}catch(System.IO.IOException){SetServerStatus("对方占线,请稍后再试!");}//根据读到的字节数来判断对方作出何种回应,1表示允许连接,2表示拒绝if(bytes==1){//将自己IP发给对方nsc.Write(writemyIP,0,writemyIP.Length);SetServerStatus("已经建立连接!");isconnecting=true;this.send.Enabled=true;//创建接受信息线程th=newThread(newThreadStart(acceptmsg));th.Name="client_amsg";th.Start();}}}//接受信息处理方法privatevoidacceptmsg(){if(wait!=null)wait.Abort();while(isconnecting){if(isclient){acceptmsg(tcpc,nsc);}else{47 acceptmsg(tcps,nss);}}}//接受信息核心函数privatevoidacceptmsg(TcpClienttcpc,NetworkStreamns){byte[]read=newbyte[1024];ns=tcpc.GetStream();try{ns.Read(read,0,read.Length);}catch(System.IO.IOExceptioneio){SetServerStatus(eio.Message);}stringstrout=System.Text.Encoding.Unicode.GetString(read);if(string.Compare(strout,infDisconn)==0){SetServerStatus("用户已经断开了连接");this.richTxtInfos.AppendText("对方已经断开了连接rn");ns.Close();tcpc.Close();disconnect();}else{DateTimenow=DateTime.Now;stringstrline=now.ToShortDateString()+""+now.ToLongTimeString();stringstrmsgline=strline+"";this.richTxtInfos.AppendText(strmsgline);this.richTxtInfos.AppendText(strout);//this.richTxtInfos.AppendText("rn");}}//断开事件时处理方法privatevoiddisconnect(){//改变按钮状态this.send.Enabled=false;isclient=false;isconnecting=false;47 if(th!=null)th.Abort();if(ReceiverWorker!=null)ReceiverWorker.Abort();if(nss!=null)nss.Close();if(nsc!=null)nsc.Close();if(tcps!=null)tcps.Close();if(tcpc!=null)tcpc.Close();wait=newThread(newThreadStart(waitconn));wait.Start();}privatevoiddisconnect(TcpClienttcpc,NetworkStreamns){byte[]write=newbyte[64];write=System.Text.Encoding.Unicode.GetBytes(infDisconn.ToCharArray());ns.Write(write,0,write.Length);ns.Close();tcpc.Close();disconnect();}//按下Ctrl+Enter或者Alt+S,产生send_Click事件privatevoidrichTextBox2_KeyDown(objectsender,System.Windows.Forms.KeyEventArgse){if(e.Control){if(e.KeyCode==Keys.Enter){send_Click(this,e);}}elseif(e.Alt){if(e.KeyCode==Keys.S){send_Click(this,e);}}}//清空richTxtInfosprivatevoidclearbox_Click(objectsender,EventArgse)47 {stringstr="确定要删除聊天记录吗?";//弹出连接对话筐stringcmd="删除记录";DialogResultdiares=MessageBox.Show(str,cmd,MessageBoxButtons.YesNo);if(diares==DialogResult.Yes)this.richTxtInfos.Text=null;elsereturn;}#region打开文件传输FormprivatevoidtoolBtnFilesTrans_Click(objectsender,EventArgse){if(fileip!=null)filetrans=newFileTrans(fileip);elsefiletrans=newFileTrans(clientip);filetrans.Show();}#endregion#regionReceiver程序逻辑privatevoidReceiverStart(){try//捕获线程中止异常{SetServerStatus("开始侦听端口");TcpListenertcpListener=newTcpListener(IPAddress.Any,ServerPort);try//捕获开始侦听时出现的错误,例如端口已被占用{tcpListener.Start();}catch(Exceptione){SetServerStatus("文件侦听端口出错:"+e.Message);ServerInvokeCancel();return;}try//确保停止侦听{SetServerStatus("等待传入连接中");while(true)//一直循环,直到线程被中止。但每次只处理一个连接{//没有等待传入的连接就一直等待。不使用异步模型以提高程序可读性while(!tcpListener.Pending())Thread.Sleep(1000);47 TcpClienttcpClient=tcpListener.AcceptTcpClient();tcpClient.NoDelay=true;tcpClient.ReceiveTimeout=30000;BinaryReaderreader=newBinaryReader(tcpClient.GetStream());BinaryWriterwriter=newBinaryWriter(tcpClient.GetStream());try//捕获文件传输中途出现的错误,并确保关闭连接{stringfilename=reader.ReadString();longtotal=reader.ReadInt64();SetServerStatus("收到文件传输请求,文件名"+filename+",大小"+total.ToString()+"字节");stringstr="用户发送文件"+filename+",是否接收?";stringcmd="文件传输请求";DialogResultdiafileres=MessageBox.Show(str,cmd,MessageBoxButtons.YesNo);if(diafileres==DialogResult.Yes){stringsaveAs=GetSaveFile(filename);if(saveAs==null){SetServerStatus("操作已取消。等待下一连接中");writer.Write("");ServerInvokeCancel();//finally段会帮忙关闭连接}else{writer.Write("yes");SetServerStatus("接收文件中");stopwatch.Reset();FileStreamfs=File.Create(saveAs);try{byte[]buffer=newbyte[8192];intlen;stopwatch.Start();while(total>0){len=reader.Read(buffer,0,8192);if(len==0)thrownewIOException("发送方中止连接");fs.Write(buffer,0,len);total-=len;47 SetServerStatus("接收文件中-剩余"+(total>>20).ToString()+"MB");}stopwatch.Stop();SetServerStatus("文件已保存至"+saveAs+",用时"+stopwatch.ElapsedTicks/3500000+"S。等待下一连接中");}finally{fs.Close();}}}else{writer.Write("");SetServerStatus("操作已取消。等待下一连接中");ServerInvokeCancel();}}catch(Exceptione){SetServerStatus("文件传输中途出错:"+e.Message);ServerInvokeCancel();}finally{try{reader.Close();}catch{}try{tcpClient.Close();}catch{}}}}finally{tcpListener.Stop();}}catch(ThreadAbortException){SetServerStatus("用户中断");ServerInvokeCancel();47 }finally{ReceiverWorker=null;}}#endregion#regionReceiver线程安全UI操作代码privatedelegatevoidSetReceiverStatusDelegate(stringstatus);privateSetReceiverStatusDelegatesetRecStatusDelegate;//赋值在构造函数里privatevoidSetServerStatus(stringstatus){if(InvokeRequired)Invoke(setRecStatusDelegate,newobject[]{status});elseserverStatus.Text=status;}privatevoidServerInvokeCancel(){if(InvokeRequired)Invoke(newMethodInvoker(ServerInvokeCancel));}privatedelegatestringGetSaveFileDelegate(stringfilename);privateGetSaveFileDelegategetSaveFileDelegate;//privatestringGetSaveFile(stringfilename){if(InvokeRequired)return(string)Invoke(getSaveFileDelegate,newobject[]{filename});else{dlgSaveFile.FileName=filename;if(dlgSaveFile.ShowDialog(this)==DialogResult.OK)returndlgSaveFile.FileName;elsereturnnull;}}#endregion#region保存聊天记录privatevoidtoolsavelog_Click(objectsender,EventArgse){47 DialogResultret;SaveFileDialogsfd=newSaveFileDialog();sfd.Filter="文本文件(*.txt)|*.txt";sfd.AddExtension=true;if((ret=sfd.ShowDialog())==DialogResult.OK){richTxtInfos.SaveFile(sfd.FileName,RichTextBoxStreamType.PlainText);}}#endregion#regionSender程序逻辑privatevoidSenderStart(){try//捕获线程中止异常{SetStatus("连接中");TcpClienttcpClient=newTcpClient();tcpClient.SendTimeout=30000;tcpClient.NoDelay=true;try//捕获连接建立时出现的错误{tcpClient.Connect(FileServerIP,FileServerPort);}catch(Exceptione){SetStatus("连接出错:"+e.Message);InvokeCancel();ReceiverWorker=null;return;}BinaryWriterwriter=newBinaryWriter(tcpClient.GetStream());BinaryReaderreader=newBinaryReader(tcpClient.GetStream());//tcpClient.ReceiveTimeout=10000;stringsendEnable;try//捕获文件传输中途出现的错误,并确保关闭连接{SetStatus("发送文件信息中,等待对方确认接收。");FileInfofi=newFileInfo(filename);writer.Write(fi.Name);writer.Write(fi.Length);try{sendEnable=reader.ReadString();}47 catch(Exceptionese){SetStatus("用户无响应,请重新尝试。"+ese.Message);InvokeCancel();return;}if(sendEnable=="yes"){SetStatus("发送文件内容中");FileStreamfs=fi.OpenRead();stopwatch1.Reset();try//保证关闭文件{longtotal=fi.Length;byte[]buffer=newbyte[8192];intlen;stopwatch1.Start();while((len=fs.Read(buffer,0,8192))!=0){writer.Write(buffer,0,len);SetStatus("发送文件内容中-剩余"+((total-=len)>>20).ToString()+"MB");}stopwatch1.Stop();SetStatus("文件"+filename+"发送完毕,用时"+stopwatch1.ElapsedTicks/3500000+"S。点击“浏览”继续选择待传输的文件");//this.txtFile.Text=null;InvokeCancel();}finally{fs.Close();InvokeCancel();}}else{SetStatus("用户拒绝接受此文件,本次发送已取消。");InvokeCancel();}}catch(Exceptione){SetStatus("文件传输中途出错:"+e.Message);47 InvokeCancel();}finally{try{writer.Close();}catch{}try{tcpClient.Close();}catch{}}}catch(ThreadAbortException){SetStatus("用户中断");InvokeCancel();}finally{ReceiverWorker=null;InvokeCancel();}}#endregion#regionSender入口&UI相关代码privatevoidbtnBrowse_Click(objectsender,EventArgse){if(dlgOpenFile.ShowDialog(this)==DialogResult.OK)txtFile.Text=dlgOpenFile.FileName;}privatevoidbtnStart_Click(objectsender,EventArgse){filename=txtFile.Text;if(!File.Exists(filename)){MessageBox.Show("文件不存在,请重新输入!");txtFile.Focus();txtFile.SelectAll();return;}btnStart.Enabled=false;btnCancel.Enabled=true;SenderWorker=newThread(newThreadStart(SenderStart));SenderWorker.IsBackground=true;SenderWorker.Start();47 }privatevoidbtnCancel_Click(objectsender,EventArgse){try{ReceiverWorker.Abort();InvokeCancel();}catch{}}privatevoidtxtFile_TextChanged(objectsender,EventArgse){btnStart.Enabled=(txtFile.Text.Length!=0);}#endregion#regionSender线程安全UI操作代码privatedelegatevoidSetStatusDelegate(stringstatus);privateSetStatusDelegatesetStatusDelegate;//赋值在构造函数里privatevoidSetStatus(stringstatus){if(InvokeRequired)Invoke(setStatusDelegate,newobject[]{status});elselblStatus.Text=status;}privatevoidInvokeCancel(){if(InvokeRequired)Invoke(newMethodInvoker(InvokeCancel));else{btnCancel.Enabled=false;btnStart.Enabled=true;}}#endregion47'