H a r b i n I n s t i t u t e o f T e c h n o l o g y a t W e i h a i
课程设计报告
课程名称:VC++课程设计
设计题目:聊天室程序设计
院系:计算机科学与技术系班级:
设计者:
学号:
指导教师:
设计时间:2011.8.25-----2011.9.7 哈尔滨工业大学(威海)
《VC++课程设计》验收及成绩评定表(项目组)
《VC++课程设计》验收及成绩评定表(项目组)
哈尔滨工业大学(威海)课程设计任务书
VC++课程设计报告
软硬件运行环境:
Intel? Pentium? 2及以上处理器,32M以上内存,4G以上硬盘
Microsoft? Windows? XP操作系统及以上版本
800*600或以上的屏幕分辨率
开发环境:
Intel? CORE i5? 2.8 GHz,2内存,320G硬盘
Microsoft? Windows? XP Professional
Microsoft? Visual C++ 6.0
问题及难点所在
要解决的问题:
如何与服务器端建立连接,如何接收服务器端发给客户端的消息,相应的处理这些消息。还有就是如何提取出服务器端发给客户端的所有的客户昵称,然后更新客户端的用户列表。最后是一些按钮的响应函数的编写。
涉及算法的思想
首先应该明白,Windows窗口应用程序是基于消息驱动的,这应该是贯穿整个过程的基本思想。
其次跟据MFC程序的体系结构,和大体框架,清楚自己写的代码应该加在哪些地方。实际编程中善于将控件窗口与一个变量相联系,可以很大的简化操作。特别要提到的是程序中更新用户列表的方法,与服务器端的算法刚好相反,充分利用FOR循环并用IF判断语句来提取信息。
系统的流程图
注:这个流程图是在另一个文档中画的,完成后复制过来发现尺寸不是很好,显示得很难看,所以采用了截图的方法,清晰度不是很高,望老师体谅。
系统的设计与分析
第一个函数:
void CLoginDlg::OnOK() //登陆窗口的登陆按钮响应函数。
{
UpdateData();//用来刷新数据的,如果参数为真,则刷新控件的值到对应的变量,若为假,则方向相反。
// m_pSocket->m_strName = this->m_strName;
if(!m_pSocket->Create())//创建。
AfxMessageBox("网络创建错误!!!");
m_pSocket->Close();
return;
}
if(!m_pSocket->Connect(m_strServer,9999))//连接,参数分别为服务器IP和端口号。
{
AfxMessageBox("连接服务器失败!!!");
m_pSocket->Close();
return;
}
Header head;
head.type = LOGIN_IO;
head.len = m_strName.GetLength();
m_pSocket->Send((char *)&head,sizeof(Header));//向服务器端发送预备消息,告知消息类型,准备好接受数据。
m_pSocket->Send(m_strName,m_strName.GetLength());//发送该客户端的用户昵称。
theApp.m_strName = m_strName;//如果不加此行,用户的昵称将无法显示到聊天窗口
CDialog::OnOK();//调用基类的ONOK函数,MFC自动调用的。
}
第二个函数:
void CClientSocket::OnReceive(int nErrorCode) //处理服务器端发送的消息
{
char buff[sizeof(Header)];
memset(buff,0,sizeof(buff));
Receive(buff,sizeof(buff));
Header *header = (Header*)buff;//这里需要一个类型的转换。
int length = header->len;
char type = header->type;
if(type == SEND_MESSAGE)
chatDlg->GetMessage();//定义一个CMyChatDlg *类型的chatDlg变量作用就在此体现。
}
if(type == LOGIN_IO)//收到服务器告知的更新用户的消息。
{
chatDlg->UpdateUser(); //调用CMyChatDlg类中的UpdateUser方法。
}
CSocket::OnReceive(nErrorCode);
}
第三个函数:
void CMyChatDlg::UpdateUser()//更新用户列表。
{
char buff[1000];
memset(buff,0,sizeof(buff));
m_pSocket->Receive(buff, sizeof(buff),0);
CString user_info = buff;
CString array[100];//存放用户名称
int b = 0;
for( int i=0; i { if(i != (user_info.GetLength() - 1))//判断是否已经到达UER_INFO的尾端。 { if ( user_info[i]=='&' )//判断是否已经到一个昵称的结尾。 { b ++;//用户加一 } else { array[b] = array[b] + user_info[i];//将一个用户名称拷贝到一个数组单元中。 } } m_num.Format("%d", b+1);//将INT类型的b转换为CString类型。 UpdateData(FALSE);//变量到控件。 m_UserList.ResetContent();//置空列表框。 for(int j=0; j { m_UserList.AddString(array[j]);//将转换好的用户名称依次显示出来。 } } 第四个函数: void CMyChatDlg::OnSend() //响应发送按钮的函数。 { UpdateData();//控件的值—>变量 if(m_strMessage == "") { AfxMessageBox("不能发送空消息!!!"); CEdit *pEdit = (CEdit*)GetDlgItem(IDC_EDIT_MESSAGE);//得到该控件类型的对象地址。 pEdit->SetFocus();//调用成员函数设置焦点。 return; } Header head; head.type = SEND_MESSAGE; head.len = m_strMessage.GetLength(); CTime time = CTime::GetCurrentTime();//得到系统当前时间。 CString t = time.Format("%H:%M:%S");//设置时间格式。 CString nikeName = theApp.m_strName; CString str =nikeName+ " " + t + "\r\n" +m_strMessage;//把要发送的信息都存储在这str变量中。 m_pSocket->Send((char *)&head,sizeof(Header));//发送“头信息”。 // TODO: Add your control notification handler code here if(m_pSocket->Send((LPCTSTR)str, str.GetLength()))//发送用户编辑的信息,并判断Send函数的返回值。 m_strMessage = ""; UpdateData(FALSE);//变量—>控件的值 CEdit *pEdit = (CEdit*)GetDlgItem(IDC_EDIT_MESSAGE); pEdit->SetFocus(); } else { AfxMessageBox("网络传输错误!"); } } 第五个函数: BOOL CMyChatDlg::GetMessage()//由CClientSocket对象中的OnReceive方法调用。 { char buff[1000];//定义一个数组来接受消息。 memset(buff,0,sizeof(buff));//将数组全部初始化为0。 m_pSocket->Receive(buff, sizeof(buff),0);//接受消息。 CString strTemp = buff; strTemp += _T("\r\n"); m_MessageList.ReplaceSel(strTemp);//用strTemp变量中的内容替换消息显示框控件的当前区域。 return TRUE; } 运行结果与分析(测试) 打开服务器并初始化 一个用户登陆服务器 客户端发送了一条消息 第二个用户登陆 两个用户之间聊天 昵称为唐毅的用户离开聊天室 昵称为烟灰缸的用户离开聊天室 总结(收获与体会) 附:源代码 注:以下代码包括了一点VC自动帮我们生成的部分。 客户端源代码: CMyChatApp类中的InitInstance方法: BOOL CMyChatApp::InitInstance()//初始化实例。 { if (!AfxSocketInit())//初始化WINSOCK环境,一旦选择支持WINSOCKET类则由MFC为我们自动生成。 { AfxMessageBox(IDP_SOCKETS_INIT_FAILED); return FALSE; } AfxEnableControlContainer(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif CClientSocket *clientSocket; clientSocket = new CClientSocket();//NEW一个CClientSocket对象,用来之后与服务器端建立连接。 CLoginDlg* loginDlg; loginDlg = new CLoginDlg(clientSocket);//NEW一个CLoginDlg对象。 if(loginDlg->DoModal() == IDCANCEL)//显示这个登陆窗口,并判断返回值是否来IDCANCEL,如是 { delete clientSocket; delete loginDlg;//则delete这两个对象。 return false;//返回false后程序结束。 } else { delete loginDlg;//如果用户点击的是登陆按钮,则会调用对应的消息处理函数,并delete这个登陆窗口对象。 } CMyChatDlg *dlg; dlg = new CMyChatDlg(clientSocket); m_pMainWnd = dlg;//m_pMainWnd是CWinThread(是CMyApp的基类)的一个成员变量,用于储存一个指向我们创建的一个线程的主窗口的对象。 int nResponse = dlg->DoModal();//显示客户端主窗口,并接受函数的返回值。 return FALSE; } 在CMyChatApp类中的成员变量: CString m_strName; 定义了一个结构体: typedef struct tagHeader { char type; int len; } Header, *pHeader; #define LOGIN_IO 1 #define SEND_MESSAGE 3 客户端套接字类中的OnReceive成员函数: void CClientSocket::OnReceive(int nErrorCode) { // TODO: Add your specialized code here and/or call the base class char buff[sizeof(Header)]; memset(buff,0,sizeof(buff)); Receive(buff,sizeof(buff)); Header *header = (Header*)buff;//这里需要一个类型的转换。 int length = header->len; char type = header->type; if(type == SEND_MESSAGE) { chatDlg->GetMessage();//定义一个CMyChatDlg *类型的chatDlg变量作用 就在此体现。 } if(type == LOGIN_IO)//收到服务器告知的更新用户的消息。 { chatDlg->UpdateUser(); //调用CMyChatDlg类中的UpdateUser方法。 } CSocket::OnReceive(nErrorCode); } 在这个类中还定义了两个公有的成员变量: CMyChatDlg *chatDlg;//用来调用主窗口中的GetMessage和UpdateUser方法。CString m_strName;//存放该客户端的昵称。 登陆窗口类的构造函数: CLoginDlg::CLoginDlg(CClientSocket *p_Socket, CWnd* pParent /*=NULL*/)//创建它的对象的时候需要传递它一个CClientSocket对象 : CDialog(CLoginDlg::IDD, pParent)//的地址,用来建立连接。 { ASSERT(p_Socket);//断言assert是一个宏,一般可以用在判断某件操作是否成功上。 m_pSocket = p_Socket;//提取传递进来的参数。 //{{AFX_DATA_INIT(CLoginDlg) m_strName = _T(""); m_strServer = _T("127.0.0.1");//_T是一个宏,这里的作用相当于初始化了这两个控件的值。 //}}AFX_DATA_INIT } 登陆窗口的确定按钮响应函数: void CLoginDlg::OnOK() { // TODO: Add extra validation here UpdateData();//用来刷新数据的,如果参数为真,则刷新控件的值到对应的变量,若为假,则方向相反。 // m_pSocket->m_strName = this->m_strName; if(!m_pSocket->Create())//创建。 {