VC++课程设计下了就能用.docx
《VC++课程设计下了就能用.docx》由会员分享,可在线阅读,更多相关《VC++课程设计下了就能用.docx(27页珍藏版)》请在冰豆网上搜索。
VC++课程设计下了就能用
VisualC++程序设计
题目名称:
C/S分布式QQ模型聊天室
系名称:
专业班级:
学生姓名:
2011年6月
目录
一、成员分工3
二、需求分析3
三、总体设计3
四、详细设计4
五、系统测试18
六、总结20
七、参考文献20
一、成员分工
?
?
?
服务器端功能
?
?
?
客户端内容功能
?
?
?
客户端登录功能
二、需求分析
当前是数字信息时代,网络时代,任何一种编辑工具都不能忽视在网络上的应用,并且随着Internet的普及和宽带技术的成熟,越来越多的用户进入网络世界中享用浏览新闻、查询信息、发送邮件、在线观看电影等服务,享受网络带来的便利和办公的高效,与此同时一个新型的Web应用程序为网民之间提供了一个实时通信的功能和场所,这就是聊天室。
在网络早期就开始盛行,不论是BBS,IRC都有类似网上聊天室的机制。
聊天室为网友提供了实时性对话的渠道,是网络上极为流行的一项服务。
聊天室适用于网上会议或闲聊的一些场合。
聊天室为网友提供了较好的交友环境,网友之间相互以文字交谈,在形式上有点类似笔友的性质,却大大节省了书信往返的时间,这也是网上交友之所以如此热门的原因。
要求:
掌握CSocket类编程原理
理解客户/服务器通信模块的原理
会用CSocket类编写较复杂的网络通信程序
CSocket对象提供阻塞模式,这对于Carchive的同步操作是至关重要的。
阻塞函数(如Receive()、Send()、ReceiveFrom()、SendTo()和Accept())直到操作完成后才返回控制权。
三、总体设计
服务器端核心:
等待客户连接;
通知所有在线用户新登录用户信息;
中转消息;
当用户离线时通知所有在线用户。
客户端核心:
获得连接参数后与服务器建立连接;
发送/接收信息;
断开时通知服务器。
模块流程图:
四、详细设计
1.服务器端
(1)新建一个基于对话框的MFC项目,命名为ChatRoom,在接下来的“高级功能”选项卡选择“WindowsSockets”复选框。
(2)在对话框上按照表放置相应的控件。
ID
控件类型
标题
风格/变量
IDC_EDIT_INFO
EditBox
ReadOnly||Multiline||VerticalScroll
IDC_STATIC
GroupBox
在线用户
IDC_LIST_USER
ListBox
CListBoxm_UserList
IDOK
ButtonControl
退出
放置控件后的界面如图服务器端。
(3)添加一个新类,命名为CserverSocket,用于接收客户端的连接。
•ServerSocket.h中的定义如下:
#pragmaonce
#include"ClientSocket.h"//CServerSocket命令目标
classCServerSocket:
publicCSocket
{
public:
CPtrListconnectList;//维护客户端连接的socket链表
CServerSocket();
virtual~CServerSocket();
virtualvoidOnAccept(intnErrorCode);//服务器接收客户端的连接
};
•ServerSocket.cpp中的实现,这里只重写了OnAccept()函数:
voidCServerSocket:
:
OnAccept(intnErrorCode)
{
//TODO:
在此添加专用代码和/或调用基类
CClientSocket*clientSocket=newCClientSocket(&connectList);//创建socket用于接收客户端连接
Accept(*clientSocket);//接收新用户连接
clientSocket->m_parent=(CChatRoomDlg*):
:
AfxGetMainWnd();//窗口指针赋值
connectList.AddTail(clientSocket);//加入到链表尾部
CSocket:
:
OnAccept(nErrorCode);
}
(4)再添加一个新类,命名为CClientSocket,用于和客户端进行数据交换。
•ClientSocket.h中的定义:
#pragmaonce
#include"ChatRoomDlg.h"
#include"tagHeader.h"
//CClientSocket命令目标
classCClientSocket:
publicCSocket
{
public:
CStringm_strName;//连接的用户名
CPtrList*clist;//与服务器连接的socket链表指针,所有的socket共享
CChatRoomDlg*m_parent;//维护窗口指针
CClientSocket(CPtrList*list);//构造函数用于将链表共享
CClientSocket();
virtual~CClientSocket();
virtualvoidOnReceive(intnErrorCode);//接收到消息的相关处理
virtualvoidOnClose(intnErrorCode);
};
•ClientSocket.cpp实现
//ClientSocket.cpp:
实现文件
//系统生成的代码略
#include"stdafx.h"
#include"ChatRoom.h"
#include"ClientSocket.h"
#include".\clientsocket.h"
CClientSocket:
:
CClientSocket(CPtrList*list):
m_parent(NULL)
{
clist=list;//链表指针赋值
}
•CClientSocket成员函数
voidCClientSocket:
:
OnReceive(intnErrorCode)
{
//TODO:
在此添加专用代码和/或调用基类
charbuff1[sizeof(Header)];
memset(buff1,0,sizeof(buff1));
Receive(buff1,sizeof(buff1));
Header*header=(Header*)buff1;
intlength=header->len;
chartype=header->type;
if(type==LOGIN_IO)//将接收到的信息是用户的登录或退出信息
{
charbuff[RECV_LEN];
memset(buff,0,sizeof(buff));
Receive(buff,length);
CTimetime=CTime:
:
GetCurrentTime();//得到系统时间
CStringt=time.Format("%Y-%m-%d%H:
%M:
%S");//格式化成字符串
CEdit*p_Edit=(CEdit*):
:
AfxGetMainWnd()->GetDlgItem(IDC_EDIT_INFO);
CStringstrTemp=t+":
"+CString(buff)+"进入聊天室...\r\n";
p_Edit->ReplaceSel(strTemp);//在界面上显示
m_strName=buff;//用户名赋值
m_parent->UpdateUser(this);
}
if(type==SEND_MESSAGE)//将要接收的信息是用户发送的信息
{
charbuf[RECV_LEN];
memset(buf,0,sizeof(buf));
Receive(buf,sizeof(buf));//接收信息
CClientSocket*curr=NULL;
POSITIONpos=clist->GetHeadPosition();
while(pos!
=NULL)//发送给每一个在线用户
{
curr=(CClientSocket*)clist->GetNext(pos);
curr->Send((char*)header,sizeof(Header));
curr->Send(buf,sizeof(buf));
}
}
CSocket:
:
OnReceive(nErrorCode);
}
voidCClientSocket:
:
OnClose(intnErrorCode)
{
//TODO:
在此添加专用代码和/或调用基类
POSITIONpos=clist->Find(this);//找到要退出的Socket对象
if(pos!
=NULL)
{
clist->RemoveAt(pos);//移除
//得到时间等信息再在界面上显示
CTimetime=CTime:
:
GetCurrentTime();
CStringt=time.Format("%Y-%m-%d%H:
%M:
%S");
CEdit*p_Edit=(CEdit*):
:
AfxGetMainWnd()->GetDlgItem(IDC_EDIT_INFO);
CStringstrTemp=t+":
"+this->m_strName+"离开聊天室\r\n";
p_Edit->ReplaceSel(strTemp);
//把退出用户从界面上移掉
m_parent->UpdateUser(this);
this->Close();//Socket对象关闭
deletethis;
}
CSocket:
:
OnClose(nErrorCode);
}
(5)用到的头文件tagHeader.h是这样定义的(后面的客户端也会用到):
typedefstructtagHeader
{
chartype;
intlen;
}Header,*pHeader;//发送的信息头
constintSERVER_PORT=9999;//服务器监听端口
constintLOGIN_IO=1;//登录或退出标记
constintSEND_MESSAGE=3;//发送信息标记
constintRECV_LEN=256;//接收信息长度
constintCLIENT_NUM=50;//聊天室的人数上线
(6)下面是服务器的初始化代码:
BOOLCChatRoomDlg:
:
OnInitDialog()
{
//系统自动生成的代码略
//TODO:
在此添加额外的初始化代码
CServerSocket*m_pSocket;
m_pSocket=newCServerSocket;
if(!
m_pSocket->Create(SERVER_PORT))//创建服务器的监听Socket
{
AfxMessageBox("创建socket失败!
");
returnFALSE;
}
if(!
m_pSocket->Listen())//监听
{
AfxMessageBox("监听发生错误!
");
returnFALSE;
}
((CEdit*)GetDlgItem(IDC_EDIT_INFO))->ReplaceSel("服务器初始化成功...\r\n");//界面显示
returnTRUE;//除非设置了控件的焦点,否则返回TRUE
}
(7)用户登录或者退出时界面上的显示:
//用户的更新,包括用户登录和退出等
voidCChatRoomDlg:
:
UpdateUser(CClientSocket*pSocket)
{
CStringuser_name;//用户名字符串
if(pSocket!
=NULL)
{
m_UserList.ResetContent();//清空过时内容
CClientSocket*pSock=NULL;
POSITIONpos=pSocket->clist->GetHeadPosition();//取得socket链表头
while(pos!
=NULL)//在界面上显示每一个在线用户
{
pSock=(CClientSocket*)pSocket->clist->GetNext(pos);//只要链表不空,依次取下一个位置
m_UserList.AddString(pSock->m_strName);//显示
user_name+=(pSock->m_strName+"&");//用户名赋值
}
Headerhead;//信息头
head.type=LOGIN_IO;//本信息是用户登录或退出信息
head.len=user_name.GetLength();
POSITIONpo=pSocket->clist->GetHeadPosition();
while(po!
=NULL)//向其他的在线用户发送新用户登录信息
{
pSock=(CClientSocket*)pSocket->clist->GetNext(po);
pSock->Send((char*)&head,sizeof(Header));//发送信息头
pSock->Send((LPCTSTR)user_name,user_name.GetLength());//发送新用户的名字
}
}
}
2.客户端
(1)添加一个基于对话框的MFC项目,命名为ChatClient,同样要在“高级功能”选项卡选择“WindowsSockets”复选框。
(2)在对话框上放置如表控件。
ID
控件类型
标题
风格/变量
IDC_STATIC
GroupBox
聊天信息
IDC_EDIT_LIST
EditBox
Mutiline||VerticalScroll
CEditm_MessageList;
IDC_STATIC
GroupBox
在线列表
IDC_LIST_USER
ListBox
CListBoxm_UserList
IDC_EDIT_MESSAGE
EditBox
CStringm_strMessage
ID_SEND
ButtonControl
发送
DefaultButton
IDOK
ButtonControl
退出
放置好控件的界面如图客户端所示。
注意:
去掉“退出”按钮缺省属性,给“发送”设置缺省属性。
(3)考虑到用户登录时需要添加诸如服务器地址,自己的用户名等信息,我们需要再加入一个对话框资源,命名为IDD_DIALOG_LOGIN,并为该对话框添加类CLoginDlg(基类是CDialog),然后再对话框上按照表放置以下控件。
ID
控件类型
标题
风格/变量
IDC_STATIC
GroupBox
登录
IDC_STATIC
StaticText
昵称:
IDC_STATIC
StaticText
服务器IP:
IDC_EDIT_NAME
EditBox
CStringm_strName
IDC_EDIT_SERVER
EditBox
CStringm_strServer
IDOK
ButtonControl
登录
放置控件后的界面如下图所示。
(4)在ChatClient项目中仍然需要一个Socket类CClientSocket来完成数据的发送和接受,具体封装和ChatRoom项目中的CClientSocket封装类似(也可以拷贝并加入到ChatClient项目中修改),下面是具体的封装:
•ClientSocket.h中的定义:
#pragmaonce
#include"tagHeader.h"
classCChatClientDlg;
classCClientSocket:
publicCSocket
{
public:
CChatClientDlg*chatDlg;//窗口指针,便于在界面上显示信息
CClientSocket();
virtual~CClientSocket();
virtualvoidOnReceive(intnErrorCode);//接收信息
};
•ClientSocket.cpp中的实现:
//CClientSocket成员函数
voidCClientSocket:
:
OnReceive(intnErrorCode)
{//根据type字段决定是接收信息或者是用户登录/退出消息
//TODO:
在此添加专用代码和/或调用基类
charbuf[sizeof(Header)];//用于接收消息头的缓冲区
memset(buf,0,sizeof(buf));
Receive(buf,sizeof(buf));//接收消息头
Header*header=(Header*)buf;//强制转换成消息头结构
intlength=header->len;
chartype=header->type;
if(type==SEND_MESSAGE)//发送的是信息
{
chatDlg->GetMessage();//接收信息
}
if(type==LOGIN_IO)//是用户登录/退出消息
{
chatDlg->UpdateUser();//处理用户登录或退出消息
}
CSocket:
:
OnReceive(nErrorCode);
}
(5)因为在两个对话框(即“登录”对话框和主聊天对话框)中都用到同一个Socket对象,所以有必要在CChatClientApp主程序中定义一个全局的客户端Socket对象,同时修改两个对话框的构造函数,具体如下:
•ClientSocket.h中对CClientSocket类的定义:
#pragmaonce
#include"tagHeader.h"
//CClientSocket命令目标
classCChatClientDlg;
classCClientSocket:
publicCSocket
{
public:
CChatClientDlg*chatDlg;//窗口指针,便于在界面上显示信息
CClientSocket();
virtual~CClientSocket();
virtualvoidOnReceive(intnErrorCode);//接收信息
};
•ClientSocket.cpp中的实现:
//ClientSocket.cpp:
实现文件
//系统生成的代码略
#include"stdafx.h"
#include"ChatClient.h"
#include"ClientSocket.h"
#include".\clientsocket.h"
#include"ChatClientDlg.h"
//CClientSocket成员函数
voidCClientSocket:
:
OnReceive(intnErrorCode)
{//根据type字段决定是接收信息或者是用户登录/退出消息
//TODO:
在此添加专用代码和/或调用基类
charbuf[sizeof(Header)];//用于接收消息头的缓冲区
memset(buf,0,sizeof(buf));
Receive(buf,sizeof(buf));//接收消息头
Header*header=(Header*)buf;//强制转换成消息头结构
intlength=header->len;
chartype=header->type;
if(type==SEND_MESSAGE)//发送的是信息
{
chatDlg->GetMessage();//接收信息
}
if(type==LOGIN_IO)//是用户登录/退出消息
{
chatDlg->UpdateUser();//处理用户登录或退出消息
}
CSocket:
:
OnReceive(nErrorCode);
}
•CLoginDlg构造函数的改写:
注意:
为了便于本地测试,程序默认服务器IP地址为本地IP地址。
//CLoginDlg对话框
IMPLEMENT_DYNAMIC(CLoginDlg,CDialog)
CLoginDlg:
:
CLoginDlg(CClientSocket*p_Socket,CWnd*pParent/*=NULL*/)
:
CDialog(CLoginDlg:
:
IDD,pParent)
m_strName(_T(""))
m_strServer(_T(""))
//获取本地IP地址并默认指示为服务器IP
{
charHostName[128];
structhostent*pHost;
CStringstr;
if(gethostname(HostName,128)==0)
{
pHost=gethostbyname(HostName);
for(inti=0;pHost!
=NULL&&pHost->h_addr_list[i]!
=NULL;++i)
{
str=inet_ntoa(*(structin_addr*)pHost->h_addr_list[i]);
}
}
m_pSocket=p_Socket;
m_strServer=str;
str.Empty();
}
•CChatClientDlg构造函数的改写:
//CChatClientDlg对话框
CChatClientDlg:
:
CChatClientDlg(CClientSocket*p_Socket,CWnd*pParent/*=NULL*/)
:
CDialog(CChatClientDlg:
:
IDD,pParent)
m_strMessage(_T("")