一、概述
本系統為內部系統,帳戶由管理員添加、管理;
分為兩個組,User組和Boss組。Boss組的帳戶可以發廣播通知;
任意兩個用戶間可以互相通信;
數據庫接口用DAO,網絡通信用 CSocket+CSocketFile;
二、詳細設計
1、數據庫設計
本系統只是一個消息通信模型,這裡的數據庫設計比較簡單。
ER圖:
把ER模型轉為關系模型,共兩個表:
User (No , Name ,Password ,G#) 候選鍵:No 外鍵:G#
Group (G# , GroupName ,Demo ) 主鍵 :G#
2、消息格式設計
<1>、傳送的消息共有5類------登錄消息,驗證返回消息,普通消息,用戶列表消息,通知消息。定義一個枚舉類型:
enum MSGTYPE {LOGIN , LOGINResponse , CHATTING , USERList , NOTICE};
<2>、定義消息類
class CMsg : public CObject
{
public:
int m_eType; //枚舉類型,記錄消息類型
CString m_strMsg; //消息
CMsg();
virtual ~CMsg();
void Serialize(CArchive &ar); //消息類系列化函數,發送和接受消息時用。
};
<3>、m_strMsg 為消息類中存放消息的成員,它的具體格式隨著消息類型m_eType不同而不同。
m_eType m_strMsg LOGIN 呢稱|密碼 LOGINResponse GOOD|歡迎!(BOSS) 或 FAILED|驗證失敗! CHATTING 發給(來自)的用戶名|消息內容 USERList 呢稱1|呢稱2|…|呢稱n|END NOTICE ALL|消息內容 或 來自的用戶|消息內容
m_strMsg中個內容用“|”隔開,用函數Decode(int n,CString strMsg) 獲的相應的內容。
CString Decode(int n,CString strMsg)
{
int pos;
CString str;
for(int i=1;i<=n;i++)
{
pos=strMsg.Find ("|",0);
if(pos<0)
str=strMsg;
else
str=strMsg.Left (pos);
strMsg=strMsg.Right(strMsg.GetLength ()-pos-1);
}
return str;
}
<4>、消息發送接收的序列化函數
void CMsg::Serialize (CArchive &ar)
{
if(ar.IsStoring())
{
ar<<m_strMsg<<m_eType;
}
else
{
ar>>m_strMsg>>m_eType;
}
}
3、通信協議設計
驗證。客戶端發送LOGIN消息,服務器回應 LOGINResponse消息;
通信。客戶端發送CHATTING 或 NOTICE 消息,服務器端根據接收到的消息,發送CHATTING,NOTICE或 USERList 消息。
4、服務器設計
<1>、建立工程
①、用MFC AppWizard(exe) 新建一個“單個文檔”的工程;
②、在Step 2 of 6中,選 “查看數據庫不使用文件支持”,點擊“Data Source..”按鈕,然後在彈出的對話框中選 “DAO”類型,再浏覽選擇數據庫文件Data.mdb. 按確定,再在彈出的窗口選User表;
③、點擊Next到Step 4 of 6,選“windows Sockets”。網絡功能支持;
④、按“NEXT“,最後點擊 “完成”;
<2>、在CSuperServerView中添加下列成員:int m_iPort;//服務器端口
<3>、CSuperServerView類的關鍵成員函數:
CSocketListen * m_pSocket; //監聽套接字。
CSocketClient m_SocketClient[Max]; //跟客戶端通信的套接字。
CArchive * m_pArOut; //發送消息時的序列化文檔對象指針。
CArchive * m_pArIn; //接收消息時的序列化化文檔對象指針。
CSocketFile * m_pSF; //套接字文件對象指針。
CMsg msg;//消息類對象
CString Decode(int n,CString strMsg); //消息的解碼函數
void SendUserList();
bool CheckLogin(CSocketClient *pClient);
void MyReceive(CSocketClient *pClient);
void MyClose(CSocketClient *pClient);
void MyAccept();
void CSuperServerView::MyReceive(CSocketClient *pClient)
{
m_pSF=new CSocketFile(pClient);
m_pArIn=new CArchive(m_pSF,CArchive::load);
msg.Serialize (*m_pArIn);
int i;
bool bOK=false;
switch(msg.m_eType )
{
case LOGIN: //處理用戶登錄。
{
m_pSF=new CSocketFile(pClient);
m_pArOut=new CArchive(m_pSF,CArchive::store);
msg.m_eType =LOGINResponse;
if(CheckLogin(pClient))
{
if(!pClient->m_bBoss)
{
msg.m_strMsg="GOOD|歡迎!";
bOK=true;
}
else
{
msg.m_strMsg="GOOD|BOSS";
bOK=true;
}
else
{
msg.m_strMsg="FAILED|驗證失敗!";
}
msg.Serialize (*m_pArOut);
m_pArOut->Flush ();
if(bOK) SendUserList();
break;
}
case CHATTING: //處理普通消息
{
for(i=0;i<Max;i++)
{
if(m_SocketClient[i].m_bBusy)
{
if(m_SocketClient[i].m_strName ==Decode(1,msg.m_strMsg))
{
m_pSF=new CSocketFile(&m_SocketClient[i]);
m_pArOut=new CArchive(m_pSF,CArchive::store);
msg.m_strMsg=pClient->m_strName+"|"+Decode(2,msg.m_strMsg);
msg.Serialize (*m_pArOut);
m_pArOut->Flush ();
break;
}
}
}
break;
}
case NOTICE: //處理廣播消息。
{
msg.m_strMsg=pClient->m_strName+"|"+Decode(2,msg.m_strMsg);
for(i=0;i<Max;i++)
{
if(m_SocketClient[i].m_bBusy &&
m_SocketClient[i].m_strName != pClient->m_strName)
{
m_pSF=new CSocketFile(&m_SocketClient[i]);
m_pArOut=new CArchive(m_pSF,CArchive::store);
msg.Serialize (*m_pArOut);
m_pArOut->Flush ();
}
}
break;
}
}
}
void CSuperServerView::MyAccept()
{
for(int i=0;i<Max;i++)
{
if(!m_SocketClient[i].m_bBusy)
{
m_pSocket->Accept (m_SocketClient[i]);
m_SocketClient[i].GetView (this);
break;
}
}
}
void CSuperServerView::OnStartServer() //開始服務
{
m_pSocket=new CSocketListen(this);
m_pSocket->Create (m_iPort,SOCK_STREAM);
m_pSocket->Listen ();
m_staState.SetWindowText("正在監聽......");
}
void CSuperServerView::OnStopServer() //關閉服務
{
if(m_pSocket) m_pSocket->Close ();
for(int i=0;i<Max;i++)
{
if(m_SocketClient[i].m_bBusy)
{
m_SocketClient[i].Close();
m_SocketClient[i].m_bBusy=false;
}
}
m_staState.SetWindowText("服務關閉");
}
數據庫維護操作函數:
void CSuperServerView::OnButtonAdd() //添加帳戶
{
CAddDlg dlg;
if(dlg.DoModal()==IDOK)
{
if(dlg.m_strName!="")
{
m_pSet->AddNew ();
m_pSet->m_Name=dlg.m_strName;
m_pSet->m_Password=dlg.m_strPwd;
m_pSet->m_G_=dlg.m_iG;
m_pSet->Update ();
UpdateData(FALSE);
}
}
}
void CSuperServerView::OnButtonDel() //刪除帳戶
{
m_pSet->Delete ();
m_pSet->MoveNext ();
if(m_pSet->IsEOF ())
m_pSet->MoveFirst();
UpdateData(FALSE);
}
void CSuperServerView::OnButtonModify() //修改帳戶信息
{
CAddDlg dlg;
dlg.m_strName=m_pSet->m_Name;
dlg.m_strPwd=m_pSet->m_Password;
dlg.m_iG=m_pSet->m_G_;
if(dlg.DoModal()==IDOK)
{
if(dlg.m_strName!="")
{
m_pSet->Edit ();
m_pSet->m_Name=dlg.m_strName;
m_pSet->m_Password=dlg.m_strPwd;
m_pSet->m_G_=dlg.m_iG;
m_pSet->Update ();
UpdateData(FALSE);
}
}
}
void CSuperServerView::OnButtonFind() // 查找帳戶
{
CFindDLG dlg;
if(dlg.DoModal()==IDOK)
{
if(dlg.m_strKey!="")
{
CString m_strName;
UpdateData(TRUE);
m_strName=dlg.m_strKey ;
if(m_pSet->IsOpen ())
m_pSet->Close ();
m_pSet->Open(AFX_DAO_USE_DEFAULT_TYPE,"SELECT * FROM user where Name=''"+m_strName+"''");
UpdateData(FALSE);
}
}
}
CSocketListen類中的接受事件函數OnAccept(int nErrorCode)。
void CSocketListen::OnAccept(int nErrorCode)
{
m_pView->MyAccept ();
CSocket::OnAccept(nErrorCode);
}
CSocketClient類中的接收消息函數。
void CSocketClient::OnClose(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
m_pView->MyClose(this);
CSocket::OnClose(nErrorCode);
}
CSocketClient類傳遞主窗口指針函數:
void CSocketClient::GetView(CSuperServerView *pView)
{
m_pView=pView;
}
<4>、程序界面
5、客戶端設計。
<1>、建立一個名為Client,基與對話框的應用程序,在Step 2 of 6中選Windows Sockts支持,
<2>、在CClientDlg中添加成員。
CString Decode(int n,CString strMsg);
在CMySocket類中添加成員。
CMsg msg;
CMySocket * m_pSocket;
CArchive * m_pArOut;
CArchive * m_pArIn;
CSocketFile * m_pSF;
void MyReceive();CClientDlg * m_pDlg;
構造函數實現,獲得指向主對話框的指針
CMySocket(CClientDlg *pDlg);
CMySocket::CMySocket(CClientDlg *pDlg)
{
m_pDlg=pDlg;
}
<3>、關鍵函數
BOOL CClientDlg::OnInitDialog()
{
...
m_strHost="192.168.1.126";
m_iPort=1234;
UpdateData(FALSE);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(false);
Expand(0);
return TRUE; // return TRUE unless you set the focus to a control
}
void CClientDlg::OnLogin() //登錄函數
{
UpdateData(TRUE);
GetDlgItem(IDC_BUTTON1)->SetWindowText("Wait");
m_pSocket=new CMySocket(this);
m_pSocket->Create();
int nTry=3,n;
do{
n=m_pSocket->Connect (m_strHost,m_iPort);
}while(n!=1 && nTry--);
if(n==1)
{
Sleep(2000);
m_pSF=new CSocketFile(m_pSocket);
m_pArOut=new CArchive(m_pSF,CArchive::store);
m_pArIn=new CArchive(m_pSF,CArchive::load);
msg.m_eType =LOGIN;
msg.m_strMsg =m_strName+"|"+m_strPwd;
msg.Serialize (*m_pArOut);
m_pArOut->Flush();
}
else
{
GetDlgItem(IDC_BUTTON1)->SetWindowText("Login");
GetDlgItem(IDC_BUTTON1)->EnableWindow(true);
AfxMessageBox("網絡原因,沒連上服務器!");
}
}
void CClientDlg::MyReceive() //接收消息函數
{
msg.Serialize (*m_pArIn);
int i=0;
CString str;
switch(msg.m_eType )
{
case LOGINResponse:
str=Decode(1,msg.m_strMsg);
if(str=="FAILED")
{
str=Decode(2,msg.m_strMsg);
m_pSocket->Close();
AfxMessageBox(str);
}
else
{
Expand(true);
GetDlgItem(IDC_BUTTON1)->SetWindowText("Login");
GetDlgItem(IDC_BUTTON1)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(true);
}
break;
case USERList:
str=Decode(++i,msg.m_strMsg);
m_ctrList.ResetContent();
while(str!="END")
{
if(str!=m_strName)
m_ctrList.AddString(str);
str=Decode(++i,msg.m_strMsg);
}
UpdateData(FALSE);
break;
case CHATTING:
MessageBox(Decode(2,msg.m_strMsg),"來自"+Decode(1,msg.m_strMsg)+"的消息:");
break;
case NOTICE:
MessageBox(Decode(2,msg.m_strMsg),"來自"+Decode(1,msg.m_strMsg)+"的通知:");
break;
}
}
CMySocket類中的事件函數
OnReceive(int nErrorCode)。
void CMySocket::OnReceive(int nErrorCode)
{
m_pDlg->MyReceive (); //調用主窗口的接收函數
CSocket::OnReceive(nErrorCode);
}
<4>、程序界面
①、登錄界面
②、登錄成功(左為User組用戶登錄後界面,右為Boss組的)
③、發送消息
④、接收消息
三、結束語
這是一個基於網絡和數據庫的系統模型,有點學習的價值。程序還有一些功能沒完善的地方,並且存在一些Bug。
本文配套源碼