繼上一篇有關伺服器的網路程式設計,這裡繼續探討客戶端如何發出連線伺服器的請求,如何與伺服器進行資料傳輸,如何與其他客戶端交換資料,最後如何斷開與伺服器之間的連線。
關鍵技術就是TCP/IP協議,socket預設使用的是非阻塞式非同步傳輸通訊方式,對應MFC中的CSoket類,採用的是面向連線的TCP協議而不是UDP協議。
工具/原料
Visual Studio 2010/2013
方法/步驟
建立一個專案,與伺服器端類似,別忘了選擇“windows套接字”。建立好以後設計對話方塊介面。2個按鈕,1個用來連線或者斷開伺服器,另一個用來發送資料;2個編輯框,一個用來顯示接收到的資料,另一個用來輸入需要傳送的資料。
編輯控制元件的屬性,通過類嚮導新增相應的變數,雙擊按鈕新增按鈕按下處理事件。
新增客戶端類CClientSocket,通過類嚮導新增OnReceive函式。
修改客戶端類的標頭檔案:
/********************ClientSocket.h*********************/
#pragma once
class CClientSocket : public CSocket
{
public:
CClientSocket();
virtual ~CClientSocket();
virtual void OnReceive(int nErrorCode);
// 重寫接收函式,通過類嚮導生成
BOOL SendMSG(LPSTR lpBuff, int nlen);
// 傳送函式,用於傳送資料給伺服器
};
修改客戶端類的原始檔:
/********************ClientSocket.cpp*********************/
#include "stdafx.h"
#include "PhoneClient.h"
#include "ClientSocket.h"
#include "PhoneClientDlg.h"
CClientSocket::CClientSocket(){}
CClientSocket::~CClientSocket(){}
void CClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此新增專用程式碼和/或呼叫基類
char* pData = NULL;
pData = new char[1024];
memset(pData, 0, sizeof(char)* 1024);
UCHAR leng = 0;
CString str;
leng = Receive(pData, 1024, 0);
str = pData;
// 在編輯框中顯示接收到的資料
((CPhoneClientDlg*)theApp.GetMainWnd())->SetDlgItemTextW(IDC_DataReceive, str);
delete pData;
pData = NULL;
CSocket::OnReceive(nErrorCode);
}
BOOL CClientSocket::SendMSG(LPSTR lpBuff, int nlen)
{
//生成協議頭
if (Send(lpBuff, nlen) == SOCKET_ERROR)
{
AfxMessageBox(_T("傳送錯誤!"));
return FALSE;
}
return TRUE;
}
說明:當客戶端接收到伺服器端發的資料時會響應接收函式OnReceive,這裡只是簡單的將獲取的資訊顯示在編輯框中。SendMSG函式用於向伺服器傳送訊息,函式會在主對話方塊類中呼叫。
修改對話方塊類的標頭檔案,新增相關函式宣告以及必要的變數定義:
bool m_connect;
CClientSocket* pSock; // 客戶端套接字指標物件
BOOL WChar2MByte(LPCWSTR lpSrc, LPSTR lpDest, int nlen);
//字元轉換函式
public:
virtual BOOL PreTranslateMessage(MSG* pMsg);
//防止按下enter、esc時退出程式
在對話方塊類的原始檔中編寫所有宣告的函式,實現各項函式的功能。
1、 連線伺服器的按鈕事件處理函式
void CPhoneClientDlg::OnBnClickedConnect()
{
if (m_connect) // 如果已經連線,則斷開伺服器
{
m_connect = false;
pSock->Close();
delete pSock;
pSock = NULL;
m_ConPC.SetWindowTextW(_T("連線伺服器"));
UpdateData(false);
return;
}
else // 未連線,則連線伺服器
{
pSock = new CClientSocket();
if (!pSock->Create()) //建立套接字
{
AfxMessageBox(_T("建立套接字失敗!"));
return;
}
}
if (!pSock->Connect(_T("127.0.0.1"), port)) //連線伺服器
{
AfxMessageBox(_T("連線伺服器失敗!"));
return;
}
else
{
m_connect = true;
m_ConPC.SetWindowTextW(_T("斷開伺服器"));
UpdateData(false);
}
}
說明:本函式通過Create和Connect與伺服器建立連線。由於在本機上測試,所以IP為127.0.0.1,實際應用時可以新增一個控制元件用於輸入伺服器的IP。埠號必須與伺服器的一致,這裡的port是一個常量:
#define port 8000
2、 傳送按鈕的事件處理函式
void CPhoneClientDlg::OnBnClickedSend()
{
// TODO: 在此新增控制元件通知處理程式程式碼
if (!m_connect)return; //未連線伺服器則不執行
UpdateData(true); //獲取介面資料
if (m_DataSend != "")
{
char* pBuff = new char[m_DataSend.GetLength() * 2];
memset(pBuff, 0, m_DataSend.GetLength() * 2);
WChar2MByte(m_DataSend.GetBuffer(0), pBuff, m_DataSend.GetLength() * 2);
pSock->SendMSG(pBuff, m_DataSend.GetLength() * 2);
}
}
說明:這裡的SendMSG函式與伺服器端的不一致,函式實體在CClientSocket類中以實現。WChar2MByte字元轉換函式與伺服器端的一致,在這不再贅述。
對話方塊的主要函式就是以上2個,一個用於連線和斷開伺服器,另一個用於傳送資料。至於虛擬函式PreTeanslateMessage,其處理方法與伺服器中介紹的一致。將伺服器與客戶端都編寫好以後,就可以測試通訊效果,可以同時開啟多個客戶端,看看伺服器如何處理。
注意事項
這篇經驗需要參考上一篇關於伺服器端程式設計的經驗
伺服器端與客戶端通過socket實現網路通訊