课 程 设 计 报 告
课程名称 Vc环境下的Windows编程
题 目 聊天通讯软件
指导教师 侯 霞
设计起止日期 2007.12.24-2008.1.2
系 别 计算机学院
专 业 计算机科学与技术
学生姓名 李杜松
班级/学号 计0402/12
成 绩
一,功能说明
1, 支持多个客户段在同时连接,在服务器和多个客户段之间进行数据传输;
2, 接受客户端发送信息显示在一个列表框内;
3, 在用户进入和离开时,发布适当的问候和欢迎信息;
4, 将接受的某一客户段的信息发给所有其他客户端程序,实现聊天室信息同步;
5, 当服务器停止服务,向每个连接客户端发送服务终止通告.
二,课程设计开发环境:
操作系统:Windows XP
开发工具:Visual C++
网络环境:互联网,局域网,本机自联均可
1,套接字的介绍
随着计算机网络的普及和Internet的迅速发展,越来越多的程序具备了网上与其它程序通信的能力.无论是在局域网还是广域网,软件的通信都采取同样的原则.类似如下图:(TCP/IP)
首先,一台计算机中某个程序等带领一个程序的连接请求,这个应用程序正在"监听"种种连接请求,就像你在等待某人来电话时守在电话机旁一样.同时,另一个应用程序试图与第一个程序连接.这种打开连接的做法与你打电话类似.就像在打电话时你必须知道对方的电话号码一样,应用程序要建立连接也必须知道对方网络地址.
但是你的电脑程序从逻辑上不可能只用这一个地址来完成成千上万个程序的通信,所以除了网络地址(ip)端口孕育而生.每一个端口从逻辑上就解决了对每一个程序使用网络接口通信的问题.
套接子,就等于是把端口,ip等网络接口这功能和在一起的一个网络编程接口.通过他从逻辑的角度简化了通讯的接口.这样套接口通过下面的方法工作,事实上我的程序也是类似这么做的.
这样,我们只要了解学习套接口的原理和类方法,就可轻松实现程序网络互联通信.
2,Win sock的介绍
因为程序的核心就是要依靠MFC封装类CSocket来实现.所以下面把CSocket类以及它的周边作一些介绍.
首先Socket有同步阻塞方式和异步非阻塞方式两种使用,事实上同步和异步在我们编程的生涯中可能遇到了很多,而Socket也没什么特别.虽然同步好用,不费劲,但不能满足一些应用场合,其效率也很低.
也许初涉编程的人不能理解"同步(或阻塞)"和"异步(或非阻塞)",其实简单两句话就能讲清楚,同步和异步往往都是针对一个函数来说的,"同步"就是函数直到其要执行的功能全部完成时才返回,而"异步"则是,函数仅仅做一些简单的工作,然后马上返回,而它所要实现的功能留给别的线程或者函数去完成.例如,SendMessage就是"同步"函数,它不但发送消息到消息队列,还需要等待消息被执行完才返回;相反PostMessage就是个异步函数,它只管发送一个消息,而不管这个消息是否被处理,就马上返回.
2.1,Socket API
首先应该知道,有Socket1.1提供的原始API函数,和Socket2.0提供的一组扩展函数,两套函数.这两套函数有重复,但是2.0提供的函数功能更强大,函数数量也更多.这两套函数可以灵活混用,分别包含在头文件Winsock.h,Winsock2.h,分别需要引入库wsock32.lib,Ws2_32.lib.
1,默认用作同步阻塞方式,那就是当你从不调用WSAIoctl()和ioctlsocket()来改变Socket IO模式,也从不调用WSAAsyncSelect()和WSAEventSelect()来选择需要处理的Socket事件.正是由于函数accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函数被用作阻塞方式,所以可能你需要放在专门的线程里,这样以不影响主程序的运行和主窗口的刷新.
2,如果作为异步用,那么程序主要就是要处理事件.它有两种处理事件的办法:
第一种,它常关联一个窗口,也就是异步Socket的事件将作为消息发往该窗口,这是由WinSock扩展规范里的一个函数WSAAsyncSelect()来实现和窗口关联.最终你只需要处理窗口消息,来收发数据.
第二种,用到了扩展规范里另一个关于事件的函数WSAEventSelect(),它是用事件对象的方式来处理Socket事件,也就是,你必须首先用WSACreateEvent()来创建一个事件对象,然后调用WSAEventSelect()来使得Socket的事件和这个事件对象关联.最终你将要在一个线程里用WSAWaitForMultipleEvents()来等待这个事件对象被触发.这个过程也稍显复杂.
2.2,CAsyncSocket
看类名就知道,它是一个异步非阻塞Socket封装类,CAsyncSocket::Create()有一个参数指明了你想要处理哪些Socket事件,你关心的事件被指定以后,这个Socket默认就被用作了异步方式.那么CAsyncSocket内部到底是如何将事件交给你的呢
CAsyncSocket的Create()函数,除了创建了一个SOCKET以外,还创建了个CSocketWnd窗口对象,并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联,以让该窗口对象处理来自Socket的事件(消息),然而CSocketWnd收到Socket事件之后,只是简单地回调CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虚函数.所以CAsyncSocket的派生类,只需要在这些虚函数里添加发送和接收的代码.
然而,最不容易被初学Socket编程的人理解的,也是本文最要提醒的一点是,客户方在使用CAsyncSocket::Connect()时,往往返回一个WSAEWOULDBLOCK的错误(其它的某些函数调用也如此),实际上这不应该算作一个错误,它是Socket提醒我们,由于你使用了非阻塞Socket方式,所以(连接)操作需要时间,不能瞬间建立.既然如此,我们可以等待呀,等它连接成功为止,于是许多程序员就在调用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的错误,直到返回成功为止.这是一种错误的做法,断言,你不能达到预期目的.事实上,我们可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发,CAsyncSocket::OnConnect()是要表明Socket要么连接成功了,要么连接彻底失败了.至此,我们在CAsyncSocket::OnConnect()被调用之后就知道是否Socket连接成功了,还是失败了.
类似的,Send()如果返回WSAEWOULDBLOCK错误,我们在OnSend()处等待,Receive()如果返回WSAEWOULDBLOCK错误,我们在OnReceive()处等待,以此类推.
还有一点,也许是个难点,那就是在客户方调用Connect()连接服务方,那么服务方如何Accept(),以建立连接的问题.简单的做法就是在监听的Socket收到OnAccept()时,用一个新的CAsyncSocket对象去建立连接,
于是,上面的pSocket和客户方建立了连接,以后的通信就是这个pSocket对象去和客户方进行,而监听的Socket仍然继续在监听,一旦又有一个客户方要连接服务方,则上面的OnAccept()又会被调用一次.当然pSocket是和客户方通信的服务方,它不会触发OnAccept()事件,因为它不是监听Socket.
2.3,CSocket
CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类.它是如何又把CAsyncSocket变成同步的,而且还能响应同样的Socket事件呢
其实很简单,CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待.你先必须明白Socket事件是如何到达这些事件函数里的.这些事件处理函数是靠CSocketWnd窗口对象回调的,而窗口对象收到来自Socket的事件,又是靠线程消息队列分发过来的.总之,Socket事件首先是作为一个消息发给CSocketWnd窗口对象,这个消息肯定需要经过线程消息队列的分发,最终CSocketWnd窗口对象收到这些消息就调用相应的回调函数(OnConnect()等).
所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上进入一个消息循环,就是从当前线程的消息队列里取关心的消息,如果取到了WM_PAINT消息,则刷新窗口,如果取到的是Socket发来的消息,则根据Socket是否有操作错误码,调用相应的回调函数(OnConnect()等).
这样就能保证技能接收到信息,又能保险证界面的响应.
3,CSocket使用
我前两天编的是FTP客户端,但是因为除了上机时间外都在家里编程,没有可用的服务器供我连接,并且现在的服务器加了各种各样的东西不让你舒服连上,所以我最后也只好选择了聊天程序.接触了FTP之后再作聊天程序,才发现FTP是要比聊天程序多了使用CSocketFile类和CArchive类(FTP必须使用这两个类,而聊天工具如果不是为了保存信息就用不到).类似下图:
可见FTP需要至少两个端口,一个提供命令交换(也就是21端口),一个提供文件数据交换(上图省略了上传文件的路径,只有下载文件的路径).这样实现起来比聊天程序要困难一些.主要对于没有接触过CSocket类的我就是难上加难.所以我选择了只使用CSocket类的聊天编程.
聊天程序只需要下面部分的交换即:
但是真正细化编码会发现,其实要比想象的要复杂许多.当然我说的是逻辑上.见下图:
三,工程及设计
1,需求分析
1.1任务概述
目标:实现完成服务器--客户传输,客户--客户传输,群传,单传功能.
运行环境:使用MFC开发
1.2功能需求
1,服务器建立与停止——可在不同端口建立;断开时要对客户发送信息
2,客户连接和断开——可对不同服务器(ip)进行连接同时向其他客户发送信息;断开时要对服务器和其他用户发送信息.
3,识别客户昵称——防止重名功能;
4,发送信息——可选择单一发送,或者群发;
5,接受信息——时间和信息显示在屏幕上.
6,保存信息功能——保存聊天信息
7,客户端过滤功能——只接受个人信息
8,服务端强行关闭客户功能——踢人的功能
1.3性能需求
数据精度:
1,消息数据可以随意长
2,命令数据固定,但是交换客户姓名的时候除外.
3,大多数据都是CString类型有少数char*类
时间特性:适应网络特点,应当注意软件的响应时间.传输数据要经可能的小.
软件适应性:可在任意Windows操作系统执行
移植性: 软件本身没有移植性,但是其类可以反复调用.
1.4运行需求
用户界面需要人性化外.硬件接口,软件接口,故障处理均并没有的要求
2,概要设计
2.1总体设计
软件只分两层界面层和逻辑层,因为时间有限,所以没有加入数据库来保存数据.
服务器端:
界面层:(主要都是提供)
逻辑层:(主要是接受)
客户端:
逻辑层:
界面层:
2.2数据结构设计
逻辑结构:
主要依靠CSocket完成网络部分,所以所有逻辑结构都是基于CSokcet的.见上图,首先需要侦听套接口,之后得到消息后,为客户创建新的对应的套接口,以后通信使用这个.而侦听套接口新的用户的接入.
物理结构设计:
没有物理层面上的设计
数据结构与程序的关系:
CSocket套接口结构是整个软件的核心(当然还有MFC其他基本结构).除此之外本软件再没有其他数据结构.
值得提出来的是.我们需要CSocket的派生类.出于某种需要,我们必须产生以CSocket为父类的派生类如:CListeningSocket CClientSocket CChatSocket.这些类的OnReceive(),OnAccept(),是自动调用的,我们只需要Override它们,就可实现我们的信息联通.
2.3运行设计
事实上,本软件实在太小,没有什么可划分的模块勉强的划分一下,如上页的接口模块图.
在运行的时候它们彼此联系紧密,耦合性很大,所以倒不如说是一个整体.但是出于想让软件的介绍更加明确,我们还是把它分成几个模块.
这些模块需要先运行"开始模块",最后执行"关闭模块",其他模块都能在这两个模块调用之间调用.
2.4出错处理设计
出错输出信息:
(客户端):
连接失败信息
ip错误信息
端口错误信息
昵称错误信息
输入为空信息
套接口创建失败信息
套接口自身检查出错信息
(服务器)
服务开启失败信息
套接口创建失败信息
侦听失败信息
输入为空信息
套接口自身检查出错信息
2.5出错处理对策:
基本采用出错就绕过的方法.
比如,现在你输入非法数值,马上AfxMessageBox("错误信息");return;关闭操作;这样做避免了不必要的操作.
3,详细设计
3.1总体设计
3.1.1需求概述
本部分需要把上面的理论复制与实际编码
3.1.2软件结构
基本定义已经不变,此部分与概要设计相同.
值得注意的是MFC提供了WinSock的初始化包括头文件:"Afxsock.h"还有调用AfxSocketInit()(他在主cpp文件的InitInstance()里调用)只有他调用成功,才能正常使用Socket.
3.1.3界面总接口
服务器端:
变量ID 变量类型 变量名称 用处
按键(开始服务)
按键(关闭服务)
按键(发送信息)
按键(复制信息)
按键(清空)
按键(踢人)
组合框(发送对象)
编辑框(输入)
编辑框(输出显示)
编辑框(端口记录)
客户端:
变量ID 变量类型 变量名称 用处
按键(连接服务器)
按键(发送信息)
按键(断开服务器)
按键(保存信息)
按键(清空信息)
复选框(过滤信息)
组合框(发送对象)
文本框(发送信息)
文本框(记录服务器ip)
文本框(记录端口)
文本框(记录昵称)
文本框(用来显示)
3.2程序描述(服务器端)
3.2.1CListeningSocket类
主要功能:
用于侦听,创建保存客户CClientSocket .
主要参数:
m_client就是CClientSocket[](数组).它从1(0是空的,用于群发信息时的标志)开始保存客户CSocket(实际上是下面介绍的CClientSocket).
m_clientnum记录客户数量.类似指针长度.并且在OnAccept时候当作分配CClientSocket::m_clientNO的根据.
m_pDlg就是主窗体句柄.方便互相调用.
m_nMsgCount原本意记录信息量(条数),但后来没有用上.
主要成员函数:
OnAccept(int nErrorCode)
主要功能:
重载父类OnAccept(int nErrorCode)
用于在侦听后,接受客户端Connect()时自行调用的函数.里面有产生新的CClientSocket的构造函数和初始化函数.
输入项: int nErrorCode.因为是重载函数具体这个东西做什么用还不太清楚,看上去像是错误类别号.
输出项: void
算法: 使用简单的判断语句,没有使用任何算法.
程序逻辑:
构造函数:
CListeningSocket(CServerDlg *pDlg)
功能:就是初始化类参数
输入项:CServerDlg *pDlg
析构函数:
~CListeningSocket();保持默认
3.2.2CClientSocket类
主要功能:
对应每一个用户的套接口,实现接受功能.当然也实现发送功能(发送功能没有重载是因为在对话框类内进行了修改).
主要参数:
m_clentName用于记录昵称,在首次传输中得到.
m_clientNO用于记录当前客户在m_client的位置,以确定删除和发送信息.
m_isfirst判断是否为第一次传输如果是则显示True.
m_pDlg用法同样在CListeningSocket
主要成员函数
OnReceive(int nErrorCode)
主要功能:
重载父类OnReceive(int nErrorCode)在接受信息时触发,自动处理转发给其他用户.当然也给自己的ListBox加信息.
输入项:
int nErrorCode
输出项:
void
算法:
使用简单的判断语句,没有使用任何算法.
程序逻辑:
构造函数:
CClientSocket(CServerDlg *pDlg)
功能:就是初始化类参数
输入项:CServerDlg *pDlg
析构函数:
~CClientSocket();保持默认
3.2.3CServerDlg类(对话框主类)
主要功能:
创建对话框,完成界面所有模块的具体功能.
主要参数:
m_BS开始服务按钮变量用于禁用选择.
m_BSt终止服务按钮变量用于禁用选择
m_BtnSend发送按钮变量用于禁用选择
m_in输入框变量
m_nServerPort端口框变量
m_out输出框变量
m_pSocket侦听套接口指针
m_to发送对象变量
主要成员函数:
void AddString(CString str)
主要功能:
把字符串显示在编辑框内,增加了时间和换行.
int Getto(CString name)
主要功能:
把字符串对应的套接口编号找出来返回.
void OnOK()
主要功能:
编辑框按Enter默认调用OnOK().如果不重载,
将会造成关闭对话框的情况.所以修改成传送输入框的信息到
OnBtnSend(),这样就类似QQ的直接按Enter发送信息的模式.
程序逻辑:见右---------------------------------------------〉〉
void Deletem_to(CString str);
主要功能:
删除对应字符串的组合框(记录传送对象)值.
BOOL SameName(CString str);
主要功能:
查找是否有与输入串相同的远程(已登录)客户昵称.
void DeleteOne(int NO);
主要功能:
删除对应号码的登录客户套接口.
void SendTo(CString str,int NO);
主要功能:
把消息送给NO号码的套接口用户.
void SendAll(CString str,int NO);
主要功能:
把消息传送给除了NO号码套接口客户外的所有用户.
程序逻辑:见右-----------------------------------------〉〉
afx_msg void OnBtnstart();
主要功能:
开始服务.
程序逻辑:类似最开始总体设计部分的逻辑,在此就不多说.
afx_msg void OnBtnstop();
主要功能:
停止服务,并通知所有用户.
afx_msg void OnBtnSend();
主要功能:
发送信息.
afx_msg void OnCopyAll();
主要功能:
拷贝显示用编辑框内所有内容.
afx_msg void OnClear();
主要功能:
清空显示用编辑框.
afx_msg void OnKick();
主要功能:
删除当前组合框内的用户,并通知其它用户.
程序逻辑:见右---------------------------------------〉〉
3.3程序描述(客户端)
3.3.1CChatSocket类
主要功能:
发送和接收服务器信息的套接口类,继承CSocket.
主要参数:
m_pDlg跟服务器端样(不赘述)
m_needUpdate其实和服务器端isfirst一个性质,首次传输由此判断
主要成员函数:
OnReceive(int nErrorCode)
主要更能:同服务器,主要能够判别系统来的信息和命令.
3.3.2CChatClientDlg类(对话框主类)
主要功能:
主要负责对话框的构造初始化,连接服务器及实现其它所有功能
主要参数:
m_pSocket CChatSocket*类型变量来保存套接口
m_out 显示用编辑框
m_Btnsend发送按键变量
m_to发送对象
m_ip 服务器地址编辑框
m_port 服务器端口编辑框
m_name 用户昵称编辑框
m_in 输入编辑框
m_lv 过滤设置变量
主要成员函数:
void AddString(CString str);
主要功能:
在显示编辑框中怎加时间,信息,换行.
void OnOK();
主要功能:
重载OnOK(),在编辑框中默认Enter调用函数.如果是发送编辑框则调用发送按钮函数.
void Deletem_to(char* str,int length);
主要功能:
删除对应用户名称.
void CopytoClipboard(CString str);
主要功能:
调用系统剪贴板,粘贴字符串.
void newName(char* str,int length);
主要功能:
从信息中找到新用户的名字.
void Disconnect();
主要功能:
与服务器断开连接操作.为了服务器发来的断开信息单独分开.主要是操作空间的Enable.
afx_msg void OnBtnconnect();
主要功能:
连接操作.
afx_msg void OnBtnSend();
主要功能:
发送功能.
afx_msg void OnDisConnect();
主要功能:
自主断开连接时调用,会现象服务器发送信息,之后断开.
afx_msg void OnCopyAll();
主要功能:
全部拷贝.
afx_msg void OnButton5();
主要功能:
清空显示编辑框.
afx_msg void OnCheck1();
主要功能:
当点击过滤选择框时,发送信息到服务器来更改自己过滤状态.
4,程序实现
4.1运行界面及其效果图
服务器界面
由于时间原因,界面布置的并不是十分漂亮.并且很多按键布置的可能不太合理.
但总的来说,应该能很好的完成任务了.
按键(开始服务)
按键(关闭服务)
按键(发送信息)
按键(复制信息)
按键(清空)
按键(踢人)
组合框(发送对象)
编辑框(输入)
编辑框(输出显示)
编辑框(端口记录)
客户端界面
按键(连接服务器)
按键(发送信息)
按键(断开服务器)
按键(保存信息)
按键(清空信息)
复选框(过滤信息)
组合框(发送对象)
文本框(发送信息)
文本框(记录服务器ip)
文本框(记录端口)
文本框(记录昵称)
文本框(用来显示)
4.2运行说明
需要先运行服务器端的"开始服务",之后是客户端的"连接",软件才能正常工作.
5,测试计划
5.1测试用例1(连接与断开)
目的:
测试连接建立和断开情况
步骤及操作:
服务器端"服务开始"按钮 ——〉 客户端"连接"按钮
服务器端"服务开始"按钮 ——〉 客户端"连接"按钮——〉服务器端"服务中止"按钮
服务器端"无操作 "——〉 客户端"连接"按钮
客户端:"连接"按钮——〉服务器端"服务开始"按钮——〉客户端"连接"按钮
服务器端"服务开始"按钮——〉"停止服务"按钮 ——〉客户端"连接"按钮
服务器端"服务开始"按钮——〉"停止服务"按钮 ——〉"服务开始"按钮 ——〉客户端"连接"按钮
服务器端"服务开始"按钮 ——〉客户端"连接"按钮——〉"断开"按钮
服务器端"服务开始"按钮 ——〉客户端"连接"按钮——〉"断开"按钮——〉"连接"按钮
(多用户)服务器端"服务开始"按钮 ——〉 客户端1"连接"按钮——〉客户端2"连接"按钮——〉客户端3"连接"按钮
9之后,依次"关闭"
9之后,服务器"停止服务"
结果:(下面序号对应上面试验用例)
结果正常,说明正常顺序下的单用户登录没有问题
服务器端:
10:39:40>>服务开始!
10:39:43>>~:lidusong:~进入聊天室!他是第1位客人.
客户端:
10:39:43>>您已经成功登陆!
结果正常,说明服务器关闭不会导致客户端错误
服务器端:
10:39:40>>服务开始!
10:39:43>>~:lidusong:~进入聊天室!他是第1位客人.
10:41:20>>服务终止!
客户端:
10:39:43>>您已经成功登陆!
10:41:20>>服务关闭!结果正常,说明客户端检测不到服务器不会出现崩溃.
客户端:
[无法连接服务对话框,提示重连]
结果正常,说明客户端建立连接撤销后,在进行连接不会出错.
结果正常,客户端提示无法找到服务器.说明服务器确实关闭了.
结果正常,说明服务器关闭后再起不会出现错误.
服务器端:
0:48:30>>服务开始!
10:48:30>>服务终止!
10:48:31>>服务开始!
10:48:33>>~:lidusong:~进入聊天室!他是第1位客人.
客户端:
10:48:33>>您已经成功登陆!
结果正常,说明客户可以正常断开连接.
服务器端:
10:48:33>>~:lidusong:~进入聊天室!他是第1位客人.
10:50:12>>`:lidusong:`退出聊天室
客户端:
10:48:33>>您已经成功登陆!
10:50:12>>您与服务器断开!
结果正常,说明客户端在断开连接后,再登录不会出错.
服务器端:
10:56:03>>~:lidusong:~进入聊天室!他是第1位客人.
10:56:04>>`:lidusong:`退出聊天室
10:56:04>>~:lidusong:~进入聊天室!他是第1位客人.
客户端:
10:56:03>>您已经成功登陆!
10:56:04>>您与服务器断开!
10:56:04>>您已经成功登陆!
结果正常,特意测试了重命名登录,系统会提示给用户,软件正常
服务器端:
10:57:41>>服务开始!
10:57:45>>~:lidusong:~进入聊天室!他是第1位客人.
10:57:47>>~:2:~进入聊天室!他是第2位客人.
10:57:49>>有客户重名名登陆!已经断开
10:57:51>>~:1:~进入聊天室!他是第3位客人.
客户端1:
10:57:45>>您已经成功登陆!
10:57:47>>~:2:~进入聊天室!他是第2位客人.
10:57:51>>~:1:~进入聊天室!他是第3位客人.
客户端2:
10:57:47>>您已经成功登陆!
10:57:51>>~:1:~进入聊天室!他是第3位客人.
客户端3:
10:57:49>>您已经成功登陆!
10:57:49>>您已经与系统断开!
10:57:49>>您的昵称与其他人重名,请更改!
10:57:51>>您已经成功登陆!
结果正常,说明在多用户下,客户的退出没有问题.并且通知其他客户很及时.
服务器端:
11:01:24>>`:lidusong:`退出聊天室
11:01:26>>`:2:`退出聊天室
11:01:27>>`:1:`退出聊天室
客户端1:
11:01:24>>您与服务器断开!
客户端2:
11:01:24>>`:lidusong:`退出聊天室
11:01:26>>您与服务器断开!
客户端3:
11:01:24>>`:lidusong:`退出聊天室
11:01:26>>`:2:`退出聊天室
11:01:27>>您与服务器断开!
结果正常,服务器端,客户端都关闭了连接,并且都不能互相通信.
服务器端:
11:02:49>>服务终止!
(所有)客户端:
11:02:49>>服务关闭!
评价
此项目测试完全通过,因为提示错误和错误处理做得很完善,所以没有出现意外情况.
5.2测试用例2(用户名传输)
目的:
测试连接建立后用户名称的传递和更新.
步骤及操作:
服务器端"开始服务"——〉客户端"连接"
2,服务器端"开始服务"——〉客户端1"连接"——〉客户端2"连接"
3,1之后 客户端"断开"
4,2之后 客户端1"断开"——〉客户端2"断开"
结果:(下面序号对应上面试验用例)
结果正常,说明正常顺序下的单用户登录,服务器端会纪录客户信息,而客户不会受到别的信息.
服务器端:
客户端:
结果正常,说明正常顺序下的多用户登录,服务器端会纪录客户信息,并向心登录客户发送现在已经登录的客户信息.当新用户登录时,服务器还会转发,新用户登录信息给别的用户.
服务器端:
客户端1:
客户端1:
结果正常,说明登录后,正常退出,服务器会正常删除登录客户信息.
服务器端:
客户端:
结果正常,说明所有客户正常退出后,都正常.
服务器端:
客户端1:
客户端2:
评价:
所有功能正常,说明在传送名称上的编程,还算成功.虽然方法土了一点(全部是拿字符串传输).
5.3测试用例3(发送与过滤)
目的:
测试连接建立后用户名间,或者服务器与用户的信息传递和客户的信息过滤.
步骤及操作:
服务器端"开始服务"——〉客户端"连接"——〉"发送"信息——〉服务器端:"发送"信息
2,服务器端"开始服务"——〉客户端1:"连接"——〉客户端2"连接"——〉"发送"信息——〉客户端1"发送"信息——〉服务器端:"发送"信息
3,2之后 客户端4连接"——〉客户端1向"客户端2""发送"信息——〉客户端2向"客户端1""发送"信息——〉服务器向"客户端1""发送"信息
4,2之后 客户端2"群发屏蔽"——〉服务器端:"发送"信息——〉客户端1"发送"群发信息——〉向"客户端2""发送"信息
结果:(下面序号对应上面试验用例)
结果正常,说明单用户(于服务器)聊天正常
服务器端:
02:19:21>>服务开始!
02:19:24>>~:lidusong:~进入聊天室!他是第1位客人.
02:19:53>>lidusong 对 大家 说 你好
02:20:02>>服务器 对 所有人 说 你好
客户端:
02:19:24>>您已经成功登陆!
02:19:53>>我对 所有人 说 你好
02:20:02>>服务器 对 所有人 说 你好
结果正常,说明多用户群聊正常
服务器端:
02:21:13>>服务开始!
02:21:15>>~:lidusong:~进入聊天室!他是第1位客人.
02:21:16>>~:123:~进入聊天室!他是第2位客人.
02:21:37>>123 对 大家 说 大家好
02:21:46>>lidusong 对 大家 说 大家好
02:21:48>>服务器 对 所有人 说 大家好
客户端1:
02:21:15>>您已经成功登陆!
02:21:16>>~:123:~进入聊天室!他是第2位客人.
02:21:37>>123 对 大家 说 大家好
02:21:46>>我对 所有人 说 大家好
02:21:48>>服务器 对 所有人 说 大家好
客户端2:
02:21:16>>您已经成功登陆!
02:21:37>>我对 所有人 说 大家好
02:21:46>>lidusong 对 大家 说 大家好
02:21:48>>服务器 对 所有人 说 大家好
结果正常,说明多用户,单聊得实现,服务器可以看见所有人的信息.客户之间能看见传给自己的单聊.
服务器端:
02:24:36>>服务开始!
02:24:37>>~:lidusong:~进入聊天室!他是第1位客人.
02:24:38>>~:123:~进入聊天室!他是第2位客人.
02:24:41>>~:4:~进入聊天室!他是第3位客人.
02:25:47>>lidusong 对 123 说 hallo!
02:25:56>>123 对 lidusong 说 hallo!
02:26:04>>服务器 对 lidusong 说 hallo
02:26:10>>服务器 对 123 说 hallo
客户端1:
02:24:37>>您已经成功登陆!
02:24:38>>~:123:~进入聊天室!他是第2位客人.
02:24:41>>~:4:~进入聊天室!他是第3位客人.
02:25:47>>我对 123 说 hallo!
02:25:56>>123 对 lidusong 说 hallo!
02:26:04>>服务器 对 lidusong 说 hallo客户端2:
客户端2:
02:24:38>>您已经成功登陆!
02:24:41>>~:4:~进入聊天室!他是第3位客人.
02:25:47>>lidusong 对 123 说 hallo!
02:25:56>>我对 lidusong 说 hallo!
02:26:10>>服务器 对 123 说 hallo
客户端3:
02:24:41>>您已经成功登陆!
客户之间屏蔽工作正常,但是服务器的群发也被屏蔽了.这是一个错误.
服务器端:
02:32:12>>服务开始!
02:32:12>>~:lidusong:~进入聊天室!他是第1位客人.
02:32:13>>~:123:~进入聊天室!他是第2位客人.
02:32:14>>~:4:~进入聊天室!他是第3位客人.
02:32:22>>123设置为过滤群发信息!
02:32:39>>服务器 对 所有人 说 这里是服务器
02:33:34>>lidusong 对 大家 说 这里是客户端1
02:33:40>>lidusong 对 123 说 你好
客户端1:
02:32:12>>您已经成功登陆!
02:32:13>>~:123:~进入聊天室!他是第2位客人.
02:32:14>>~:4:~进入聊天室!他是第3位客人.
02:32:39>>服务器 对 所有人 说 这里是服务器
02:33:34>>我对 所有人 说 这里是客户端1
02:33:40>>我对 123 说 你好
客户端2:
02:32:13>>您已经成功登陆!
02:32:14>>~:4:~进入聊天室!他是第3位客人.
02:33:40>>lidusong 对 123 说 你好
客户端3:
02:32:14>>您已经成功登陆!
02:32:39>>服务器 对 所有人 说 这里是服务器
02:33:34>>lidusong 对 大家 说 这里是客户端1
评价:
发送功能很成功,但是屏蔽功能是后加入的,错误就出现了.这也是软件检错部分唯一的建功点.
6,总结报告
6.1技术方案评价
首先在对CSocket完全不了解的前提下,通过学习和实践,把软件做出来并完成了任务,从这一点上,在技术方面的评奖应该很高的.但是,很多书都应用了串行化处理这一步,使用CArchive类.而作者,认为CSocket部分还不太了解呢,这时候再引入不了解的类会对整个编程的难度有较大的增加.所以决定,想另外的办法,实现传输.结果表明,整个软件的理解达到了一定程度.但在信息传递的有效性和正规方面做得很差.以至于,相对的降低了开发效率(用户姓名传输部分,请看代码部分的OnReceive函数).所以在整体上技术方面并不能给高分.
6.2软件质量评价
从自己的角度,看这个软件质量只能是良好,而不能是优.因为大部分的Bug处理都是CSocket自己能够应对的.很大一部分大漏洞不出现的原因都是因为CSocket自己太强悍了.其实作者对Bug的修补并不能做到尽善尽美.但是还是或多或少的做了一些修改,使得软件用起来不会出现崩溃的情况.
6.3课程设计过程中软件开发时间分配评价
因为课程设计,大多数时间还是用在了软件的编码和逻辑部分用下图可以表示我的分配情况;
在逻辑上,OnReceive中分辨用户名信息的部分花的时间最长.因为这部分就是书上没有介绍的部分,也是我自己独特的部分.
6.4经验与教训
对于没有接触的领域,应该在思想上弄明白,在着手去做.如果上来就忙于制作,而不把制作的内容弄明白很容易造成返工的情况.其实地一周的前4天我实在做FTP,幸好我现在做得软件与FTP是相通着,不然我真的来不及作完了.周5之后我发现我对CSocket有了新的认识.才明白她的调用原理.还有机制.和重载的意义.之后的几天里才高速的完成了编码.其实逻辑上很是简单.但我却编辑了那么长的时间.看出来前几天我对知识的掌握还是很初级的阶段.希望以后能吸取这样的教训.
6.5总结与体会
其实总的体会就是觉得软件这个行当,以后的工作可能就是不断地接触新的事物,我要编程的东西,肯定我自己还不是很了解他.当编程完了以后,才能真正地对她有了初步的认识.所以说在这个行当,就是要多编程,才能有长进.虽然这次编程没有大三大二,编程量那么吓人(我记得大三自己编过了500行,自己有的逻辑,自己编完了都想不起来了其作用了.但是这次到没有这种情况)但是还是觉得编的时候,进度缓慢,力不从心.或者说满脑子想法,但就是实现不了.这个原因就是对CSocket类的理解太过于初步.现在当然也不能说就弄透了,但是起码比1周前要好很多.
最后其实这个报告的前部分(大部分)都是在制作软件之前应该写好的,但是1个人做这个东西就没有按着这个步骤去做.所以前部分或多或少都失去了写的价值.都是编完之后总结的内容.软件测试部分还是测试出很多错误来的.但是都打了很多的补丁,在今天,写报告的时候已经没什么错误了.即便如此还是发现了一个过滤错误.可见我的软件编程的熟练程度还不到家阿.
希望能把这次经验带到下礼拜的新的课程设计上去.
7,程序附录
7.1主要运行界面
客户端 服务器端
7.2主要程序代码
服务器端:
void CListeningSocket::OnAccept(int nErrorCode) //connect触发
{
CSocket::OnAccept(nErrorCode);//固定模式调用父类对应方法
this->m_clientnum++;//新用户增加用户总量
this->m_client[m_clientnum]=new CClientSocket(CListeningSocket::m_pDlg);//创建对应客户的套接口
if(CListeningSocket::Accept(*m_client[m_clientnum]))//接受!使用新创建的套接口
{
m_client[m_clientnum]->m_clientNO=m_clientnum;//计数,把当前数给client
}else
{//如果失败——很少发生
delete m_client[m_clientnum];//撤销对应套接口
m_clientnum--;//用户减1
}
}
void CClientSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);//同理
char buffer[200];//缓存
int re;//个数
if((re=Receive(buffer,199,0))>0)//接受最长199字
{
buffer[re]='\0';//最后一位+结尾符号
CString name=buffer;//用于更新用户名称!
if(this->m_isfirst)//是否为第一次
{
if(m_pDlg->SameName(name))//如果有重名退出连接
{
m_pDlg->SendTo("CMD:SAMENAME",this->m_clientNO);//传送给登录用户
m_pDlg->DeleteOne(m_clientNO);//从连表里删除
m_pDlg->AddString("有客户重名名登陆!已经断开");
return;
}
name="~";//用于传名字的命令
if(m_pDlg->m_to.GetCount()>1)//
{
for(int i=1;im_to.GetCount());i++)//循环传出已经登陆的客户名字
{
name+=":";
CString tem;
m_pDlg->m_to.GetLBText(i,tem);//得到对象框中的名字
name+=tem;
}
}
name+=":~";//结束也必须用:~这是我规定的
m_pDlg->SendTo(name,m_clientNO);//传送给登录用户(初始化消息)
m_clientName=buffer;//从缓存中得到用户名字
this->m_pDlg->m_to.AddString(m_clientName);//防止重叠名字,自己加入之前更新
CString temp1= buffer;
CString temp = itoa(this->m_clientNO,buffer,10);//buffer借来一用
temp="他是第"+temp+"位客人.";//主要为了构造输出语句
temp="~:"+temp1+":~"+"进入聊天室!"+temp;//显示用"~"表示要传输名字
this->m_pDlg->SendAll(temp,this->m_clientNO);//其他传输
this->m_pDlg->AddString(temp);//服务器端显示
this->m_isfirst=FALSE; //第一次变量置否
}else
{ //不是第一次了
if(strcmp(buffer,"CMD:CLOSE")!=0)//是否为cmd:close特殊命令
{//如果不是
if(strcmp(buffer,"CMD:ISLV")==0)//命令比较
{
this->m_islv=TRUE;//设置过滤标志
CString temp=this->m_clientName +"设置为过滤群发信息!";
this->m_pDlg->AddString(temp);//显示
return;
}
if(strcmp(buffer,"CMD:ISNOTLV")==0)//命令比较
{
this->m_islv=FALSE;//设置过滤标志
CString temp=this->m_clientName +"取消过滤群发信息设置!";
this->m_pDlg->AddString(temp);
return;
}
char name[20];
char say[200];
for(int i=0;buffer[i]!=':';i++)//得到名字对象
{
name[i]=buffer[i];//复制
}
name[i]='\0';//结束符
for(int j=0;buffer[i]!='\0';j++)//得到说话的内容
{ i++;
say[j]=buffer[i];
}
say[j]='\0';//结束符
CString saysomething=say;
CString who=name;
int to=this->m_pDlg->Getto(who);
if(to==0)//如果是群发
{
saysomething=this->m_clientName+" 对 大家 说 "+saysomething;
m_pDlg->SendAll(saysomething,this->m_clientNO);//其他传输
}
else//单发
{
saysomething=this->m_clientName+" 对 "+who+" 说 "+saysomething;
m_pDlg->SendTo(saysomething,to);
}
this->m_pDlg->AddString(saysomething);//服务器端显示
}else
{ //对应客户退出聊天室
CString temp="`:"+m_clientName+":`退出聊天室";
m_pDlg->SendAll(temp,this->m_clientNO);//向所有客户报告情况
m_pDlg->AddString(temp);//在自己地方显示
this->m_pDlg->DeleteOne(this->m_clientNO);//删除对应套接口
this->m_pDlg->Deletem_to(this->m_clientName);//删除对应名字
}
}
}
}
void CServerDlg::OnBtnstart() //开始服务按钮
{
UpdateData(TRUE);//将桌面信息读入对应变量
this->m_pSocket=new CListeningSocket(this);//创建侦听套接口
if(this->m_pSocket->Create(this->m_nServerPort))//创建对应端口匹配
{
if(!this->m_pSocket->Listen(10))//侦听开始最多10次成功侦听
AfxMessageBox("套接字侦听失败!");//提示
}
this->AddString("服务开始!");//在自己的listbox里增加信息
this->m_BS.EnableWindow(FALSE);//解锁
this->m_BSt.EnableWindow();
this->m_BtnSend.EnableWindow();
GetDlgItem(IDC_SERVERPORT)->EnableWindow(FALSE);//枷锁
}
void CServerDlg::OnBtnstop() //关闭服务按钮
{
this->m_BS.EnableWindow();
this->m_BSt.EnableWindow(FALSE);
this->m_BtnSend.EnableWindow(FALSE);//枷锁
GetDlgItem(IDC_SERVERPORT)->EnableWindow();//解锁
CString temp="CMD:CLOSE";
this->SendAll(temp,0);//传信息到所有
m_pSocket->Close();//关闭
delete this->m_pSocket;//删除
this->m_pSocket=NULL;
this->AddString("服务终止!");//本地显示
this->m_to.ResetContent();//重置组合框
m_to.AddString("所有人");
m_to.SetCurSel(0);
}
void CServerDlg::OnBtnSend() //传送按钮
{
UpdateData(TRUE);//同理
if(m_in=="")//如果是空
{
AfxMessageBox("输入为空无法发送!");
return;
}
CString name;
m_to.GetLBText(m_to.GetCurSel(),name);
CString temp="服务器 对 "+name+" 说 "+m_in;
if(m_to.GetCurSel()==0)SendAll(temp,0);//传送给所有
else
this->SendTo(temp,Getto(name));//传送给某个人
this->AddString(temp);//本地显示
m_in.Empty();
UpdateData(FALSE);
}
void CServerDlg::SendAll(CString str,int NO)
{
for(int i = 1; im_clientnum ; i++)
{//循环到每一个用户套接口,除了NO
if(i!=NO)
if(!(m_pSocket->m_client[i]->m_islv))
m_pSocket->m_client[i]->Send(str,str.GetLength(),0);
}
}
void CServerDlg::SendTo(CString str, int NO) //传递给NO信息
{
m_pSocket->m_client[NO]->Send(str,str.GetLength(),0);
}
void CServerDlg::DeleteOne(int NO)
{
if(NO!=m_pSocket->m_clientnum)
{
m_pSocket->m_client[NO]->Close();//关闭
this->m_pSocket->m_client[NO]=this->m_pSocket->m_client[this->m_pSocket->m_clientnum];
m_pSocket->m_clientnum--;//减人
}else//如果是最后一个直接删减人数.
{
m_pSocket->m_client[NO]->Close();//关闭
m_pSocket->m_clientnum--;//减人
}
}
BOOL CServerDlg::SameName(CString str)//新用户登录时返回是否有重名
{
if(m_pSocket->m_clientnum==1)//如果只有一个用户(考虑到一开始就增加了一个但没有名字)
return FALSE;//所以返回假以便得到第一个名字
for(int i=1;im_clientnum);i++)//别忘了是从1开始的0就等于是服务器
{
CString temp=m_pSocket->m_client[i]->m_clientName;
if(strcmp(temp,str)==0)//比较
return TRUE;//相同!
}
return FALSE;//没有相同的
}
void CServerDlg::Deletem_to(CString str)//删除发送对象框中某个字符串
{
CString temp;
for(int i=0;im_to.GetCount();i++)//寻找
{
m_to.GetLBText(i,temp);//从框中得到名字
if(strcmp(temp,str)==0)//比较
{
m_to.DeleteString(i);//删除
return;
}
}
}
int CServerDlg::Getto(CString name)//得到要发送的人的m_clientNO
{
CString temp;
for(int i=1;im_pSocket->m_clientnum;i++)//寻找
{
temp=m_pSocket->m_client[i]->m_clientName;//从类中得到名字
if(strcmp(temp,name)==0)//比较奥
return i;//找到!
}
return 0;
}
void CServerDlg::OnCopyAll()
{
m_out.SetSel(0,-1);//全选
m_out.Copy();//选择区域拷贝
AfxMessageBox("已复制到剪贴板,请新建文档复制即可!");
}
void CServerDlg::OnOK()
{
CWnd* pWnd = GetFocus();
ASSERT(pWnd != NULL); //听说这东西不生成代码,只用在debug里调试用,说明pWnd绝不能为空.
UINT nID = pWnd->GetDlgCtrlID();
if(nID==IDC_EDIT1)//如果焦点在输入编辑框里
{
if(this->m_BtnSend.IsWindowEnabled())//并且发送键可用
{
this->OnBtnSend(); //调用发送键触发函数
}
}
}
void CServerDlg::AddString(CString str)
{
CTime tNow;
tNow=tNow.GetCurrentTime();//得到当前时间
CString t=tNow.Format("%I:%M:%S")+">>";//固定格式
str=t+str+"\r\n";//增加换行
m_out.SetSel(m_out.GetWindowTextLength(),m_out.GetWindowTextLength());//线则最后一个字节
m_out.ReplaceSel(str);//增加
m_out.LineScroll(m_out.GetLineCount()+1);//滚动滑动条
}
void CServerDlg::OnClear()
{
m_out.SetSel(0,-1);
m_out.ReplaceSel("");
}
void CServerDlg::OnKick()
{
CString name;
m_to.GetLBText(m_to.GetCurSel(),name); //得到表当前名成
if(strcmp("所有人",name)==0)
{
AfxMessageBox("不能删除这个选项!");
return;
}
m_to.DeleteString(m_to.GetCurSel());//删除表内名称
m_to.SetCurSel(0);//表归位
int to=this->Getto(name);//得到client位置
name="`:"+name+":`被管理员踢出!";//
this->AddString(name);
this->SendAll(name,to);//传输给所有人
this->SendTo("CMD:CLOSE",to);//传输给被踢的人
this->DeleteOne(to);//删除自己的套接口
}
客户端:
void CChatSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);
char buffer[200];
int re;//个数
if((re=Receive(buffer,199,0))>0)//接受信息
{
buffer[re]=0;//结束符
CString t=buffer;
int cas;
if(strcmp(t,"CMD:CLOSE")==0)//是否等于关闭操作
cas=3;
else
{
if(strcmp(t,"CMD:SAMENAME")==0)//是否重名
cas=4;
else
cas=0;//普通情况
}
switch(cas)
{
case 0://普通情况
if(m_needUpdate)//需要更新的情况
{
m_pDlg->newName(buffer,re);//更新名称操作
this->m_needUpdate=FALSE;//改变第一次登录的设置
}else//不需要更新
{
if(buffer[0]=='~')//如果是增加客户信息
{
m_pDlg->newName(buffer,re);
}
if(buffer[0]=='`')//如果是删除客户信息
{
m_pDlg->Deletem_to(buffer,re);//具体删除对象框操作
m_pDlg->m_to.SetCurSel(0);
}
t=buffer;
m_pDlg->AddString(t);//自己显示
}
break;
case 3:
m_pDlg->AddString("服务关闭!");
m_pDlg->Disconnect();//断开连接
break;
case 4:
m_pDlg->AddString("您已经与系统断开!");
m_pDlg->AddString("您的昵称与其他人重名,请更改!");
m_pDlg->Disconnect();//断开连接
break;
}
}
}
void CChatClientDlg::OnBtnconnect()
{
UpdateData(TRUE);//更新变量
if(this->m_name=="")//如果为空
{
AfxMessageBox("请输入昵称!");
return;
}
if(m_name.Find('~')>=0||m_name.Find('`')>=0||m_name.Find(':')>=0)//昵称不能输入~ ` :
{
AfxMessageBox("昵称不允许包含 '~',':','`'字符!");//寻找到返回信息提示错误
return;
}
this->m_pSocket=new CChatSocket(this);//创建套接口
if(!this->m_pSocket->Create())//如果失败
{
delete this->m_pSocket;
this->m_pSocket=NULL;
AfxMessageBox("套接字创建错误");
return;
}
if(!this->m_pSocket->Connect(this->m_ip,this->m_port))//如果连接失败
{
if(AfxMessageBox("无法连接服务器!\n重试 ",MB_YESNO)==IDNO)//如果选择no
{
delete this->m_pSocket;
this->m_pSocket=NULL;
return;
}
}else
{
UpdateData(TRUE);//读数据到变量
m_pSocket->Send(this->m_name,m_name.GetLength(),0); //发送自己的昵称
this->AddString("您已经成功登陆!");//自己显示信息
GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE);//枷锁
GetDlgItem(IDC_EDIT2)->EnableWindow(FALSE);
GetDlgItem(IDC_EDIT3)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON3)->EnableWindow(TRUE);
GetDlgItem(IDC_CHECK1)->EnableWindow(TRUE);
}
}
void CChatClientDlg::OnBtnSend()
{
UpdateData(TRUE);//同理
if(m_in=="")
{
AfxMessageBox("输入为空无法发送!");
return;
}
CString towho;//给谁
CString temp;
this->m_to.GetLBText(m_to.GetCurSel(),towho);//得到发送对象框名字
temp=towho+":"+m_in;
this->m_pSocket->Send(temp,temp.GetLength(),0);//发送
temp="我对 "+towho+" 说 "+m_in;
this->AddString(temp);//自己显示
m_in.Empty();//编辑框清空
UpdateData(FALSE);//变量上传
}
void CChatClientDlg::OnDisConnect() //主动断开时使用
{
CString temp="CMD:CLOSE";
this->m_pSocket->Send(temp,temp.GetLength(),0);
temp="您与服务器断开!";
this->AddString(temp);
this->Disconnect();
}
void CChatClientDlg::Disconnect()//主要是为了服务器的操作,分离出来的
{
GetDlgItem(IDC_EDIT1)->EnableWindow(TRUE);//解锁
GetDlgItem(IDC_EDIT2)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT3)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON3)->EnableWindow(FALSE);
GetDlgItem(IDC_CHECK1)->EnableWindow(FALSE);
this->m_pSocket->Close();
delete this->m_pSocket;
this->m_to.ResetContent();//删除筐内所有东西
m_to.AddString("所有人");
m_to.SetCurSel(0);
}
void CChatClientDlg::newName(char* str,int length)
{
char temp[20];
int tempn=0;
BOOL begin=FALSE;//用于开启搜索
for(int i=0;im_to.AddString(temp); //增加
tempn=0;
temp[tempn]='\0';//清空
continue;
}
temp[tempn]=str[i];
tempn++;
}
}
}
void CChatClientDlg::OnCopyAll() //粘贴到剪贴板
{
m_out.SetSel(0,-1);
m_out.Copy();
AfxMessageBox("已复制到剪贴板,请新建文档复制即可!");
}
void CChatClientDlg::Deletem_to(char* str,int length)//删除对应对象框文字
{
char temp[20];
int tempn=0;
BOOL begin=FALSE;//用于开启搜索
for(int i=0;i<=length;i++)
{
if(str[i]=='`')//同理newName
{
begin=(!begin);//转换开关第一次由关到开
str[i]=' ';
}
else
if(begin)
{
if(str[i]==':'||str[i]=='\0')
{
if(str[i]==':')
str[i]=' ';
temp[tempn]='\0';//完毕
if(tempn!=0)//如果内容为空则不记录,主要为了滤":"
break;
tempn=0;
temp[tempn]='\0';//清空
continue;
}
temp[tempn]=str[i];
tempn++;
}
}
CString todelete=temp;//要删除的
CString m_totext;//现有的
for(i=0;im_to.GetCount();i++)//真正删除部分寻找同名
{
m_to.GetLBText(i,m_totext);
if(strcmp(m_totext,todelete)==0)
{
m_to.DeleteString(i);
return;
}
}
}
void CChatClientDlg::OnCheck1() //过滤选择
{
CString temp="CMD:ISLV";
CString temp1="CMD:ISNOTLV";
UpdateData(TRUE);//更新
if(this->m_lv)//如果是设置
this->m_pSocket->Send(temp,temp.GetLength(),0);
else //如果是取消设置
m_pSocket->Send(temp1,temp1.GetLength(),0);
}
四, 参考文献
John E.Swanke(美)编著,前导工作室 译,《Visual C++ MFC开发网络典型应用实例导航》
机械工业出版社,2000年01月
常晋义 沈健 徐文彬 编著,《Visual C++ 程序设计简明教程》中国电力出版社,2002年9月
http://www.vckbase.com(搜索用例子用)
http://www.baidu.com(搜索例子用)
http://www.google.com(搜索用例子用)
[6] http://msdn2.microsoft.com/zh-cn/library (查阅库函数)
2007年VC++课程设计:聊天程序 李杜松
/38
CString可省略
CString可省略
网络
OnReceive()
Send()
Close()
Close()
连接套接字
Close()
Send()
OnReceive()
Socket()
侦听套接字
OnAccept
Listen()
Bind()
Socket()
Conect()
Bind()
Socket()
客户端
客户端
注:( )为触发,并不是直接调用
……
客户端
服务器端
网络
CSocket
CSocket
聊天信息
聊天信息
CSocket
CSocket
命令
命令
客户端
网络
服务器端
文件
文件
CSocketFile
CArchive
CSocket
CSocket
CArchive
CSocketFile
客户端
客户端
客户端
客户端
客户端套接口
客户端套接口
客户端套接口
客户端套接口
服务器套接口群
服务器端
服务器(监控)
客户机
发送连接请求
发送连接请求
发送连接请求
端口信息
输出窗口信息
输入窗口信息
输出对象窗口信息
服务开始模块
发信息模块
侦听套接口
对应客户套接口
转发客户信息模块
客户对应服务器套接口
昵称
服务器地址
端口
客户连接离开模块
输出对象窗口信息
输入窗口信息
输出窗口信息
聊天开始模块
接受信息模块
发送信息模块
网络部分由CSocket自己解决
聊天关闭模块
服务关闭模块
模块之间的通信已经省略
增加用户
父类OnAccept(nErrorCode)
创建对应用户的CClientSocket实例,放入client数组里
新建的客户套接口Accept()
设置新建客户套接口的代号
成功
失败(很少发生)
结束
删除对应套接口
较少用户数量
开始
创建失败忽略!assert:绝对不会失败
父类OnReceive (nErrorCode)
Receive读取信息
得到发送对象
否
如果是第一次
是
传输已登录用户信息
返回昵称重名错误
增加用户昵称
结束
群发信息
客户昵称重名
是
否
开始
是否关闭命令
否
是
是否过滤命令
否
是
是否为群发
否
是
单发信息
设置过滤项
群发此用户关闭信息
删除操作
结束
服务器端显示
群发新登录用户信息
服务器端显示
OnOK()
得到当前焦点
assert焦点非空
否
是
uID是发送编辑框框
得到焦点uID
否
是
发送按键可用
发送到发送按键事件
结束
否
是
标记小于最大用户数
SendAll();
初始华标记
发送信息
标记自加1
否
是
标记是NO
否
是
标记用户是过滤用户
结束
OnKick();
得到当前组合框内用户名称
否
是
名称是"所有人"
结束
删除操作
显示信息
对用户发送关闭信息
群发用户被踢信息
得到用户client中位置号
给出不能删除信息
删除组合框内对应信息
组合框归位
串标志位自加1
串是否结束
是
否
否
是
是否为开关符
否
是
开关已开
开关反置
是否为名结束符
是
Deletem_to开始
结束
保存名字
非空
是
删除对应
寻找对应名称在对象框
否
否
保存一个字符
OnBtnSend开始
接受界面数据
结束
串为空
提示出错
是
否
得到发送对象
组合对象和内容
发送内容
显示框显示
发送框清空
第1周
第2周
周1
周2
周3
周6
周5
周4
周6
周5
周4
周3
周2
周1
选择题目,阅览有关书籍,掌握必要知识
根据书上的例子,一步一步跟着完成.
脱离介绍书籍,自己发挥和想象功能.(因为书上的串行化自己觉得太难了)
修改Bug并增加一些小功能并开始写报告.
来学校向老师报告制作成果.在家里写课程设计报告.