任何通信協議的格式都是由命令和附帶數據兩部分組成,包括網絡通信協議和硬件通信協議。命令號也叫協議號或協議編號,就是用一個數字或字符串通知對方執行一個對應的命令。大部分協議在發送命令號後還要發送附帶數據,例如,登錄協議要附帶賬號和密碼等數據。
創建工程名爲“s”和“c”的兩個對話框程序,演示 C/S 結構的網絡版信息管理軟件開發過程。客戶端通過 TCP 對服務器上的數據進行增、刪、改、查等功能。在應用程序嚮導的第 2 頁,選中“WIndows Sockets”複選框。如果沒有選擇該選項,那麼可以在 stdafx.h 中包含“afxsock.h”頭文件,並在 App類的 InitInstance 函數中調用 AfxSocketInit 函數。
服務器程序設計
1.在服務器“s”工程的 CSApp 類頭文件中,添加一個用於數據管理的鏈表變量。
//此處枚舉類型定義和結構體類型定義均爲全局定義。不屬於類的數據類型
enum
{
//定義協議編號
INF_ADD = 0x1234,
INF_BROW,
INF_DEL,
INF_MOD,
INF_FIND
};
struct SData
{
int nNumb;
char sName[20];
float fSala;
};
#include<afxtempl.h>
class CSApp:public CWinApp
{
public:
CList<SData, SData>m_list;
...
2.在“s”工程中創建兩個 CSSocket 類的派生類 CListenSocket 和 CClientSocket。
3.在偵聽類(CListenSocket)中添加虛函數 OnAccept,用於截獲客戶端的鏈接。
#include "ClientSocket.h"
void CListenSocket::OnAccept(int nErrorCode)
{
//新建一個CClientSocket對象,與新連接進入的客戶端關聯
CClientSocket* pSock = new CClientSocket;
if (!Accept(*pSock))
delete pSock;
CSocket::OnAccept(nErrorCode);
}
4.在應答類(CClientSocket)中添加虛函數 OnClose,在客戶端連接斷開後及時清理堆空間。
void CClientSocket::OnClose(int nErrorCode)
{
//清理對象所佔用的堆空間
delete this;
CSocket::OnClose(nErrorCode);
}
5.在應答類(CClientSocket)中添加虛函數 OnReceive,用於接收和解析客戶端協議。
void CClientSocket::OnReceive(int nErrorCode)
{
int nCmd = 0, nLen = 0;
if (Receive(&nCmd, sizeof(nCmd)) <= 0)
return;
switch ()
{
case INF_ADD:
AddData();
break;
case INF_BROW:
Browse();
break;
}
CSocket::OnReceive(nErrorCode);
}
6.在CClientSocket 類中,添加兩個私有函數AddData 和 Browse 作爲協議處理函數。
extern CSApp theApp;
void CClientSocket::AddData()
{
//添加數據的協議處理
SData data;
if (Receive(&data, sizeof(data) <= 0))
return;
theApp.m_list.AddTail(data);
}
void CClientSocket::Browse()
{
//瀏覽數據協議處理,先發送總數再逐條發送具體數據
int nCount = theApp.m_list.GetCount();
Send(&nCount, sizeof(nCount));
POSITION pos = theApp.m_list.GetHeadPosition();
while (pos)
{
SData data = theApp.m_list.GetNext(pos);
Send(&data, sizeof(data));
}
}
7.在主對話框類的頭文件中,添加一個 CListenSocket 類型的成員變量。
#include "ListenSocket.h"
class CSDlg:public CDialog
{
CListenSocket m_sock;
...
8.修改主對話框初始化函數的代碼。
BOLL CSDlg:OnInitDialog()
{
CDialog::OnInitDialog();
if(m_sock.Create(8668))
m_sock.Listen();
else
{
CString str;
str.Format("Socket Create Error:%d", GetLastError());
AfxMessageBox(str);
}
}
9.編譯並運行,測試服務器端代碼。
客戶端代碼編寫
1.在客戶端“c”工程的主對話框中添加一些控件, 如下圖
2.修改控件屬性,給控件取 ID 值
3.通過類嚮導爲列表控件建立控件型關聯變量,CListCtrl m_list;
4.在主對話框的頭文件中,添加一個 CSocket 類型的成員變量,以及通信數據和協議定義。
enum
{
//定義協議編號
INF_ADD = 0x1234,
INF_BROW,
INF_DEL,
INF_MOD,
INF_FIND
};
struct SData
{
int nNumb;
char sName[20];
float fSala;
};
class CCDlg:public CDialog
{
public:
CSocket m_sock;
...
5.修改主對話框初始化函數的代碼。
BOOL CCDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_sock.Create();
if(!m_sock.Connect("10.27.10.3", 8668))
{
AfxMessageBox(" 連接失敗:無法連接服務器,請檢查你的網絡連接!");
EndDialog(IDCANCEL);
return FALSE;
}
m_lisr.InsertColumn(0, "工號", 0, 100);
m_lisr.InsertColumn(1, "姓名", 0, 100);
m_lisr.InsertColumn(2, "工資", 0, 100);
OnRefresh();
...
6.在主對話框中添加一個私有成員函數 OnFresh,用於從服務器接收數據來刷新列表。
void CCDlg::OnFresh()
{
//發送完協議編號後,先接收數據總數在逐條接收每條具體數據
int nCmd = INF_BROW;
m_sock.Send(&nCmd, sizeof(nCmd));
int nCount = 0, i = 0;
m_sock.Receive(&nCount, sizeof(nCount));
m_list.DeleteAllItems();
SData Data;
CString str;
while(i < nCouunt)
{
//從服務器端接收每條數據,並將內容插入到列表控件中顯示
m_sock.Receive(&data, sizeof(data));
str.Format("%d", data.nNumb);
m_list.InsertItem(i, str);
m_list.SetItemText(i, 1, data.sName);
str.Format("%0.2f", data.fSala);
m_list.SetItemText(i, 2, str);
++i;
}
}
7.建立“添加”按鈕的消息映射函數 OnAdd。
void CCDlg::OnAdd()
{
//先發送協議編號,再發送一條信息數據到服務器端
int nCmd = INF_ADD;
m_sock.Send(&nCmd, sizeof(nCmd));
SData data = {GetDlgItemInt(IDC_NUMB);}
GetDlgItemText(IDC_NAME, data.sName, sizeof(data.sName));
CSTring str;
GetDlgItemText(IDC_SALA, str);
data.fSala = (float)atof(str);
m_sock.Send(&data, sizeof(data));
OnRefresh();
}
//int GetDlgItemText( HWND hDlg , int nID, LPTSTR lpStr, int nMaxCount) const;
//int GetDlgItemText( int nID, CString& rString) const;
/*
UINT GetDlgItemInt(
int nID,
BOOL* lpTrans = NULL,
BOOL bSigned = TRUE
) const;
int i = GetDlgItemInt(IDC_EDIT_TEMPERATURE, NULL, 1); //若看做有符號數,則bSigned爲1,返回值直接以int類型去接收
UINT j = GetDlgItemInt(IDC_EDIT_TEMPERATURE, NULL, 0); //若看做無符號數,則bSigned爲0,返回值按照UINT類型來接收
*/
8.編譯並運行,測試服務器端和客戶端代碼。