計(jì)算機(jī)網(wǎng)絡(luò)課設(shè)基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室_第1頁
計(jì)算機(jī)網(wǎng)絡(luò)課設(shè)基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室_第2頁
計(jì)算機(jī)網(wǎng)絡(luò)課設(shè)基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室_第3頁
計(jì)算機(jī)網(wǎng)絡(luò)課設(shè)基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室_第4頁
計(jì)算機(jī)網(wǎng)絡(luò)課設(shè)基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室_第5頁
已閱讀5頁,還剩33頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

基于TCP協(xié)議編程的網(wǎng)絡(luò)聊天室設(shè)計(jì)內(nèi)容:基于TCP協(xié)議編程的方式,編寫程序模擬網(wǎng)絡(luò)聊天室的運(yùn)營過程。設(shè)計(jì)規(guī)定:1.采用C/S模式,基于TCP協(xié)議編程的方式,使得各個用戶通過服務(wù)器轉(zhuǎn)發(fā)實(shí)現(xiàn)聊天的功能。2.分為兩大模塊:客戶端模塊和服務(wù)器端模塊。3.客戶端模塊的重要功能:1)登陸功能:用戶可以注冊,然后選擇服務(wù)器登入聊天室。2)顯示用戶:將在線用戶顯示在列表中。3)接受信息:能接受其他用戶發(fā)出的信息。4)發(fā)送信息:能發(fā)出用戶要發(fā)出的信息。4.服務(wù)器端模塊的重要功能:1)檢查登陸信息:檢查登陸信息是否對的,并向客戶端返回登陸信息,如信息對的。就允許用戶登陸。2)顯示在線狀態(tài):將該用戶的狀態(tài)發(fā)給各在線用戶。3)轉(zhuǎn)發(fā)聊天信息:將消息轉(zhuǎn)發(fā)給所有在線的用戶。5.編程語言不限。需求分析此程序重要分為兩部分:服務(wù)器端和客戶端。服務(wù)器端用于提供一個網(wǎng)絡(luò)端口,等待客戶端發(fā)出請求,登錄到此服務(wù)端,然后進(jìn)行網(wǎng)絡(luò)通訊和消息的轉(zhuǎn)發(fā);客戶端可通過服務(wù)器端的IP地址發(fā)送連接請求,然后登陸聊天室。在服務(wù)器端的成員列表欄中會顯示在線的所有人名單,有人退出聊天室,成員列表會自動除名。整個程序的主體使用了CSocket類的方法,實(shí)現(xiàn)了網(wǎng)絡(luò)通訊聊天。整個程序設(shè)計(jì)為兩個部分:服務(wù)器(SpeakerServer)和客戶端(SpeakerClient)

