基于.NET局域网聊天工具设计毕业论文.doc
基于.NET局域网聊天工具设计 学校: 系: 计算机系 专业: 小学教育计算机双语 姓名: 完成时间: 2012年5月28日 设计(论文)题目:基于.NET局域网聊天工具设计设计(论文)要求:本设计的主要工作是设计一个基于WINDOWS平台的局域网即时聊天工具,具体是采用CS模式实现用户之间的通信,然后阐述本软件的功能、特点及使用方法,并详细阐述开发本软件所用的相关技术,具体分析本软件的各个模块的功能及实现方法,说明本软件的设计思想及方法。局域网聊天工具,是在局域网内部使用的,用户之间用来交流的一个工具,一般都具有文本聊天和文件传输功能。局域网聊天软件因其使用简单,系统资源消耗少等优点,成为各企事业单位等的局域网内广泛应用的软件之一。 中文摘要:随着计算机网络技术的发展,各种各样基于网络的应用也随之诞生,比如基于互联网的信息发布,通信,数据共享等等。局域网的发展也同样迅速。很多政府机构,企业,学校,都是先以一个统一的局域网联结在一起,再分别接入INTERNET。因此基于局域网的即时通信工具,就这样应运而生了。本文提出了一个局域网聊天工具的设计,并在WINDOWS平台上加以了实现。本设计将文本聊天和文件传输等功能综合在一个客户端程序之内,使用C#语言进行网络编程,并进行了人性化的界面设计,使用起来简单方便,并且功能十分合理,又易于扩展以及个性化定制。关键词:局域网;C/S体系结构;文本聊天;文件传输英文摘要:Abstract:Along with the high-speed development of the computer network technology, various of applications which are based on network were born, such as Internet-based information releasing,communications,data sharing and so on. The development of LAN is the same fast. Because of Some government institutions, enterprises and schools constitute a LAN first ,then join into INTERNET, the real-time LAN-based communication tools emerged. this paper proposed a LAN chat tool designing, and then implement it on WINDOWS platform. The design integrated text-chat and file-transfer and other functions in a client procedure. It used C # language for network programming with designed a user-friendly and easy to use interface , and it's functions are reasonable and easy to extend, as well as customization.Key words: LAN; C/S Architecture ;text-chat; file-transfer; 目录1.系统设计 11.1系统功能结构 11.2系统功能需求 11.3性能要求 12.系统模块详细设计 22.1文本聊天模块实现 22.2文件传输模块实现 22.2.1 文件传输接收端工作流程 22.2.2 文件传输发送界面设计 33.系统测试 33.1系统测试概述 33.2白盒测试 33.3黑盒测试 44.主要问题及解决 44.1多线程问题 44.2套接字异常 44.3网络流异常 45.附条(设计图纸) 61.系统设计1.1系统功能结构本系统中主要包括两个部分:服务器端和客户端。1. 服务器端的功能包括:验证客户登录、添加新用户、传输用户信息。2. 客户端的功能包括:用户登录、文本聊天、文件传输。客户端功能结构如图1-1所示。1.2 系统功能需求这个程序要实现的功能如下:1. 程序启动之后就能看到当前哪些机器在线,哪些可以与之进行对等通信。2. 一旦有某个网内的机器上线了,要有即时通知,并能及时更新用户界面中的用户列表。3. 当单击用户列表项的时候,直接在聊天对话框打字聊天,可以在其中编辑要发送的聊天信息,并进行发送。4. 聊天界面要人性化,下面是发送框,上面有已有聊天记录,并借助滚动条看到当次所有的聊天记录。5. 当有远程用户向本机发送文件的时候,要弹出一个消息提示,提示本机用户,可以选择接收或者拒绝。6. 为用户提供一个简单的聊天记录保存功能。7. 文件传输过程中,应该有当前传输状态提示,并作为当前网络状态的一种反馈。1.3 性能要求首先要求程序要完全可靠,可以应付各种由于系统问题产生的错误,比如初始网络失败,对方突然下线等。要求提前设想到类似的尽可能多的可能发生的事件,做出相应的应对措施,并向用户提交简单易懂清晰明白的提示信息。程序要有良好的容错性,当用户进行非法操作时或者系统本身出现问题时要能以最好的方式退出程序,避免发生程序假死现象。要求程序对所运行之系统的硬件条件要求尽可能低,运行时内存占用尽可能小,响应速度要尽可能快。并且不发生内存泄漏之类影响系统运行的错误事件。并且要求易于维护及扩展。所以应该采用模块化开发,各个模块之间不要有太多的耦合,以免维护困难。2.系统模块详细设计2.1文本聊天模块实现开启服务器,文本聊天窗体加载成功后,将启动监听线程,服务器主要是负责监听局域网内的用户的连接请求。收到客户发起的连接请求后,若服务器处于空闲状态,弹出消息提示框,对请求进行处理,服务器可以选择接受或者拒绝客户端的连接。若同意连接,则返回给客户端同意信号,开启服务器的接收信息线程,并置消息发送事件为Enable。客户端收到服务器返回的同意信号后,也开启接收信息线程,并置消息发送事件为Enable。通信双方就可以进行文字聊天了。若服务器端拒绝客户端的连接,则关闭tcpconnect,并关闭网络流,继续执行监听。客户端得知服务器端关闭tcpconnect并关闭网络流之后,则释放本次socket和网络流。若已建立连接,当断开连接时,将向对方发送断开信号,然后将释放Socket和网络流,不影响到下一次连接的接入。文本聊天服务器工作流程如图5-1所示。客户端用来向服务器端发起一个连接,等待服务器的允许接入确认。若服务器端同意连接请求,接收到服务器的同意连接信号后,开启接收信息线程,并置消息发送事件为Enable,通信双方就可以进行文本聊天了。若服务器端拒绝连接,则释放Socket连接并关闭网络流。若已建立连接,当断开连接时,将向对方发送断开信号,然后将释放Socket和网络流,不影响到下一次连接的发起。如图2-12.2文件传输模块实现2.2.1 文件传输接收端工作流程开启窗体后,接收端执行监听线程。当接收到客户发起的文件传入请求后,弹出消息提示,提示用户对请求进行处理,同意或者拒绝接收。若同意接收,则弹出文件保存对话框,用户选择文件保存路径并确认后,发送接收信号给文件发送端,启动计时器开始接收文件流,将远程文件保存在本地,并在文件传输过程中对文件传输状态进行反馈,文件接收完毕后,关闭计时器,关闭文件流,并在状态栏上显示出完毕状态。若拒绝接收,则向发送端返回拒绝信号,继续执行监听线程,等待下一次的文件传入请求。文件传输接收端工作流程图如图2-2所示。文本传输界面请具体参考参考图2-3。2.2.2 文件传输发送界面设计开启窗体后,发送端选择用户需要发送的文件,向接收端发起的文件传输请求,若接收端端同意接收,开启计时器,将本地文件通过网络发送给接收端,并在文件传输过程中对文件传输状态进行反馈,文件发送完毕,关闭计时器,关闭文件流,并在状态栏上显示出完毕状态。若接收端拒绝接受文件,则取消本次文件发送,等待用户的下一次文件发送事件。3.系统测试3.1系统测试概述测试的目的是为了发现功能是否达到,或者是否有更多的缺陷;测试只能证明缺陷存在,而不能证明缺陷不存在;测试有助于提高软件的质量,但是提高软件的质量不能依赖于测试;关于测试人员的安排,由我本人做白盒测试,由同学帮助做黑盒测试。3.2白盒测试正确性测试检查软件的功能是否符合规格说明。由于正确性是软件最重要的质量因素,所以其测试也最重要。在集成P2P文本聊天程序退出时,常常发生错误,异常退出,造成表面上看程序已结束,但却仍然驻留在内存中的现象。经过反复分析代码,最终确定问题出在程序退出处理流程上,通过修改和调试,问题解决。整个开发过程中,共历经多次相互断开、连接测试,P2P文本聊天模块终于可以正常运行。借助在P2P文本聊天模块开发过程中积累的经验,较顺利的完成了P2P文件传输模块的开发。通过在总集成后的程序的各个流程中添加消息提示框显示程序内部数据、对象的状态,并插入断点进行单步跟踪发现各模块工作正常,数据也未发生异常现象。3.3黑盒测试用两台或两台以上机器通过交换机等构成一个局域网,进行正确的网络配置,每台机器上都安装了WINDOWS操作系统、DirectX SDK 以及.NET 2.0,并安装了本软件的正确拷贝。先在其中一台机器A上启动服务端软件,开启监听服务。当A机器和B机器启动了客户端软件后,向服务器进行登录操作,服务器端验证了用户的登录后,向局域网广播客户端的登录状态。此时就在客户端与服务器上进行功能性测试。最后对系统软件进行容错性测试。容错性测试是检查软件在异常条件下的行为。容错性好的软件能确保系统不发生无法意料的事故。4.主要问题及解决4.1多线程问题通过查阅资料,掌握了C#中在winform关闭时需要进行的操作:1、接收线程需要关闭;2、监听需要关闭;3、Socket需要关闭;4、网络流需要关闭;5、如果是传文件的话还需要关闭流文件。在后来经过不断的修改和大量的测试,终于将这个bug解决了。通过对这个问题的解决,掌握了C#中的使用多线程时,线程的创建、相互协调和销毁等技术。4.2套接字异常在文本聊天模块测试时,通过在两个用户间连续地连接、断开,出现Socket异常,因为Socket在使用时,一个IPEndpoint只能使用一次,如果本地在使用某个IPEndpoint,远程主机断开连接后,本地并没有释放此Socket,当远程主机再次尝试连接此IPEndpoint时,将出现Socket异常。解决的办法就是在断开连接的时候,向对方发送“断开”信号,对方接收到断开信号后,将其自身的Socket释放掉。4.3网络流异常消息的传递离不开网络流操作,在编写文本聊天模块时,碰到网络流异常。当互相连接的两个用户之间,任意一方在退出的时候,需要向对方发送“断开”信号,并随后关闭其自身的网络流,而对方在接收到“断开”信号的时候,有个处理过程,但是流却已经关闭了,所以产生流异常,“本地主机尝试读取一个不存在的流”,通过修改,对网络流使用try catch 操作后,问题解决。5.附条(设计图纸)5.1设计图纸 客户端服务器界面图1-1 客户端功能结构开启监听线程,监听端口开始开始开始发送拒绝信号发送拒绝信号是是是否是否断开连接?发送断开信号,关闭文本聊天是终止否连接服务器连接服务器图2-1执行监听线程同意接收文件?否是发送同意接收信号,启动计时器,接收文件流,反馈状态终止接收发送端的文件传入请求关闭计时器,关闭文件流将远程文件存储至本地文件开始发送拒绝信号图2-2文件传输接收端程序流程图图2-3文件传输接收图5.2主窗体代码using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using Aptech.UI;using System.Data.SqlClient;using System.Media;namespace MyQQ public partial class MainForm : Form int fromUserId; int friendFaceId; int messageImageIndex = 0; public MainForm() InitializeComponent(); private void MainForm_Load(object sender, EventArgs e) tsbtnMessageReading.Image = ilMessage.Images0; ShowSelfInfo(); sbFriends.AddGroup("我的好友"); sbFriends.AddGroup("陌生人"); ShowFriendList(); private void MainForm_FormClosed(object sender, FormClosedEventArgs e) Application.Exit(); private void tsbtnPersonalInfo_Click(object sender, EventArgs e) PersonalInfoForm personalInfoForm = new PersonalInfoForm(); personalInfoForm.mainForm = this; personalInfoForm.Show(); private void tsbtnSearchFriend_Click(object sender, EventArgs e) SearchFriendForm searchFriendForm = new SearchFriendForm(); searchFriendForm.Show(); private void sbFriends_ItemDoubleClick(SbItemEventArgs e) if (tmrChatRequest.Enabled = true) tmrChatRequest.Stop(); e.Item.ImageIndex = this.friendFaceId; ChatForm chatForm = new ChatForm(); chatForm.friendId = Convert.ToInt32(e.Item.Tag); chatForm.nickName = e.Item.Text; chatForm.faceId = e.Item.ImageIndex; chatForm.Show(); private void tsbtnUpdateFriendList_Click(object sender, EventArgs e) ShowFriendList(); private void tmrMessage_Tick(object sender, EventArgs e) ShowFriendList(); int messageTypeId = 1; int messageState = 1; string sql = string.Format( "SELECT Top 1 FromUserId, MessageTypeId, MessageState FROM Messages WHERE ToUserId=0 AND MessageState=0", UserHelper.loginId); SqlCommand command; try command = new SqlCommand(sql, DBHelper.connection); DBHelper.connection.Open(); SqlDataReader dataReader = command.ExecuteReader(); if (dataReader.Read() this.fromUserId = (int)dataReader"FromUserId" messageTypeId = (int)dataReader"MessageTypeId" messageState = (int)dataReader"MessageState" dataReader.Close(); catch (Exception ex) Console.WriteLine(ex.Message); finally DBHelper.connection.Close(); if (messageTypeId = 2 && messageState = 0) SoundPlayer player = new SoundPlayer("system.wav"); player.Play(); tmrAddFriend.Start(); else if (messageTypeId = 1 && messageState = 0) sql = "SELECT FaceId FROM Users WHERE Id=" + this.fromUserId; try command = new SqlCommand(sql, DBHelper.connection); DBHelper.connection.Open(); this.friendFaceId = Convert.ToInt32(command.ExecuteScalar(); catch (Exception ex) Console.WriteLine(ex.Message); finally DBHelper.connection.Close(); if (!HasShowUser(fromUserId) UpdateStranger(fromUserId); SoundPlayer player = new SoundPlayer("msg.wav"); player.Play(); tmrChatRequest.Start(); private void tmrAddFriend_Tick(object sender, EventArgs e) messageImageIndex = messageImageIndex = 0 ? 1:0; tsbtnMessageReading.Image = ilMessage.ImagesmessageImageIndex; private void tsbtnMessageReading_Click(object sender, EventArgs e) tmrAddFriend.Stop(); messageImageIndex = 0; tsbtnMessageReading.Image = ilMessage.ImagesmessageImageIndex; RequestForm requestForm = new RequestForm(); requestForm.Show(); private void tmrChatRequest_Tick(object sender, EventArgs e) for (int i = 0; i < 2; i+) for (int j = 0; j < sbFriends.Groupsi.Items.Count; j+) if (Convert.ToInt32(sbFriends.Groupsi.Itemsj.Tag) = this.fromUserId) if (sbFriends.Groupsi.Itemsj.ImageIndex < 100) sbFriends.Groupsi.Itemsj.ImageIndex= 100; else sbFriends.Groupsi.Itemsj.ImageIndex = this.friendFaceId; sbFriends.Invalidate(); private void cmsFriendList_Opening(object sender, CancelEventArgs e) if (sbFriends.SeletedItem = null) tsmiDelete.Visible = false; else tsmiDelete.Visible = true; if (sbFriends.SeletedItem != null && sbFriends.SeletedItem.Parent = sbFriends.Groups1) tsmiAddFriend.Visible = true; else tsmiAddFriend.Visible = false; private void tsmiView_Click(object sender, EventArgs e) if (sbFriends.View = SbView.LargeIcon) sbFriends.View = SbView.SmallIcon; tsmiView.Text = "显示大头像" else if (sbFriends.View = SbView.SmallIcon) sbFriends.View = SbView.LargeIcon; tsmiView.Text = "显示小头像" private void tsmiDelete_Click(object sender, EventArgs e) DialogResult result; int deleteResult = 0; if (sbFriends.SeletedItem != null) result = MessageBox.Show("确实要删除该好友吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result = DialogResult.Yes) if (sbFriends.VisibleGroup = sbFriends.Groups0) string sql = string.Format( "DELETE FROM Friends WHERE HostId=0 AND FriendId=1", UserHelper.loginId, Convert.ToInt32(sbFriends.SeletedItem.Tag); try SqlCommand command = new SqlCommand(sql, DBHelper.connection); DBHelper.connection.Open(); deleteResult = command.ExecuteNonQuery(); catch (Exception ex) Console.WriteLine(ex.Message); finally DBHelper.connection.Close(); if (deleteResult = 1) MessageBox.Show("好友已删除", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); sbFriends.SeletedItem.Parent.Items.Remove(sbFriends.SeletedItem); else MessageBox.Show("好友已删除", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); sbFriends.SeletedItem.Parent.Items.Remove(sbFriends.SeletedItem); private void tsmiAddFriend_Click(object sender, EventArgs e) int result = 0; string sql = string.Format( "INSERT INTO Friends (HostId, FriendId) VALUES(0,1)", UserHelper.loginId, Convert.ToInt32(sbFriends.SeletedItem.Tag); try SqlCommand command = new SqlCommand(sql, DBHelper.connection); DBHelper.connection.Open(); result = command.ExecuteNonQuery();