计算机网络课设基于TCP协议书编程的网络聊天室.docx
《计算机网络课设基于TCP协议书编程的网络聊天室.docx》由会员分享,可在线阅读,更多相关《计算机网络课设基于TCP协议书编程的网络聊天室.docx(25页珍藏版)》请在冰豆网上搜索。
计算机网络课设基于TCP协议书编程的网络聊天室
基于TCP协议编程的网络聊天室
设计内容:
基于TCP协议编程的方式,编写程序模拟网络聊天室的运行过程。
设计要求:
1.采用C/S模式,基于TCP协议编程的方式,使得各个用户通过服务器转发实现聊天的功能。
2.分为两大模块:
客户端模块和服务器端模块。
3.客户端模块的主要功能:
1)登陆功能:
用户可以注册,然后选择服务器登入聊天室。
2)显示用户:
将在线用户显示在列表中。
3)接收信息:
能接收其他用户发出的信息。
4)发送信息:
能发出用户要发出的信息。
4.服务器端模块的主要功能:
1)检验登陆信息:
检查登陆信息是否正确,并向客户端返回登陆信息,如信息正确。
就允许用户登陆。
2)显示在线状态:
将该用户的状态发给各在线用户。
3)转发聊天信息:
将消息转发给所有在线的用户。
5.编程语言不限。
一、需求分析
此程序主要分为两部分:
服务器端和客户端。
服务器端用于提供一个网络端口,等待客户端发出请求,登录到此服务端,然后进行网络通讯和消息的转发;客户端可通过服务器端的IP地址发送连接请求,然后登陆聊天室。
在服务器端的成员列表栏中会显示在线的所有人名单,有人退出聊天室,成员列表会自动除名。
整个程序的主体使用了CSocket类的方法,实现了网络通讯聊天。
整个程序设计为两个部分:
服务器(SpeakerServer)和客户端(SpeakerClient) 。
多人聊天的关键在于要将每个客户端发送过来的消息分发给所有其他客户端,为了解决这个问题,在服务器程序中建立一个套接口链表,用来保存所有与客户端建立了连接的服务端口。
设计原理:
服务器通过socket()系统调用创建一个Socket数组后(设定了接受连接客户的最大数目),与指定的本地端口绑定bind(),就可以在端口进行侦听listen()。
如果有客户端连接请求,则在数组中选择一个空socket,将客户端地址赋给这个socket,然后登陆成功的客户就可以在服务器上聊天了。
客户端程序相对简单,只要建立一个socket与服务器端连接,成功后通过这个socket来发送和接收就可以了。
服务器端功能:
1、初始化socket,创建服务器端。
2、维护一个链表,保存所有用户的IP地址,端口信息。
3、接受用户传送来的聊天信息,然后向链表中的所用用户转发。
4、接受用户传送来的连接判断命令,并向用户发出响应命令。
客户端功能:
客户端界面上的两个文本框,一个用于显示接受的聊天信息,一个用来接受用户输入的聊天信息。
当按下“发送”按钮时将信息发送给服务器。
一、概要设计:
服务器客户端
(设计流程图)
二、详细设计:
服务器端:
1、启动服务器代码:
//服务器启动时,先创建套接字并绑定端口,再监听此端口。
voidCSpeakerServerDlg:
:
OnBnClickedStart()
{
UINTuPort=GetDlgItemInt(IDC_PORT);
//创建套接字
if(!
m_TCPSocketListen.Create(uPort))
{
m_TraceRichEdit.TraceString(TEXT("绑定监听端口失败,请确认该端口没有被其它程序占用"),TraceLevel_Warning);
return;
}
//监听套接字
if(!
m_TCPSocketListen.Listen())
{
m_TraceRichEdit.TraceString(TEXT("监听失败"),TraceLevel_Warning);
return;
}
UINTuMaxConnect=GetDlgItemInt(IDC_MAX);
//设置接口
m_TCPSocketListen.SetTCPSocketService(this);
//更新界面
m_TraceRichEdit.TraceString(TEXT("服务器启动成功"),TraceLevel_Normal);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
}
2、监听端口,收到连接请求,接受的代码:
//先检验是否在服务器的最大连接限制内,若在,则获取当前客户的IP地址和端口等信息,插入链表中。
//为什么要限制连接人数?
因为TCP连接是相当占资源的,若不限制连接人数,服务器的资源不够分配。
voidCSpeakerServerDlg:
:
OnAccept()
{
//承载能力
if(m_TCPSocketItemMap.size()>GetDlgItemInt(IDC_MAX))
{
m_TraceRichEdit.TraceString(TEXT("服务器承载人数已满,已过滤其他连接"),TraceLevel_Warning);
return;
}
//绑定套接字
CTCPSocketService*pTCPSocketConnect=newCTCPSocketService;
try
{
SOCKADDR_INSocketAddr;
intnBufferSize=sizeof(SocketAddr);
//连接
m_TCPSocketListen.Accept(*pTCPSocketConnect,(SOCKADDR*)&SocketAddr,&nBufferSize);
if(pTCPSocketConnect->m_hSocket==INVALID_SOCKET)throwTEXT("无效的连接套接字");
//获取客户端IP
pTCPSocketConnect->m_dwClientAddr=SocketAddr.sin_addr.S_un.S_addr;
pTCPSocketConnect->SetTCPSocketService(this);
//绑定数据
boolbActive=true;
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.begin();
for(;iter!
=m_TCPSocketItemMap.end();iter++)
{
if(pTCPSocketConnect->m_hSocket==iter->first)
{
bActive=false;
break;
}
}
//插入客户数据
if(bActive)
{
tagBindParameter*pBindParameter=newtagBindParameter;
pBindParameter->pTCPSocketService=pTCPSocketConnect;
pBindParameter->dwUserID=0;
m_TCPSocketItemMap.insert(pair(pTCPSocketConnect->m_hSocket,pBindParameter));
}
}
catch(...)
{
if(pTCPSocketConnect->m_hSocket!
=INVALID_SOCKET)pTCPSocketConnect->Close();
}
}
3、接收并检验数据的代码:
voidCSpeakerServerDlg:
:
OnReceive(SOCKEThSocket)
{
BYTEcbDataBuffer[SOCKET_TCP_BUFFER];
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.find(hSocket);
if(iter==m_TCPSocketItemMap.end())return;
//接收数据
iter->second->pTCPSocketService->Receive(cbDataBuffer,CountArray(cbDataBuffer));
//解析数据
TCP_Command*pCommand=(TCP_Command*)cbDataBuffer;
//解释数据
WORDwPacketSize=pCommand->wPacketSize;
WORDwDataSize=wPacketSize-sizeof(TCP_Command);
//数据包效验
if(wPacketSize>SOCKET_TCP_BUFFER+sizeofTCP_Command)
{
m_TraceRichEdit.TraceString(TEXT("数据包太大,已拒绝"),TraceLevel_Warning);
return;
}
//子消息处理事件
if(!
OnEventTCPSocketRead(hSocket,pCommand->wMainCmdID,pCommand->wSubCmdID,pCommand+1,wDataSize))
{
BYTE*pClientIP=(BYTE*)&iter->second->pTCPSocketService->m_dwClientAddr;
m_TraceRichEdit.TraceString(TraceLevel_Warning,TEXT("收到伪数据包或未处理的数据包,wMainCmdID:
%d,wSubCmdID:
%d,来源IP:
%d.%d.%d.%d"),pCommand->wMainCmdID,pCommand->wSubCmdID,pClientIP[0],pClientIP[1],pClientIP[2],pClientIP[3]);
return;
}
}
4、群发登录消息和用户发送的消息代码:
//服务器收到客户的消息之后会将收到的消息发送给链表之中除了发送客户之外的所有客户。
boolCSpeakerServerDlg:
:
OnEventTCPSocketRead(SOCKEThSocket,WORDwMainCmdID,WORDwSubCmdID,VOID*pData,WORDwDataSize)
{
//获取绑定套接字
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.find(hSocket);
if(iter==m_TCPSocketItemMap.end())returnfalse;
CTCPSocketService*pTCPSocketService=iter->second->pTCPSocketService;
switch(wMainCmdID)
{
caseMDM_GP_LOGON:
{
if(wSubCmdID==SUB_CS_LOGON)
{
//效验数据
ASSERT(wDataSize==sizeofCMD_CS_LOGON);
if(wDataSize!
=sizeofCMD_CS_LOGON)returnfalse;
//获取数据
CMD_CS_LOGON*pUserLogon=(CMD_CS_LOGON*)pData;
m_TraceRichEdit.TraceString(TraceLevel_Normal,TEXT("%s登陆服务器"),pUserLogon->szUserName);
tagUserData*pUserData=newtagUserData;
//随机给用户分配一个UserID,UserID一般存储于数据库中,是一个独一无二的数字,
//一般在数据库表中设为主键,是整个游戏或者软件识别用户的唯一依据,这里我们没有涉及到数据库,暂时随机取一个数值代替
//其次,我们应该通过数据库SQL语句查询或者存储过程等方法,或在数据库中做密码的效验也好,
//或在查询到用户的密码在服务器中进行判断也好,不管什么方法,此处一般需要进行用户密码的效验,这样才可以判定用户是否可以登陆了
pUserData->dwUserID=GetTickCount();
_sntprintf_s(pUserData->szUserName,CountArray(pUserData->szUserName),pUserLogon->szUserName);
_sntprintf_s(pUserData->szPassWord,CountArray(pUserData->szPassWord),pUserLogon->szPassWord);
//更新绑定数据
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.find(hSocket);
if(iter!
=m_TCPSocketItemMap.end())
iter->second->dwUserID=pUserData->dwUserID;
//群发登陆消息
SendUserItem(NULL,pUserData);
//发送在线用户
CUserItemArray:
:
iteratorpUserItemSend=m_pUserManager->GetUserItemArray()->begin();
for(;pUserItemSend!
=m_pUserManager->GetUserItemArray()->end();pUserItemSend++)
SendUserItem(pTCPSocketService,pUserItemSend->second);
//插入数据
m_pUserManager->InsertUserItem(pUserData);
returntrue;
}
}
break;
caseMDM_GP_USER:
{
if(wSubCmdID==SUB_CS_USERT_CHAT)
{
//获取数据
CMD_CS_CHATMSG*pCHATMSG=(CMD_CS_CHATMSG*)pData;
//这里其实需要做很多的效验,如dwSendUserID的有效性,字符串是否为空等,这里就不做这些效验了
CMD_SC_CHATMSG_SC_CHATMSG;
ZeroMemory(&_SC_CHATMSG,sizeof_SC_CHATMSG);
//获取时间
GetLocalTime(&_SC_CHATMSG.SystemTime);
_sntprintf_s(_SC_CHATMSG.szSendUserName,CountArray(_SC_CHATMSG.szSendUserName),m_pUserManager->GetUserName(iter->second->dwUserID));
_sntprintf_s(_SC_CHATMSG.szDescribe,CountArray(_SC_CHATMSG.szDescribe),pCHATMSG->szDescribe);
SendDataBatch(MDM_GP_USER,SUB_CS_USERT_CHAT,&_SC_CHATMSG,sizeof_SC_CHATMSG);
returntrue;
}
}
break;
}
returnfalse;
}
5、当服务器端有人退出登录时的代码:
//客户端退出时,服务器端获取用户名并群发退出消息,再在链表中删除该用户的数据,清理他的Socket
voidCSpeakerServerDlg:
:
OnClose(SOCKEThSocket)
{
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.find(hSocket);
if(iter==m_TCPSocketItemMap.end())return;
//获取用户
m_TraceRichEdit.TraceString(TraceLevel_Normal,TEXT("%s退出了服务器"),m_pUserManager->GetUserName(iter->second->dwUserID));
//删除用户
CMD_DC_DELETE_DC_DELETE;
ZeroMemory(&_DC_DELETE,sizeof_DC_DELETE);
_sntprintf_s(_DC_DELETE.szUserName,CountArray(_DC_DELETE.szUserName),m_pUserManager->GetUserName(iter->second->dwUserID));
//群发消息
SendDataBatch(MDM_GP_USER,SUB_SC_DELETE,&_DC_DELETE,sizeof_DC_DELETE);
//销毁数据
m_pUserManager->RemoveUserItem(iter->second->dwUserID);
iter->second->pTCPSocketService->Close();
SafeDelete(iter->second->pTCPSocketService);
SafeDelete(iter->second);
m_TCPSocketItemMap.erase(iter);
}
6、关闭服务器连接代码:
voidCSpeakerServerDlg:
:
OnBnClickedStop()
{
//关闭监听套接字
m_TCPSocketListen.Close();
//关闭连接套接字
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.begin();
for(;iter!
=m_TCPSocketItemMap.end();++iter)
{
iter->second->pTCPSocketService->Close();
SafeDelete(iter->second->pTCPSocketService);
SafeDelete(iter->second);
}
//更新界面
m_TraceRichEdit.TraceString(TEXT("服务器关闭成功"),TraceLevel_Normal);
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
}
7、退出服务器代码:
voidCSpeakerServerDlg:
:
OnCancel()
{
if(m_TCPSocketListen.m_hSocket!
=INVALID_SOCKET)
{
if(AfxMessageBox(TEXT("确定退出服务器吗?
其它所有用户将失去连接"),MB_YESNO|MB_ICONQUESTION)==IDYES)
{
CTCPSocketItemMap:
:
iteratoriter=m_TCPSocketItemMap.begin();
for(;iter!
=m_TCPSocketItemMap.end();++iter)
{
iter->second->pTCPSocketService->Close();
SafeDelete(iter->second->pTCPSocketService);
SafeDelete(iter->second);
}
}
}
__super:
:
OnCancel();
}
客户端:
1、客户端登录:
//登陆消息
LRESULTCSpeakerClientDlg:
:
OnLogonMessage(WPARAMwParam,LPARAMlParam)
{
tagLogonInfo*pLogonInfo=(tagLogonInfo*)wParam;
//关闭之前socket
m_TCPScoketClient.Close();
//初始化套接字
if(!
m_TCPScoketClient.Create())
{
SetTraceString(TEXT("套接字创建失败"));
SafeDelete(pLogonInfo);
returnFALSE;
}
//创建连接
if(m_TCPScoketClient.Connect(pLogonInfo->szServerAddr,pLogonInfo->nPort)==FALSE)
{
intnErrorCode=m_TCPScoketClient.GetLastError();
if(nErrorCode!
=WSAEWOULDBLOCK)
{
SetTraceString(TEXT("连接服务器失败,错误码:
%d"),nErrorCode);
SafeDelete(pLogonInfo);
returnFALSE;
}
}
//设置接口
m_TCPScoketClient.SetTCPSocketService(this);
//构建数据
CMD_CS_LOGONUserLogon;
ZeroMemory(&UserLogon,sizeofUserLogon);
_sntprintf_s(UserLogon.szUserName,CountArray(UserLogon.szUserName),pLogonInfo->szUserName);
_sntprintf_s(UserLogon.szPassWord,CountArray(UserLogon.szPassWord),pLogonInfo->szPassWord);
//发送登陆请求
m_TCPScoketClient.SendData(MDM_GP_LOGON,SUB_CS_LOGO