摘要
沟通是人类社会里不可或缺的一种行为,在信息发达的今天,沟通的方式多种多样,而网络聊天作为一种新的沟通方式,例如MSN、QQ,在现实生活里发挥着重要的作用,网络聊天,已经成为人们生活中一种重要的行为。
学习聊天程序的编程,加深对VC网络技术的认识,对于我们来说也是有着十分重要的意义。理解并掌握对话框编程,学习多种技术的融合,从而提高我们的编程水平。
聊天程序一般由两大部分组成:服务器端聊天程序和客户端聊天程序。服务器端聊天程序负责接收来自客户端的聊天信息,并根据客户端的要求把这些信息发到一个或多个客户中。客户聊天程序则负责建立和维护与服务器端的连接,向服务器发送本客户的聊天内容,同时从服务器接受对方的响应。
功能要求如下:
客户端部分:
●手动输入服务器端IP地址和端口号进行连接
●发送消息给服务器端并显示服务器端回传的消息
服务器端部分:
●手动建立服务器端与客户端的连接请求
●接收所有用户发送的消息
●向所有在线用户群发消息
1目录
1 方案比较与选择 (1)
1.1系统分析与设计 (1)
1.2程序方案 (2)
1.2.1方案一:基于TCP的socket编程 (2)
1.2.2方案二:基于UDP(面向无连接)的socket程序 (3)
1.3方案比较结果 (5)
2 程序的编程与实现 (6)
2.1项目创建 (6)
2.2界面设计 (6)
2.3代码编写 (6)
2.3.1CClientSocket (7)
2.3.2CServerSocket (17)
2.3.3CMessg (19)
2.3.4CChatDlg (21)
3 程序的运行与结果 (41)
3.1使用说明 (41)
3.2程序功能演示 (41)
4 讨论及进一步研究建议 (44)
5 课程设计心得 (45)
6 Abstract (46)
7 参考文献 (47)
1方案比较与选择
1.1系统分析与设计
系统构架方式如下图所示:
设计出一个完整的网络聊天程序,使之实现以上基本要求。
1.服务端需要完成的三件事
1)在特定端口等待连接请求,并需要维护一个客户连接表,已记录所有成功连
接。
2)及时接受消息,然后转发到客户连接。
3)监控连接状态,客户离开或故障时从列表中删除相应表项,并及时更新连接
表。
2.客户端需要完成的三件事
1)建立与维护服务器的连接,并随时监测连接状态。
2)把用户输入的信息及时发送到服务端,同时准备好接受,并显示信息。
3)在用户退出时关闭连接并保存聊天记录。
1.2程序方案
1.2.1方案一:基于TCP的socket编程
TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。
服务器端程序流程如下:
1)创建套接字(socket);
2)将套接字绑定到一个本地地址和端口上(bind);
3)将套接字设为监听模式,准备接受客户请求(listen);
4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连
接的套接字(accept);
5)用返回的套接字和客户端进行通信(send/recv);
6)返回,等待另一客户请求;
7)关闭套接字;
客户端程序流程如下:
1)创建套接字(socket);
2)向服务器发出连接请求(connect);
3)和服务器端进行通信(send/recv);
4)关闭套接字。
在服务器端,当调用accept函数时,程序就会等待,等待客户调用connect函数发出连接请求,然后服务器端接受该请求,于是双方就建立了连接。之后,服务器端和客户端就可以利用send和recv函数进行通信了。因为服务器需要接受客户端的请求,所以必须告诉本地主机它打算在哪个IP地址和哪个端口上等待客户要求,因此必须调用bind 函数来实现这一功能。而对客户端来说,当它发起连接请求,服务器端接受请求后,在服务端就保存了改客户端的IP地址和端口的信息。这样,对服务器端来说,一旦建立连接之后,实际上它已经保存了客户端的IP地址和端口号的信息,就可以利用所返回的套接字调用send/recv函数与客户端进行通信。
程序流程图如下:
1.2.2方案二:基于UDP(面向无连接)的socket程序
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境
服务器端也叫接收端,对于基于UDP(面向无连接)的套接字编程来说,它的服务器端和客户端这种概念不是很强化,我们也可以把服务器端,即先启动的一端称为接收端,发送数据的一端称为发送端,也称为客户端。
服务端程序编写流程如下:
1)创建套接字(socket);
2)将套接字绑定到一个本地地址和端口上(bind);
3)等待接受数据(recvfrom);
4)关闭套接字。
虽然面向无连接的socket编程无须建立连接,但是为了完成这次通信,对于接受端来说,它必须先启动以接受客户端发送的数据,因此接收端必须告诉主机它是在哪个地址和端口上等待数据的到来,接收端(服务器端)必须调用bind函数将套接字绑定到一个本地地址和端口上。
客户端程序编写流程如下:
1)创建套接字(socket);
2)向服务器发送数据(sengto);
3)关闭套接字。
在UDP的套接字编程时,利用的是sendto和recvfrom这两个函数实现数据的发送和接收,而基于TCP的套接字编程时,发送数据是调用send函数,接受数据调用recv 函数。
程序流程图如下:
1.3方案比较结果
TCP与UDP最基本的区别在于基于连接与无连接,相比之下,第一种方案对系统的要求以及数据量都比较大,但是保证数据的正确性与数据顺序,在传输大量数据的时候具有更高的可靠性。至于第二种方案的优点在于传输的速度快,程序结构精简。
总的来说,我认为TCP协议更能满足目前各行业对远程数据传输的要求,它提供更稳定更便利的传输通道,满足了对安全性的要求以及远程数据传输的要求。
所以我们小组选择方案一。
2程序的编程与实现
2.1项目创建
使用VisualC++6.0创建一个基于对话框的MFC AppWizard(exe)项目。
1)从菜单栏中选择“文件”,然后“新建”,弹出“新建”对话框。
2)在对话框中单击“工程”选项卡,选中MFC AppWizard(exe),并设置工程名称为
“Chat”,单击确定。
3)在MFC AppWizard向导界面中,选择“Dialog based”,建立基于对话框的应用程
序。
4)在MFC AppWizard向导界面中,选择“Windows Sockets”,其他默认。
5)单击“完成”,完成工程的创建。
2.2界面设计
打开IDD_CHAT_DIALOG对话框,设计对话框的界面,添加控件。
完成后对话框如下图:
添加控件后,打开ClassWizard,为IDD_CHAT_DIALOG指向的CChatRoomDlg类添加成员变量。
2.3代码编写
聊天程序总共有6个类。其中,CChatApp和CAboutDlg由AppWizard实现;另外,CServerSocket和CClientSocket分别负责服务端和客户端的网络通信功能;CChatDlg为程序的主控类,主界面、通信方式及程序逻辑均由该类实现,它继承自CDialog类;CMessgage是对消息的封装。
2.3.1CClientSocket
通过相应的OnReceive消息来接收数据,响应OnClose消息来断开对话的处理,实现以CArchive对数据进行的串行化。
类定义代码如下:
#include"Messg.h" //命令目标
class CChatDlg;
class CClientSocket:public CSocket
{
public:
CArchive *m_aSessionIn;
CArchive *m_aSessionOut;
CSocketFile *m_sfSocketFile;
CChatDlg *m_dlg;
bool m_bInit; //是否进行了初始化
bool m_bClose; //连接是否关闭
public:
void Init(CChatDlg *dlg);
BOOL SendMessage(CMessg *msg);
void CloseSocket();
public:
static int GetLocalHostName(CString &sHostName); //获得本地计算机名称
static int GetIpAddress(const CString &sHostName,
CString &sIpAddress); //获得本地IP
static int GetIpAddress(const CString &sHostName,BYTE &f0,
BYTE &f1,BYTE &f2,BYTE &f3); //获得本地IP static CString ErrorReason(int tag);
public:
virtual void OnReceive(int nErrorCode);
virtual void OnClose(int nErrorCode); public:
CClientSocket();
virtual ~CClientSocket(); protected:
};
CClientSocket.CPP中的代码:
#include "stdafx.h"
#include "chat.h"
#include "ClientSocket.h"
#include"ChatDlg.h"
// CClientSocket
CClientSocket::CClientSocket()
{
m_aSessionIn=NULL;
m_aSessionOut=NULL;
m_sfSocketFile=NULL;
m_bInit=false;
m_bClose=false;
}
CClientSocket::~CClientSocket()
{
if(m_aSessionIn)
delete m_aSessionIn;
delete m_aSessionOut;
if(m_sfSocketFile)
delete m_sfSocketFile;
}
//ClientSocket成员函数
void CClientSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode); //OnReceive()函数的实现
do
{
CMessg temp;
temp.Serialize(*m_aSessionIn);
m_dlg->m_sMsgList+=temp.m_strText+"\r\n";
m_dlg->m_tmpMsgList=temp.m_strText+"\r\n";
m_dlg->SetDlgItemText(IDC_SHOWTEXT,m_dlg->m_sMsgList);
FILE* fp;
fp = fopen("chatnote.txt","a+");
fputs(m_dlg->m_tmpMsgList,fp);
fclose(fp);
m_dlg->SetDlgItemText(IDC_SHOWTEXT,m_dlg->m_sMsgList);
int linenum=((CEdit*)
(m_dlg->GetDlgItem(IDC_SHOWTEXT)))->GetLineCount();
((CEdit*)
(m_dlg->GetDlgItem(IDC_SHOWTEXT)))->LineScroll(linenum);
if(!m_dlg->m_bClient)
{
for(POSITION pos=m_dlg->m_connectionList.GetHeadPosition();
pos!=NULL;)
CClientSocket *t=(CClientSocket*)
m_dlg->m_connectionList.GetNext(pos);
if(t->m_hSocket!=this->m_hSocket)
{
t->SendMessage(&temp);
}
}
}
}
while(!m_aSessionIn->IsBufferEmpty());
}
void CClientSocket::Init(CChatDlg *dlg)
{
m_sfSocketFile=new CSocketFile(this);
m_aSessionIn=new CArchive(m_sfSocketFile,CArchive::load);
m_aSessionOut=new CArchive(m_sfSocketFile,CArchive::store);
m_bClose=false;
this->m_dlg=dlg;
}
/*
*SendMessage()函数的实现
*主要功能:
*将信息串行化
*/
BOOL CClientSocket::SendMessage(CMessg *msg)
{
{
msg->Serialize(*m_aSessionOut);
m_aSessionOut->Flush();
return TRUE;
}
else
{
m_bClose=true; //对方关闭了连接
CloseSocket();
m_dlg->CloseSessionSocket();
return FALSE;
}
}
/*
*CloseSocket()函数的实现
*主要功能:
*关闭套接字的连接
*/
void CClientSocket::CloseSocket()
{
if(m_aSessionIn)
{
delete m_aSessionIn;
m_aSessionIn=NULL;
}
if(m_aSessionOut)
{
m_aSessionOut=NULL;
}
if(m_sfSocketFile)
{
delete m_sfSocketFile;
m_sfSocketFile=NULL;
}
Close();
m_bInit=false;
m_bClose=true;
}
/*
*OnClose()函数的实现
*主要功能:
*关闭套接字的连接
*/
void CClientSocket::OnClose(int nErrorCode) {
m_bClose=true;
CloseSocket();
m_dlg->CloseSessionSocket();
CSocket::OnClose(nErrorCode);
}
/*
*GetLocalHostName()函数的实现
*主要功能:
*获得本地计算机的名称
*/
{
char szHostName[256];
int nRetCode;
nRetCode=gethostname(szHostName,sizeof(szHostName));
if(nRetCode!=0)
{ //产生错误
sHostName=_T("没有取得");
return GetLastError();
}
sHostName=szHostName;
return 0;
}
/*
*GetIpAddress()函数的实现
*主要功能:
*取得本地IP地址
*/
int CClientSocket::GetIpAddress(const CString &sHostName,CString &sIpAddress) //获得本地IP
{
struct hostent FAR *lpHostEnt=gethostbyname(sHostName);
if(lpHostEnt==NULL)
{ //产生错误
sIpAddress=_T("");
return GetLastError();
}
LPSTR lpAddr=lpHostEnt->h_addr_list[0];
if(lpAddr)
struct in_addr inAddr;
memmove(&inAddr,lpAddr,4);
sIpAddress=inet_ntoa(inAddr); //转换为标准格式
if(sIpAddress.IsEmpty())
sIpAddress=_T("没有取得");
}
return 0;
}
/*
* GetIpAddress ()函数的实现
*主要功能:
*获得本地IP地址
*/
int CClientSocket::GetIpAddress(const CString &sHostName,BYTE &f0,
BYTE &f1,BYTE &f2,BYTE &f3)//获得IP地址{
struct hostent FAR *lpHostEnt=gethostbyname(sHostName);
if(lpHostEnt==NULL)
{ //产生错误
f0=f1=f2=f3=0;
return GetLastError();
}
LPSTR lpAddr=lpHostEnt->h_addr_list[0]; //获取IP
if(lpAddr)
{
struct in_addr inAddr;
memmove(&inAddr,lpAddr,4);
f0=inAddr.S_un.S_un_b.s_b1;
f2=inAddr.S_un.S_un_b.s_b3;
f3=inAddr.S_un.S_un_b.s_b4;
}
return 0;
}
CString CClientSocket::ErrorReason(int tag) //错误信息的宏定义
{
CString result;
switch(tag)
{
case WSANOTINITIALISED:
result="A successful AfxSocketInit must occur before using this API.";
break;
case WSAENETDOWN:
result="The network subsystem failed";
break;
case WSAEADDRINUSE:
result="The specified address is already in use";
break;
case WSAEINPROGRESS:
result="A blocking Windows Socket call is in progress";
break;
case WSAEADDRNOTA V AIL:
result="The specified address is not available from the local machine";
break;
case WSAEAFNOSUPPORT:
break;
case WSAECONNREFUSED:
result="The attempt to connect eas rejected";
break;
case WSAEDESTADDRREQ:
result="A destination address is requireed";
break;
case WSAEFAULT:
result="The nSockAddrLen arguement is incorrect";
break;
case WSAEINV AL:
result="Invalid host address";
break;
case WSAEISCONN:
result="The socket is already connected";
break;
case WSAEMFILE:
result="No more file descriptions are available";
break;
case WSAENETUNREACH:
result="The network cannot be reached from this host at this time";
break;
case WSAENOBUFS:
result="No buffer space is available.The socket cannot be connected";
break;
case WSAENOTSOCK:
result="The descriptor is not a socket";
break;
result="Attempt to connect timed out without establishing a connection";
break;
case WSAEWOULDBLOCK:
result="The socket is marked as nonblocking and the connnection cannot be completed immediately";
break;
default:
result="unknown error";
}
return result;
}
2.3.2CServerSocket
CServerSocket主要功能是实现OnAccept()消息,负责监听服务窗口,是一个服务socket。
CServerSocket.h的代码如下:
// CServerSocket command target
class CChatDlg;
class CServerSocket: public CSocket
{
public:
CServerSocket();
virtual ~CServerSocket();
CChatDlg *m_dlg;
UINT m_uPort;
BOOL Init(UINT port,CChatDlg *dlg);
virtual void OnAccept(int nErrorCode); //用于响应OnAccept消息的函数};
CServerSocket.CPP的代码如下:
#include "stdafx.h"
#include "chat.h"
#include "ServerSocket.h"
#include "ChatDlg.h"
// CServerSocket
CServerSocket::CServerSocket()
{
}
CServerSocket::~CServerSocket()
{
}
//CServerSocket成员函数
BOOL CServerSocket::Init(UINT port,CChatDlg *dlg)
{
m_uPort=port;
m_dlg=dlg;
if(Create(m_uPort)==FALSE)
{
AfxMessageBox(_T("Server Socket Create Error"));
return FALSE;
}