搜档网
当前位置:搜档网 › 基于TCP的socket编程的聊天工具实现

基于TCP的socket编程的聊天工具实现

摘要

沟通是人类社会里不可或缺的一种行为,在信息发达的今天,沟通的方式多种多样,而网络聊天作为一种新的沟通方式,例如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;

}

相关主题