。多人聊天的關(guān)鍵在于要將每個客戶端發(fā)送過來的消息分發(fā)給所有其他客戶端,為了解決這個問題,在服務(wù)器程序中建立一個套接口鏈表,用來保存所有與客戶端建立了連接的服務(wù)端口。設(shè)計(jì)原理:服務(wù)器通過socket()系統(tǒng)調(diào)用創(chuàng)建一個Socket數(shù)組后(設(shè)定了接受連接客戶的最大數(shù)目),與指定的本地端口綁定bind(),就可以在端口進(jìn)行偵聽listen()。假如有客戶端連接請求,則在數(shù)組中選擇一個空socket,將客戶端地址賦給這個socket,然后登陸成功的客戶就可以在服務(wù)器上聊天了。客戶端程序相對簡樸,只要建立一個socket與服務(wù)器端連接,成功后通過這個socket來發(fā)送和接受就可以了。服務(wù)器端功能:初始化socket,創(chuàng)建服務(wù)器端。維護(hù)一個鏈表,保存所有用戶的IP地址,端口信息。接受用戶傳送來的聊天信息,然后向鏈表中的所用用戶轉(zhuǎn)發(fā)。接受用戶傳送來的連接判斷命令,并向用戶發(fā)出響應(yīng)命令??蛻舳斯δ埽嚎蛻舳私缑嫔系膬蓚€文本框,一個用于顯示接受的聊天信息,一個用來接受用戶輸入的聊天信息。當(dāng)按下“發(fā)送”按鈕時將信息發(fā)送給服務(wù)器。概要設(shè)計(jì):服務(wù)器客戶端(設(shè)計(jì)流程圖)具體設(shè)計(jì):服務(wù)器端:1、啟動服務(wù)器代碼://服務(wù)器啟動時,先創(chuàng)建套接字并綁定端口,再監(jiān)聽此端口。voidCSpeakerServerDlg::OnBnClickedStart(){ UINTuPort=GetDlgItemInt(IDC_PORT); //創(chuàng)建套接字 if(!m_TCPSocketListen.Create(uPort)) { m_TraceRichEdit.TraceString(TEXT("綁定監(jiān)聽端口失敗,請確認(rèn)該端口沒有被其它程序占用"),TraceLevel_Warning); return; } //監(jiān)聽套接字 if(!m_TCPSocketListen.Listen()) { m_TraceRichEdit.TraceString(TEXT("監(jiān)聽失敗"),TraceLevel_Warning); return; } UINTuMaxConnect=GetDlgItemInt(IDC_MAX); //設(shè)立接口 m_TCPSocketListen.SetTCPSocketService(this); //更新界面 m_TraceRichEdit.TraceString(TEXT("服務(wù)器啟動成功"),TraceLevel_Normal); GetDlgItem(IDC_START)->EnableWindow(FALSE); GetDlgItem(IDC_STOP)->EnableWindow(TRUE);}2、監(jiān)聽端口,收到連接請求,接受的代碼://先檢查是否在服務(wù)器的最大連接限制內(nèi),若在,則獲取當(dāng)前客戶的IP地址和端口等信息,插入鏈表中。//為什么要限制連接人數(shù)?由于TCP連接是相稱占資源的,若不限制連接人數(shù),服務(wù)器的資源不夠分派。voidCSpeakerServerDlg::OnAccept(){ //承載能力 if(m_TCPSocketItemMap.size()>GetDlgItemInt(IDC_MAX)) { m_TraceRichEdit.TraceString(TEXT("服務(wù)器承載人數(shù)已滿,已過濾其他連接"),TraceLevel_Warning); return; } //綁定套接字 CTCPSocketService*pTCPSocketConnect=newCTCPSocketService; try { SOCKADDR_IN SocketAddr; 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); //綁定數(shù)據(jù) boolbActive=true; CTCPSocketItemMap::iteratoriter=m_TCPSocketItemMap.begin(); for(;iter!=m_TCPSocketItemMap.end();iter++) { if(pTCPSocketConnect->m_hSocket==iter->first) { bActive=false; break; } } //插入客戶數(shù)據(jù) if(bActive) { tagBindParameter*pBindParameter=newtagBindParameter; pBindParameter->pTCPSocketService=pTCPSocketConnect; pBindParameter->dwUserID=0; m_TCPSocketItemMap.insert(pair<SOCKET,tagBindParameter*>(pTCPSocketConnect->m_hSocket,pBindParameter)); } } catch(...) { if(pTCPSocketConnect->m_hSocket!=INVALID_SOCKET) pTCPSocketConnect->Close(); }}3、接受并檢查數(shù)據(jù)的代碼:voidCSpeakerServerDlg::OnReceive(SOCKEThSocket){ BYTEcbDataBuffer[SOCKET_TCP_BUFFER]; CTCPSocketItemMap::iteratoriter=m_TCPSocketItemMap.find(hSocket); if(iter==m_TCPSocketItemMap.end())return; //接受數(shù)據(jù) iter->second->pTCPSocketService->Receive(cbDataBuffer,CountArray(cbDataBuffer)); //解析數(shù)據(jù) TCP_Command*pCommand=(TCP_Command*)cbDataBuffer; //解釋數(shù)據(jù) WORDwPacketSize=pCommand->wPacketSize; WORDwDataSize=wPacketSize-sizeof(TCP_Command); //數(shù)據(jù)包效驗(yàn) if(wPacketSize>SOCKET_TCP_BUFFER+sizeofTCP_Command) { m_TraceRichEdit.TraceString(TEXT("數(shù)據(jù)包太大,已拒絕"),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("收到偽數(shù)據(jù)包或未解決的數(shù)據(jù)包,wMainCmdID:%d,wSubCmdID:%d,來源IP:%d.%d.%d.%d"),pCommand->wMainCmdID,pCommand->wSubCmdID,pClientIP[0],pClientIP[1],pClientIP[2],pClientIP[3]); return; }}4、群發(fā)登錄消息和用戶發(fā)送的消息代碼://服務(wù)器收到客戶的消息之后會將收到的消息發(fā)送給鏈表之中除了發(fā)送客戶之外的所有客戶。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) { //效驗(yàn)數(shù)據(jù) ASSERT(wDataSize==sizeofCMD_CS_LOGON); if(wDataSize!=sizeofCMD_CS_LOGON)returnfalse; //獲取數(shù)據(jù) CMD_CS_LOGON*pUserLogon=(CMD_CS_LOGON*)pData; m_TraceRichEdit.TraceString(TraceLevel_Normal,TEXT("%s登陸服務(wù)器"),pUserLogon->szUserName); tagUserData*pUserData=newtagUserData; //隨機(jī)給用戶分派一個UserID,UserID一般存儲于數(shù)據(jù)庫中,是一個獨(dú)一無二的數(shù)字, //一般在數(shù)據(jù)庫表中設(shè)為主鍵,是整個游戲或者軟件辨認(rèn)用戶的唯一依據(jù),這里我們沒有涉及到數(shù)據(jù)庫,暫時隨機(jī)取一個數(shù)值代替 //另一方面,我們應(yīng)當(dāng)通過數(shù)據(jù)庫SQL語句查詢或者存儲過程等方法,或在數(shù)據(jù)庫中做密碼的效驗(yàn)也好, //或在查詢到用戶的密碼在服務(wù)器中進(jìn)行判斷也好,不管什么方法,此處一般需要進(jìn)行用戶密碼的效驗(yàn),這樣才可以鑒定用戶是否可以登陸了 pUserData->dwUserID=GetTickCount(); _sntprintf_s(pUserData->szUserName,CountArray(pUserData->szUserName),pUserLogon->szUserName); _sntprintf_s(pUserData->szPassWord,CountArray(pUserData->szPassWord),pUserLogon->szPassWord); //更新綁定數(shù)據(jù) CTCPSocketItemMap::iteratoriter=m_TCPSocketItemMap.find(hSocket); if(iter!=m_TCPSocketItemMap.end()) iter->second->dwUserID=pUserData->dwUserID; //群發(fā)登陸消息 SendUserItem(NULL,pUserData); //發(fā)送在線用戶 CUserItemArray::iteratorpUserItemSend=m_pUserManager->GetUserItemArray()->begin(); for(;pUserItemSend!=m_pUserManager->GetUserItemArray()->end();pUserItemSend++) SendUserItem(pTCPSocketService,pUserItemSend->second); //插入數(shù)據(jù) m_pUserManager->InsertUserItem(pUserData); returntrue; } } break; caseMDM_GP_USER: { if(wSubCmdID==SUB_CS_USERT_CHAT) { //獲取數(shù)據(jù) CMD_CS_CHATMSG*pCHATMSG=(CMD_CS_CHATMSG*)pData; //這里其實(shí)需要做很多的效驗(yàn),如dwSendUserID的有效性,字符串是否為空等,這里就不做這些效驗(yàn)了 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、當(dāng)服務(wù)器端有人退出登錄時的代碼://客戶端退出時,服務(wù)器端獲取用戶名并群發(fā)退出消息,再在鏈表中刪除該用戶的數(shù)據(jù),清理他的SocketvoidCSpeakerServerDlg::OnClose(SOCKEThSocket){ CTCPSocketItemMap::iteratoriter=m_TCPSocketItemMap.find(hSocket); if(iter==m_TCPSocketItemMap.end())return; //獲取用戶 m_TraceRichEdit.TraceString(TraceLevel_Normal,TEXT("%s退出了服務(wù)器"),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)); //群發(fā)消息 SendDataBatch(MDM_GP_USER,SUB_SC_DELETE,&_DC_DELETE,sizeof_DC_DELETE); //銷毀數(shù)據(jù) m_pUserManager->RemoveUserItem(iter->second->dwUserID); iter->second->pTCPSocketService->Close(); SafeDelete(iter->second->pTCPSocketService); SafeDelete(iter->second); m_TCPSocketItemMap.erase(iter);}關(guān)閉服務(wù)器連接代碼:voidCSpeakerServerDlg::OnBnClickedStop(){ //關(guān)閉監(jiān)聽套接字 m_TCPSocketListen.Close(); //關(guān)閉連接套接字 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("服務(wù)器關(guān)閉成功"),TraceLevel_Normal); GetDlgItem(IDC_START)->EnableWindow(TRUE); GetDlgItem(IDC_STOP)->EnableWindow(FALSE);}7、退出服務(wù)器代碼:voidCSpeakerServerDlg::OnCancel(){ if(m_TCPSocketListen.m_hSocket!=INVALID_SOCKET) { if(AfxMessageBox(TEXT("擬定退出服務(wù)器嗎?其它所有用戶將失去連接"),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; //關(guān)閉之前socket m_TCPScoketClient.Close(); //初始化套接字 if(!m_TCPScoketClient.Create()) { SetTraceString(TEXT("套接字創(chuàng)建失敗")); SafeDelete(pLogonInfo); returnFALSE; } //創(chuàng)建連接 if(m_TCPScoketClient.Connect(pLogonInfo->szServerAddr,pLogonInfo->nPort)==FALSE) { intnErrorCode=m_TCPScoketClient.GetLastError(); if(nErrorCode!=WSAEWOULDBLOCK) { SetTraceString(TEXT("連接服務(wù)器失敗,錯誤碼:%d"),nErrorCode); SafeDelete(pLogonInfo); returnFALSE; } } //設(shè)立接口 m_TCPScoketClient.SetTCPSocketService(this); //構(gòu)建數(shù)據(jù) CMD_CS_LOGONUserLogon; ZeroMemory(&UserLogon,sizeofUserLogon); _sntprintf_s(UserLogon.szUserName,CountArray(UserLogon.szUserName),pLogonInfo->szUserName); _sntprintf_s(UserLogon.szPassWord,CountArray(UserLogon.szPassWord),pLogonInfo->szPassWord); //發(fā)送登陸請求 m_TCPScoketClient.SendData(MDM_GP_LOGON,SUB_CS_LOGON,&UserLogon,sizeofUserLogon); //設(shè)立界面 SetTraceString(TEXT("連接服務(wù)器成功")); m_LogonDlg.PostMessage(WM_CLOSE); //清理數(shù)據(jù) SafeDelete(pLogonInfo); returnTRUE;}2、客戶端發(fā)送數(shù)據(jù)代碼:voidCSpeakerClientDlg::OnBnClickedSend(){ //設(shè)立數(shù)據(jù) CMD_CS_CHATMSG_UserChat_Msg; ZeroMemory(&_UserChat_Msg,sizeof_UserChat_Msg); GetDlgItemText(IDC_EDITCHAT,_UserChat_Msg.szDescribe,CountArray(_UserChat_Msg.szDescribe)); //效驗(yàn)數(shù)據(jù) if(_UserChat_Msg.szDescribe[0]==TEXT('\0')) { SetTraceString(TEXT("聊天內(nèi)容為空,請先輸入您想說的話")); return; } //發(fā)送數(shù)據(jù) m_TCPScoketClient.SendData(MDM_GP_USER,SUB_CS_USERT_CHAT,&_UserChat_Msg,sizeof_UserChat_Msg);}3、客戶端接受數(shù)據(jù)代碼://客戶端接受數(shù)據(jù)和服務(wù)器段類似,也需解析、檢查voidCSpeakerClientDlg::OnReceive(intnErrorCode){ //接受消息 BYTEcbDataBuffer[SOCKET_TCP_BUFFER]; m_TCPScoketClient.Receive(cbDataBuffer,CountArray(cbDataBuffer)); //解析數(shù)據(jù) TCP_Command*pCommand=(TCP_Command*)cbDataBuffer; //解釋數(shù)據(jù) WORDwPacketSize=pCommand->wPacketSize; WORDwDataSize=wPacketSize-sizeof(TCP_Command); //數(shù)據(jù)包效驗(yàn) if(wPacketSize>SOCKET_TCP_BUFFER+sizeofTCP_Command) { SetTraceString(TEXT("數(shù)據(jù)包太大,已拒絕")); return; } //子消息解決事件 if(!OnEventTCPSocketRead(pCommand->wMainCmdID,pCommand->wSubCmdID,pCommand+1,wDataSize)) { SetTraceString(TEXT("收到未解決的數(shù)據(jù)包,wMainCmdID:%d,wSubCmdID:%d"),pCommand->wMainCmdID,pCommand->wSubCmdID); return; }}4、客戶端消息的顯示代碼://顯示的消息類型:當(dāng)用戶登錄時,將用戶數(shù)據(jù)插入用戶列表中。服務(wù)器端會有xx登錄的顯示。當(dāng)用戶發(fā)消息時,服務(wù)器端就可以轉(zhuǎn)發(fā)該消息給用戶鏈表的所有其他用戶。用戶退出時,同理,客戶端也會接受到XX退出了的消息。boolCSpeakerClientDlg::OnEventTCPSocketRead(WORDwMainCmdID,WORDwSubCmdID,VOID*pData,WORDwDataSize){ switch(wMainCmdID) { caseMDM_GP_LOGON: //登陸消息 { if(wSubCmdID==SUB_SC_USERCOME) //用戶進(jìn)入 { CMD_SC_USERCOME*pUserCome=(CMD_SC_USERCOME*)pData; //插入數(shù)據(jù) if(m_ListUser.FindString(-1,pUserCome->szUserName)==LB_ERR) { //設(shè)立自己信息 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); m_ListUser.SetItemData(m_ListUser.GetCount()-1,pUserCome->dwUserID); } returntrue; } break; } caseMDM_GP_USER: //用戶消息 { if(wSubCmdID==SUB_CS_USERT_CHAT) //聊天消息 { CMD_SC_CHATMSG*pCHATMSG=(CMD_SC_CHATMSG*)pData; //設(shè)立聊天數(shù)據(jù) staticCStringstr; CStringStrDescribe; StrDescribe.Format(TEXT("%s%04d-%02d-%02d%02d:%02d:%02d\r\n"),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("\r\n"); SetDlgItemText(IDC_CHATRECV,str); returntrue; } elseif(wSubCmdID==SUB_SC_DELETE

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論