《计算机网络课设-基于TCP协议编程的网络聊天室(共27页).docx》由会员分享,可在线阅读,更多相关《计算机网络课设-基于TCP协议编程的网络聊天室(共27页).docx(27页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上基于TCP协议编程的网络聊天室设计内容:基于TCP协议编程的方式,编写程序模拟网络聊天室的运行过程。设计要求:1. 采用C/S模式,基于TCP协议编程的方式,使得各个用户通过服务器转发实现聊天的功能。2. 分为两大模块:客户端模块和服务器端模块。3. 客户端模块的主要功能:1)登陆功能:用户可以注册,然后选择服务器登入聊天室。2)显示用户:将在线用户显示在列表中。3)接收信息:能接收其他用户发出的信息。4)发送信息:能发出用户要发出的信息。4.服务器端模块的主要功能:1)检验登陆信息:检查登陆信息是否正确,并向客户端返回登陆信息,如信息正确。就允许用户登陆。2)显示在
2、线状态:将该用户的状态发给各在线用户。3)转发聊天信息:将消息转发给所有在线的用户。5. 编程语言不限。一、 需求分析此程序主要分为两部分:服务器端和客户端。服务器端用于提供一个网络端口,等待客户端发出请求,登录到此服务端,然后进行网络通讯和消息的转发;客户端可通过服务器端的IP地址发送连接请求,然后登陆聊天室。在服务器端的成员列表栏中会显示在线的所有人名单,有人退出聊天室,成员列表会自动除名。整个程序的主体使用了CSocket类的方法,实现了网络通讯聊天。整个程序设计为两个部分:服务器(SpeakerServer)和客户端 (SpeakerClient)。 多人聊天的关键在于要将每个客户端发
3、送过来的消息分发给所有其他客户端,为了解决这个问题,在服务器程序中建立一个套接口链表,用来保存所有与客户端建立了连接的服务端口。设计原理:服务器通过socket()系统调用创建一个Socket数组后(设定了接受连接客户的最大数目),与指定的本地端口绑定bind(),就可以在端口进行侦听listen()。如果有客户端连接请求,则在数组中选择一个空socket,将客户端地址赋给这个socket,然后登陆成功的客户就可以在服务器上聊天了。客户端程序相对简单,只要建立一个socket与服务器端连接,成功后通过这个socket来发送和接收就可以了。服务器端功能:1、 初始化socket,创建服务器端。2
4、、 维护一个链表,保存所有用户的IP地址,端口信息。3、 接受用户传送来的聊天信息,然后向链表中的所用用户转发。4、 接受用户传送来的连接判断命令,并向用户发出响应命令。客户端功能:客户端界面上的两个文本框,一个用于显示接受的聊天信息,一个用来接受用户输入的聊天信息。当按下“发送”按钮时将信息发送给服务器。一、 概要设计: 服务器 客户端 (设计流程图)二、 详细设计:服务器端:1、启动服务器代码:/服务器启动时,先创建套接字并绑定端口,再监听此端口。void CSpeakerServerDlg:OnBnClickedStart()UINT uPort = GetDlgItemInt(IDC_
5、PORT);/创建套接字if ( !m_TCPSocketListen.Create(uPort) )m_TraceRichEdit.TraceString(TEXT(绑定监听端口失败,请确认该端口没有被其它程序占用),TraceLevel_Warning);return;/监听套接字if( !m_TCPSocketListen.Listen() )m_TraceRichEdit.TraceString(TEXT(监听失败),TraceLevel_Warning);return;UINT uMaxConnect = GetDlgItemInt(IDC_MAX);/设置接口m_TCPSocket
6、Listen.SetTCPSocketService(this);/更新界面m_TraceRichEdit.TraceString(TEXT(服务器启动成功),TraceLevel_Normal);GetDlgItem(IDC_START)-EnableWindow(FALSE);GetDlgItem(IDC_STOP)-EnableWindow(TRUE);2、监听端口,收到连接请求,接受的代码:/先检验是否在服务器的最大连接限制内,若在,则获取当前客户的IP地址和端口等信息,插入链表中。/为什么要限制连接人数?因为TCP连接是相当占资源的,若不限制连接人数,服务器的资源不够分配。void
7、CSpeakerServerDlg:OnAccept()/承载能力if ( m_TCPSocketItemMap.size() GetDlgItemInt(IDC_MAX) )m_TraceRichEdit.TraceString(TEXT(服务器承载人数已满,已过滤其他连接),TraceLevel_Warning);return;/绑定套接字CTCPSocketService *pTCPSocketConnect = new CTCPSocketService; try SOCKADDR_INSocketAddr; int nBufferSize = sizeof(SocketAddr);
8、/连接 m_TCPSocketListen.Accept(*pTCPSocketConnect,(SOCKADDR *) &SocketAddr, &nBufferSize); if (pTCPSocketConnect-m_hSocket = INVALID_SOCKET) throw TEXT(无效的连接套接字); /获取客户端IP pTCPSocketConnect-m_dwClientAddr = SocketAddr.sin_addr.S_un.S_addr; pTCPSocketConnect-SetTCPSocketService(this);/绑定数据bool bActive
9、= true;CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.begin();for (;iter!= m_TCPSocketItemMap.end();iter+)if ( pTCPSocketConnect-m_hSocket = iter-first )bActive = false;break;/插入客户数据if ( bActive )tagBindParameter *pBindParameter = new tagBindParameter;pBindParameter-pTCPSocketService = pTCPSoc
10、ketConnect;pBindParameter-dwUserID = 0;m_TCPSocketItemMap.insert(pair(pTCPSocketConnect-m_hSocket,pBindParameter); catch (.) if (pTCPSocketConnect-m_hSocket != INVALID_SOCKET)pTCPSocketConnect-Close(); 3、接收并检验数据的代码:void CSpeakerServerDlg:OnReceive(SOCKET hSocket)BYTE cbDataBufferSOCKET_TCP_BUFFER;CT
11、CPSocketItemMap:iterator iter = m_TCPSocketItemMap.find(hSocket);if ( iter = m_TCPSocketItemMap.end() ) return;/接收数据iter-second-pTCPSocketService-Receive(cbDataBuffer,CountArray(cbDataBuffer) );/解析数据TCP_Command * pCommand=(TCP_Command *)cbDataBuffer;/解释数据WORD wPacketSize = pCommand-wPacketSize;WORD
12、wDataSize = wPacketSize-sizeof(TCP_Command);/数据包效验if ( wPacketSize SOCKET_TCP_BUFFER+sizeof TCP_Command )m_TraceRichEdit.TraceString(TEXT(数据包太大,已拒绝),TraceLevel_Warning);return;/子消息处理事件if( !OnEventTCPSocketRead(hSocket,pCommand-wMainCmdID,pCommand-wSubCmdID,pCommand+1,wDataSize) )BYTE * pClientIP=(BY
13、TE *)&iter-second-pTCPSocketService-m_dwClientAddr;m_TraceRichEdit.TraceString(TraceLevel_Warning,TEXT(收到伪数据包或未处理的数据包,wMainCmdID:%d,wSubCmdID:%d,来源IP:%d.%d.%d.%d),pCommand-wMainCmdID,pCommand-wSubCmdID,pClientIP0,pClientIP1,pClientIP2,pClientIP3);return;4、群发登录消息和用户发送的消息代码:/服务器收到客户的消息之后会将收到的消息发送给链表之中
14、除了发送客户之外的所有客户。bool CSpeakerServerDlg:OnEventTCPSocketRead( SOCKET hSocket,WORD wMainCmdID, WORD wSubCmdID, VOID * pData, WORD wDataSize )/获取绑定套接字CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.find(hSocket);if ( iter = m_TCPSocketItemMap.end() ) return false;CTCPSocketService *pTCPSocketService
15、 = iter-second-pTCPSocketService;switch ( wMainCmdID )case MDM_GP_LOGON:if ( wSubCmdID = SUB_CS_LOGON )/效验数据ASSERT( wDataSize = sizeof CMD_CS_LOGON );if ( wDataSize != sizeof CMD_CS_LOGON ) return false;/获取数据CMD_CS_LOGON *pUserLogon = (CMD_CS_LOGON*)pData;m_TraceRichEdit.TraceString(TraceLevel_Norma
16、l,TEXT(%s登陆服务器),pUserLogon-szUserName);tagUserData *pUserData = new tagUserData;/随机给用户分配一个UserID,UserID一般存储于数据库中,是一个独一无二的数字,/一般在数据库表中设为主键,是整个游戏或者软件识别用户的唯一依据,这里我们没有涉及到数据库,暂时随机取一个数值代替/其次,我们应该通过数据库SQL语句查询或者存储过程等方法,或在数据库中做密码的效验也好,/或在查询到用户的密码在服务器中进行判断也好,不管什么方法,此处一般需要进行用户密码的效验,这样才可以判定用户是否可以登陆了pUserData-dw
17、UserID = GetTickCount();_sntprintf_s(pUserData-szUserName,CountArray(pUserData-szUserName),pUserLogon-szUserName);_sntprintf_s(pUserData-szPassWord,CountArray(pUserData-szPassWord),pUserLogon-szPassWord);/更新绑定数据CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.find(hSocket);if ( iter != m_TCPSock
18、etItemMap.end() )iter-second-dwUserID = pUserData-dwUserID;/群发登陆消息SendUserItem(NULL,pUserData);/发送在线用户CUserItemArray:iterator pUserItemSend = m_pUserManager-GetUserItemArray()-begin();for (;pUserItemSend!=m_pUserManager-GetUserItemArray()-end();pUserItemSend+ )SendUserItem(pTCPSocketService,pUserIte
19、mSend-second);/插入数据m_pUserManager-InsertUserItem(pUserData);return true;break;case MDM_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_C
20、HATMSG);/获取时间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
21、_CS_USERT_CHAT,&_SC_CHATMSG,sizeof _SC_CHATMSG);return true;break;return false;5、当服务器端有人退出登录时的代码:/客户端退出时,服务器端获取用户名并群发退出消息,再在链表中删除该用户的数据,清理他的Socketvoid CSpeakerServerDlg:OnClose(SOCKET hSocket)CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.find(hSocket);if ( iter = m_TCPSocketItemMap.end() ) re
22、turn;/获取用户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-
23、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、 关闭服务器连接代码:void CSpe
24、akerServerDlg:OnBnClickedStop()/关闭监听套接字m_TCPSocketListen.Close();/关闭连接套接字CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.begin();for (;iter != m_TCPSocketItemMap.end(); +iter)iter-second-pTCPSocketService-Close();SafeDelete(iter-second-pTCPSocketService);SafeDelete(iter-second);/更新界面m_TraceRich
25、Edit.TraceString(TEXT(服务器关闭成功),TraceLevel_Normal);GetDlgItem(IDC_START)-EnableWindow(TRUE);GetDlgItem(IDC_STOP)-EnableWindow(FALSE);7、退出服务器代码:void CSpeakerServerDlg:OnCancel()if ( m_TCPSocketListen.m_hSocket != INVALID_SOCKET )if ( AfxMessageBox(TEXT(确定退出服务器吗?其它所有用户将失去连接),MB_YESNO|MB_ICONQUESTION) =
26、 IDYES )CTCPSocketItemMap:iterator iter = m_TCPSocketItemMap.begin();for (;iter != m_TCPSocketItemMap.end(); +iter)iter-second-pTCPSocketService-Close();SafeDelete(iter-second-pTCPSocketService);SafeDelete(iter-second);_super:OnCancel();客户端:1、客户端登录:/登陆消息LRESULT CSpeakerClientDlg:OnLogonMessage( WPAR
27、AM wParam,LPARAM lParam )tagLogonInfo *pLogonInfo = (tagLogonInfo*)wParam;/关闭之前socketm_TCPScoketClient.Close();/初始化套接字if ( !m_TCPScoketClient.Create() )SetTraceString(TEXT(套接字创建失败);SafeDelete(pLogonInfo);return FALSE;/创建连接if( m_TCPScoketClient.Connect(pLogonInfo-szServerAddr,pLogonInfo-nPort) = FALS
28、E )int nErrorCode = m_TCPScoketClient.GetLastError();if ( nErrorCode !=WSAEWOULDBLOCK )SetTraceString(TEXT(连接服务器失败,错误码:%d),nErrorCode);SafeDelete(pLogonInfo);return FALSE;/设置接口m_TCPScoketClient.SetTCPSocketService(this);/构建数据CMD_CS_LOGON UserLogon;ZeroMemory(&UserLogon,sizeof UserLogon);_sntprintf_s
29、(UserLogon.szUserName,CountArray(UserLogon.szUserName),pLogonInfo-szUserName);_sntprintf_s(UserLogon.szPassWord,CountArray(UserLogon.szPassWord),pLogonInfo-szPassWord);/发送登陆请求m_TCPScoketClient.SendData(MDM_GP_LOGON,SUB_CS_LOGON,&UserLogon,sizeof UserLogon);/设置界面SetTraceString(TEXT(连接服务器成功);m_LogonDl
30、g.PostMessage(WM_CLOSE);/清理数据SafeDelete(pLogonInfo);return TRUE;2、客户端发送数据代码:void CSpeakerClientDlg:OnBnClickedSend()/设置数据CMD_CS_CHATMSG _UserChat_Msg;ZeroMemory(&_UserChat_Msg,sizeof _UserChat_Msg);GetDlgItemText(IDC_EDITCHAT,_UserChat_Msg.szDescribe,CountArray(_UserChat_Msg.szDescribe);/效验数据if ( _U
31、serChat_Msg.szDescribe0 = TEXT(0) )SetTraceString(TEXT(聊天内容为空,请先输入您想说的话);return;/发送数据m_TCPScoketClient.SendData(MDM_GP_USER,SUB_CS_USERT_CHAT,&_UserChat_Msg,sizeof _UserChat_Msg);3、客户端接收数据代码:/客户端接收数据和服务器段类似,也需解析、检验void CSpeakerClientDlg:OnReceive( int nErrorCode )/接收消息BYTE cbDataBufferSOCKET_TCP_BUF
32、FER;m_TCPScoketClient.Receive(cbDataBuffer,CountArray(cbDataBuffer) );/解析数据TCP_Command * pCommand=(TCP_Command *)cbDataBuffer;/解释数据WORD wPacketSize = pCommand-wPacketSize;WORD wDataSize = wPacketSize-sizeof(TCP_Command);/数据包效验if ( wPacketSize SOCKET_TCP_BUFFER+sizeof TCP_Command )SetTraceString(TEXT
33、(数据包太大,已拒绝);return;/子消息处理事件if( !OnEventTCPSocketRead(pCommand-wMainCmdID,pCommand-wSubCmdID,pCommand+1,wDataSize) )SetTraceString(TEXT(收到未处理的数据包,wMainCmdID:%d,wSubCmdID:%d),pCommand-wMainCmdID,pCommand-wSubCmdID);return;4、客户端消息的显示代码:/显示的消息类型:当用户登录时,将用户数据插入用户列表中。服务器端会有xx登录的显示。当用户发消息时,服务器端就可以转发该消息给用户链
34、表的所有其他用户。用户退出时,同理,客户端也会接收到XX退出了的消息。bool CSpeakerClientDlg:OnEventTCPSocketRead( WORD wMainCmdID, WORD wSubCmdID, VOID * pData, WORD wDataSize )switch ( wMainCmdID )case MDM_GP_LOGON:/登陆消息if ( wSubCmdID = SUB_SC_USERCOME)/用户进入CMD_SC_USERCOME *pUserCome = (CMD_SC_USERCOME*)pData;/插入数据if( m_ListUser.Fi
35、ndString(-1,pUserCome-szUserName) = LB_ERR )/设置自己信息if ( m_UserData.dwUserID = 0 )_sntprintf_s(m_UserData.szUserName,CountArray(m_UserData.szUserName),pUserCome-szUserName);m_UserData.dwUserID = m_UserData.dwUserID;SetWindowText(m_UserData.szUserName);/添加用户列表m_ListUser.AddString(pUserCome-szUserName)
36、;m_ListUser.SetItemData(m_ListUser.GetCount()-1,pUserCome-dwUserID);return true;break;case MDM_GP_USER:/用户消息if ( wSubCmdID = SUB_CS_USERT_CHAT )/聊天消息CMD_SC_CHATMSG *pCHATMSG = (CMD_SC_CHATMSG*)pData;/设置聊天数据static CString str;CString StrDescribe;StrDescribe.Format(TEXT(%s %04d-%02d-%02d %02d:%02d:%02
37、drn),pCHATMSG-szSendUserName,pCHATMSG-SystemTime.wYear, pCHATMSG-SystemTime.wMonth, pCHATMSG-SystemTime.wDay, pCHATMSG-SystemTime.wHour, pCHATMSG-SystemTime.wMinute, pCHATMSG-SystemTime.wSecond);str += StrDescribe;str += pCHATMSG-szDescribe;str += TEXT(rn);SetDlgItemText(IDC_CHATRECV,str);return tru
38、e;else if ( wSubCmdID = SUB_SC_DELETE )/用户退出消息CMD_DC_DELETE *pDeleteUser = (CMD_DC_DELETE*)pData;int nIndex = m_ListUser.FindString(-1,pDeleteUser-szUserName);if ( nIndex != LB_ERR )m_ListUser.DeleteString(nIndex);static CString str;CString StrDescribe;StrDescribe.Format(TEXT(%s退出了.), pDeleteUser-sz
39、UserName);str += StrDescribe;str += TEXT(rn);SetDlgItemText(IDC_CHATRECV, str);return true;break;return false;5、退出客户端:void CSpeakerClientDlg:OnCancel()if (m_TCPScoketClient.m_hSocket != INVALID_SOCKET)if (AfxMessageBox(TEXT(确定退出客户端吗?), MB_YESNO | MB_ICONQUESTION) = IDYES)/关闭套接字m_TCPScoketClient.Clos
40、e();CDialog:OnCancel();三、 调试分析: 客户端用户登录:服务器端:客户1发hello:客户2发你好:用户1和用户2退出时服务器端的显示:四、 课设总结: 这次课程设计的制作我主要是参照了网上的一些网络编程实例和图书馆的相关书籍,找到类似的程序,跟着书上一步一步的做出来的,虽然这个程序算不上是我自己写出来的,但是通过这个过程,还是让我学会了好多东西,也算能比较熟练地掌握MFC这一个软件了,这算是一个不小的收获。而且在做这个程序的过程中,遇到许许多多的问题,有的通过网上搜索可以找到答案。有的却不行,最后还是大家一起讨论出来的。总之,最后的这个程序的功能达到了之前预想的可能,
41、通过服务器端的消息转发,实现了多用户之间的群聊,完整了一个简单的网络聊天软件的功能。 但是最后验收的时候,老师还是希望我能做出私聊的效果来,但是由于时间限制,并未能实现它。私聊实现的思路:用户1若想与用户2私聊,则可以在用户列表选中用户2,服务器接收到用户1的请求之后,将用户2的IP地址和端口号发送给用户1,这样用户1就可以跟用户2T做TCP连接,并聊天了。通过此次的实验,也同时让我对于CSocket网络聊天类的使用有了更深入的了解,在关于网络编程的方面也有了新的认识。虽然此程序的功能还比较的简单,而且某些方面还没能完善。现在就软件自身还存在的问题罗列如下:1、 成员列表在非正常退出时,服务器端得不到及时而有效地更新2、 用户登录前必须先知道服务器端的IP,这样操作显然比较麻烦3、 程序界面做的比较单一,缺少层次性的美感。专心-专注-专业