计算机网络课设基于TCP协议编程的网络聊天室Word下载.docx
- 文档编号:19037640
- 上传时间:2023-01-03
- 格式:DOCX
- 页数:21
- 大小:88.87KB
计算机网络课设基于TCP协议编程的网络聊天室Word下载.docx
《计算机网络课设基于TCP协议编程的网络聊天室Word下载.docx》由会员分享,可在线阅读,更多相关《计算机网络课设基于TCP协议编程的网络聊天室Word下载.docx(21页珍藏版)》请在冰豆网上搜索。
服务器(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())
监听失败"
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连接是相当占资源的,若不限制连接人数,服务器的资源不够分配。
OnAccept()
//承载能力
if(m_TCPSocketItemMap.size()>
GetDlgItemInt(IDC_MAX))
服务器承载人数已满,已过滤其他连接"
//绑定套接字
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;
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;
dwUserID=0;
m_TCPSocketItemMap.insert(pair<
SOCKET,tagBindParameter*>
(pTCPSocketConnect->
m_hSocket,pBindParameter));
catch(...)
m_hSocket!
=INVALID_SOCKET)pTCPSocketConnect->
Close();
3、接收并检验数据的代码:
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)
数据包太大,已拒绝"
//子消息处理事件
OnEventTCPSocketRead(hSocket,pCommand->
wMainCmdID,pCommand->
wSubCmdID,pCommand+1,wDataSize))
BYTE*pClientIP=(BYTE*)&
iter->
m_dwClientAddr;
m_TraceRichEdit.TraceString(TraceLevel_Warning,TEXT("
收到伪数据包或未处理的数据包,wMainCmdID:
%d,wSubCmdID:
%d,来源IP:
%d.%d.%d.%d"
),pCommand->
wSubCmdID,pClientIP[0],pClientIP[1],pClientIP[2],pClientIP[3]);
4、群发登录消息和用户发送的消息代码:
//服务器收到客户的消息之后会将收到的消息发送给链表之中除了发送客户之外的所有客户。
boolCSpeakerServerDlg:
OnEventTCPSocketRead(SOCKEThSocket,WORDwMainCmdID,WORDwSubCmdID,VOID*pData,WORDwDataSize)
//获取绑定套接字
if(iter==m_TCPSocketItemMap.end())returnfalse;
CTCPSocketService*pTCPSocketService=iter->
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->
szPassWord,CountArray(pUserData->
szPassWord),pUserLogon->
szPassWord);
//更新绑定数据
CTCPSocketItemMap:
if(iter!
=m_TCPSocketItemMap.end())
iter->
dwUserID=pUserData->
dwUserID;
//群发登陆消息
SendUserItem(NULL,pUserData);
//发送在线用户
CUserItemArray:
iteratorpUserItemSend=m_pUserManager->
GetUserItemArray()->
begin();
for(;
pUserItemSend!
=m_pUserManager->
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->
dwUserID));
_sntprintf_s(_SC_CHATMSG.szDescribe,CountArray(_SC_CHATMSG.szDescribe),pCHATMSG->
szDescribe);
SendDataBatch(MDM_GP_USER,SUB_CS_USERT_CHAT,&
returnfalse;
5、当服务器端有人退出登录时的代码:
//客户端退出时,服务器端获取用户名并群发退出消息,再在链表中删除该用户的数据,清理他的Socket
OnClose(SOCKEThSocket)
//获取用户
m_TraceRichEdit.TraceString(TraceLevel_Normal,TEXT("
%s退出了服务器"
),m_pUserManager->
//删除用户
CMD_DC_DELETE_DC_DELETE;
ZeroMemory(&
_DC_DELETE,sizeof_DC_DELETE);
_sntprintf_s(_DC_DELETE.szUserName,CountArray(_DC_DELETE.szUserName),m_pUserManager->
//群发消息
SendDataBatch(MDM_GP_USER,SUB_SC_DELETE,&
//销毁数据
m_pUserManager->
RemoveUserItem(iter->
dwUserID);
SafeDelete(iter->
pTCPSocketService);
m_TCPSocketItemMap.erase(iter);
6、关闭服务器连接代码:
OnBnClickedStop()
//关闭监听套接字
m_TCPSocketListen.Close();
//关闭连接套接字
for(;
iter!
++iter)
iter->
SafeDelete(iter->
服务器关闭成功"
7、退出服务器代码:
OnCancel()
if(m_TCPSocketListen.m_hSocket!
=INVALID_SOCKET)
if(AfxMessageBox(TEXT("
确定退出服务器吗?
其它所有用户将失去连接"
),MB_YESNO|MB_ICONQUESTION)==IDYES)
CTCPSocketItemMap:
for(;
iter->
SafeDelete(iter->
__super:
OnCancel();
客户端:
1、客户端登录:
//登陆消息
LRESULTCSpeakerClientDlg:
OnLogonMessage(WPARAMwParam,LPARAMlParam)
tagLogonInfo*pLogonInfo=(tagLogonInfo*)wParam;
//关闭之前socket
m_TCPScoketClient.Close();
//初始化套接字
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;
UserLogon,sizeofUserLogon);
_sntprintf_s(UserLogon.szUserName,CountArray(UserLogon.szUserName),pLogonInfo->
_sntprintf_s(UserLogon.szPassWord,CountArray(UserLogon.szPassWord),pLogonInfo->
//发送登陆请求
m_TCPScoketClient.SendData(MDM_GP_LOGON,SUB_CS_LOGON,&
UserLogon,sizeofUserL
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机网络 基于 TCP 协议 编程 网络 聊天室