?? 15.6.3 實(shí)現(xiàn)接收端功能.txt
字號(hào):
15.6.3 實(shí)現(xiàn)接收端功能
接下來,編寫接收端程序。因?yàn)楫?dāng)在接收端接收數(shù)據(jù)時(shí),如果沒有數(shù)據(jù)到來, recvfrom 函數(shù)會(huì)阻
塞,從而導(dǎo)致程序暫停運(yùn)行。所以,我們可以將接收數(shù)據(jù)的操作放置在一個(gè)單獨(dú)的線程中完成,井
給這個(gè)線程傳遞兩個(gè)參數(shù),一個(gè)是己創(chuàng)建的套接字,一個(gè)是對(duì)話框控件的句柄,這樣,在該線程中,
當(dāng)接收到數(shù)據(jù)后,可以將該數(shù)據(jù)傳回給對(duì)話框,經(jīng)過處理后顯示在接收編輯框控件上。我們可以回
頭看看 CreateThread函數(shù)的聲明,發(fā)現(xiàn)該函數(shù)只為我們提供了一個(gè)參數(shù),即該函數(shù)的第四個(gè)參數(shù),
用來向創(chuàng)建的線程傳遞參數(shù)。而現(xiàn)在我們需要傳遞兩個(gè)參數(shù),這應(yīng)如何實(shí)現(xiàn)呢?"山窮水盡疑無路,
柳暗花明又一村",我們可以看到 CreateThread函數(shù)的第四個(gè)參數(shù)是指針類型,既然是一個(gè)指針,
那么它既可以是一個(gè)指向變量的指針,也可以是一個(gè)指向?qū)ο蟮闹羔槪@樣,我們可以定義一個(gè)結(jié)
構(gòu)體,在該結(jié)構(gòu)體中包含想要傳遞給線程的兩個(gè)參數(shù),然后將該結(jié)構(gòu)體類型的指針變量傳遞給
CreateThread函數(shù)的第四個(gè)變量。
我們先在 CChatDialog類的頭文件中,在該類的聲明的外部定義一個(gè)阻RECVPARAM結(jié)構(gòu)體,代碼如例
15-11所示。
例15-11
struct RECVPARAM
{SOCKET sock; //己創(chuàng)建的套接字
HWND hwnd; //對(duì)話框句柄
};
在該結(jié)構(gòu)體中,定義了兩個(gè)成員,一個(gè)是 SOCKET類型的成員: sock,另一個(gè)是HWND類型的成員: hwnd。
提示:定義結(jié)構(gòu)體時(shí),最后一定要在其后加上分號(hào)。
然后在 CChatDialog類的 OnInitDialog函數(shù)中,在上面剛剛添加的 InitSocket函數(shù)調(diào)用(在上述如
例 15-10所示代碼中加灰顯示的那行代碼)后面添加下述代碼,以完成數(shù)據(jù)接收線程的創(chuàng)建,并傳遞
所需的參數(shù)。
例 15-12
RECVPARAM *pRecvParam=new RECVPARAM;
pRecvParam->sock=m_socket;
pRecvParam->hwnd=m_hWnd;
//創(chuàng)建接收線程
HANDLE hThread=CreateThread(NULL, 0, RecvProc , (LPVOID)pRecvParam, 0, NULL) ;
//關(guān)閉該接收線程句柄,釋放其引用計(jì)數(shù)
CloseHandle(hThread) ;
在上述例 15-12所示代碼中,首先定義了一個(gè)阻CVPARAM結(jié)構(gòu)指針: pRecvParam, 并利用 new操作符
為該變量分配空間。然后,對(duì)該結(jié)構(gòu)體變量中的兩個(gè)成員進(jìn)行初始化,將 sock成員設(shè)置為己創(chuàng)建的
套接字,將 hwnd成員設(shè)置為對(duì)話框的句柄。在前面章節(jié)已經(jīng)講過,所有與窗口有關(guān)的類都有一個(gè)數(shù)
據(jù)成員: m_hWnd,它保存了與該類相關(guān)的窗口的句柄。因此, CChatD ialog類的成員變量 mJ1W_hWnd
就是該對(duì)話框的句柄。
接下來,就調(diào)用 CreateThread函數(shù)創(chuàng)建數(shù)據(jù)接收線程,其中第四個(gè)參數(shù)就是主線程要向數(shù)據(jù)接收線
程傳遞的參數(shù),因?yàn)樵搮?shù)的類型是 LPVOID,而我們將要傳遞的卻是 RECVPARAM指針類型,所以需
要進(jìn)行強(qiáng)制轉(zhuǎn)換。
下面,我們要完成數(shù)據(jù)接收線程入口函數(shù)的編寫。我們可以按照本章前面介紹的方法,把線程入口
函數(shù)定義為一個(gè)全局函數(shù),但是在實(shí)際工作中,有些單位要求在面向?qū)ο蟮木幊讨胁荒苁褂萌趾?數(shù),所有的數(shù)據(jù)成員和方法都必須封裝到類中,那么是否可以將線程函數(shù)定義為類的成員函數(shù)呢?
我們可以試驗(yàn)一下,按照前面介紹的線程入口函數(shù)的寫法為 CChatDialog類增加一個(gè)成員函數(shù):
RecvProc,并在此函數(shù)中添加一條簡(jiǎn)單的 return語句,結(jié)果如例 15-13所示。
例 15-13
DWORD WINAPI CChatDlg : : RecvProc (LPVOID lpPararneter)
{
return 0 ;
然后編譯 Chat程序,編譯器將報(bào)告如下錯(cuò)誤:
D:\VC++深入編程\Chapter15 \Chat\ChatDlg.cpp(123) : error C2664: 'CreateThread':
cannot convert pararneter 3 frorn 'unsigned long (void *)' to 'unsigned long ( stdcall
*) (void *)'
None of the functions with this name in scope match the target type
該錯(cuò)誤是說, CreateThread函數(shù)不能將第三個(gè)參數(shù)從 unsigned long (void *)類型轉(zhuǎn)換為
unsigned long Lstdcall *)(void *)類型。因?yàn)楫?dāng)創(chuàng)建線程時(shí),系統(tǒng),即運(yùn)行時(shí)代碼會(huì)調(diào)用線程函
數(shù)來啟動(dòng)線程。因?yàn)檫@里的線程函數(shù)是 CChatDialog類的成員函數(shù),為了調(diào)用這個(gè)函數(shù),必須先產(chǎn)
生一個(gè) CChatDialog類的對(duì)象,然后才能調(diào)用該對(duì)象內(nèi)部的成員函數(shù)。然而對(duì)于運(yùn)行時(shí)代碼來說,
它如何知道要產(chǎn)生哪一個(gè)對(duì)象呢?也就是說,運(yùn)行時(shí)根本不知道如何去產(chǎn)生一個(gè) CChatDialog類的對(duì)
象。對(duì)于運(yùn)行時(shí)代碼來說,如果要調(diào)用線程函數(shù)來啟動(dòng)某個(gè)線程的話,應(yīng)該不需要產(chǎn)生某個(gè)對(duì)象就
可以調(diào)用這個(gè)線程函數(shù)。然而這里我們錯(cuò)誤地將這個(gè)線程函數(shù)定義為類的成員函數(shù),所以就出錯(cuò)了。
可以這樣來解決這個(gè)問題:將線程函數(shù)聲明為類的靜態(tài)函數(shù),即在 CChatDialog類的頭文件中,在
RecvProc函數(shù)的聲明的最前面添加關(guān)鍵詞: static,這時(shí)該函數(shù)的聲明代碼如下所示 :
static DWORD WINAPI RecvProc(LPVOID lpParameter);
因?yàn)閷?duì)類的靜態(tài)函數(shù)而言,它不屬于該類的任一個(gè)對(duì)象,它只屬于類本身。所以在 CChatDialog類
的 OnlnitDialog函數(shù)中創(chuàng)建線程時(shí),運(yùn)行時(shí)代碼就可以直接調(diào)用 CChatDialog類的靜態(tài)函數(shù),從而
啟動(dòng)線程。這時(shí)再次編譯 Chat程序,將會(huì)看到程序成功通過。
通過上例說明,如果單位要求采用完全面向?qū)ο蟮乃枷雭砭幊蹋簿褪钦f,不能使用全局函數(shù)和全
局變量了,我們就可以采用靜態(tài)成員函數(shù)和靜態(tài)成員變量的方法來解決上述問題。
下面,為 RecvProc這個(gè)線程入口函數(shù)添加實(shí)現(xiàn)代碼以完成接收數(shù)據(jù)的功能,結(jié)果如例 15-14所示。
例 15-14
DWORD WINAPI CChatDlg : : RecvProc (LPVOID lpParameter)
{
//獲取主線程傳遞的套接字和窗口句柄
SOCKET sock=((RECVPARAM*)lpParameter)->sock;
HWND hwnd=( (RECVPARAM*)lpParameter)->hwnd ;
delete lpParameter;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR) ;
char recvBuf[200] ;
char tempBuf[300] ;
int retval;
while (TRUE)
//接收數(shù)據(jù)
retval=recvfrom(sock, recvBuf , 200 , 0, (SOCKADDR*)&addrFrom, &len);
if(SOCKET_ERROR==retval)
break;
sprintf(tempBuf , "%s說: %s" , inet_ntoa (addrFrom. sin_addr) , recvBuf);
::PostMessage(hwnd,WM_RECVDATA , 0, (LPARAM}tempBuf) ; return 0 ;
在 Recv Proc這個(gè)線程函數(shù)中,首先取出主線程傳遞來的參數(shù),這時(shí)應(yīng)將參數(shù) IpParameter轉(zhuǎn)換為
RECVPARAM*類型,然后再訪問該結(jié)構(gòu)體中的成員。
接下來就可以調(diào)用 recvfrom函數(shù)接收數(shù)據(jù)了。為了讓接收線程能夠不斷地運(yùn)行,線程函數(shù) RecvProc
進(jìn)行了一個(gè) while循環(huán),在此循環(huán)中不斷地調(diào)用 recvfrom函數(shù)接收數(shù)據(jù),如果該函數(shù)調(diào)用失敗,將
返回 SOCKET ERROR值,那么這時(shí)就調(diào)用 break語旬,終止 while循環(huán):否則返回接收到的字節(jié)數(shù),
這時(shí),可以對(duì)接收到的數(shù)據(jù)進(jìn)行格式化,并將格式化后的數(shù)據(jù)傳遞給對(duì)話框,因?yàn)槲覀円呀?jīng)將對(duì)話
框句柄作為參數(shù)傳遞給線程了,所以可以采用發(fā)送消息的方式將數(shù)據(jù)傳遞給對(duì)話框。本例調(diào)用
PostMessage函數(shù)向?qū)υ捒虬l(fā)送一條自定義的消息: WM_RECVDATA,將參數(shù) wParam設(shè)置為 0,而將需
要顯示的數(shù)據(jù)作為lParam參數(shù)傳遞,并將該數(shù)據(jù)轉(zhuǎn)換為參數(shù) LParam需要的類型 : LP.ARAM。然后在
CChatDLg類的頭文件中,定義 WM RECVDATA這個(gè)消息的值,即在該頭文件中添加下面這條語句:
#define WM_RECVDATA WM_USER+1
并在 CChatDlg類的頭文件中編寫該消息響應(yīng)函數(shù)原型的聲明,即添加如例 15-15所示代碼中加灰顯
示的那條代碼。讀者應(yīng)注意,前面章節(jié)曾介紹過,在發(fā)送消息時(shí),如果不需要傳遞參數(shù),那么在定
義消息響應(yīng)函數(shù)時(shí)可以沒有參數(shù)。因?yàn)楸纠诎l(fā)送 WM RECVDATA消息時(shí)需要傳遞一個(gè)參數(shù),所以在
定義該消息響應(yīng)函數(shù)時(shí)應(yīng)帶有 wParam和 IParam參數(shù)。
例 15-15
II Generated message map functions
11{{AFX_MSG(CChatDlg}
virtual BOOL OnlnitDialog(} ;
afx_msg void OnSysCommand(U工 NT nID, LPARAM lParam} ;
afx_msg void Onpa工 nt ( ) ;
afx_msg HCURSOR OnQueryDraglcon(};
II }}AFX_MSG
afx_msg void OnRecvData(WPARAM wParam, LPARAM lparam}; DECLARE_MESSAGE_MAP(}
接下來,在 CChatDIg類的源文件中添加 WM RECVDATA消息映射,即添加如例 15-16所示代碼中加灰
顯示的那條代碼,注意該代碼后不要添加任何標(biāo)點(diǎn)符號(hào)。這里再次提醒讀者,因?yàn)?CAboutDl g類和
CChatDialog類這兩個(gè)類的實(shí)現(xiàn)代碼在同一個(gè)源文件中,所以一定要看仔細(xì),注意消息映射的位置,
不能寫錯(cuò)位置了。
BEGIN_MESSAGE_MAP(CChatDlg , CDialog} 11{{AFX_MSG_MAP(CChatDlg}
ON_WM_SYSCOMMAND()
ON_WM_PAINT ()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_RECVDATA, OnRecvData)
END_MESSAGE_MAP()
最后就是該消息響應(yīng)函數(shù)的實(shí)現(xiàn),具體代碼如例 15-17所示。
例15-17
//接收數(shù)據(jù)消息響應(yīng)函數(shù)
void CChatDlg : : OnRecvData(WPARAM wParam, LPARAM lParam)
//取出接收到的數(shù)據(jù)
CString str=(char*)lParam;
CString strTemp;
//獲得己有數(shù)據(jù)
GetDlgItemText(IDC_EDIT_RECV, strTemp) ;
str+="\r\n" ;
str+=strTemp;
//顯示所有接收到的數(shù)據(jù)
SetDlgItemText(IDC_ EDIT_RECV, str) ;
每當(dāng)接收到新的數(shù)據(jù)時(shí),應(yīng)在對(duì)話框中接收編輯框的第一行顯示該數(shù)據(jù),而以前的數(shù)據(jù)應(yīng)依次向下
移動(dòng)。在例 15-17所示OnRecvData函數(shù)中,首先定義了一個(gè)CString類型的變量:str,用來保存從消
息響應(yīng)函數(shù)的lParam參數(shù)取出的數(shù)據(jù),即當(dāng)前接收到的新數(shù)據(jù)。接著又定義了一個(gè) CString類型的
變量: strTemp,用來保存從編輯框控件中獲得的己有文本,即以前接收到的數(shù)據(jù)。該文本的獲得可
以通過調(diào)用GetDlgltemText函數(shù)來實(shí)現(xiàn)。接下來為新收到的數(shù)據(jù)加上換行符:"\r\n",并加上先前己
有的數(shù)據(jù),然后調(diào)用 SetDlgItemText函數(shù),將處理后的數(shù)據(jù)放回到接收數(shù)據(jù)的編輯框中顯示出來。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -