进程间通信 - 邮槽实现
引子
前面的一篇博文介紹了進程之間通信的一種最為簡單的方式,
也就是在本地進程之間通過剪貼板來實現進程間通信,而剪貼板自有其缺陷,
很顯然的是,剪貼板只能在本地機器上實現,
無法實現本地進程與遠程服務器上的進程之間的通信,
那么有沒有辦法實現本地進程和遠程進程的通信呢?
辦法自然是有的,要是實在搞不出,
我拿?Socket?來實現本地進程和遠程進程的通信來實現也是可以的,
但是你想啊,要用?Socket?來實現本地進程和遠程進程之間的通信,
那不僅我要在本地進程中加一堆的?Socket?代碼,
并且服務器上的進程中也是需要加一堆的?Socket?代碼的,
那不搞死人去,也太麻煩了吧,所以不行不行,得換一種方案。
下面就來介紹一種超級無敵簡單的方案,其可以用來實現本地進程與遠程進程之間的通信,
那就是通過郵槽來實現。
???????????
????????????
郵槽定義
郵槽(Mailslot)也稱為郵件槽,其是 Windows 提供的一種用來實現進程間通信的手段,
其提供的是基于不可靠的,并且是單向數據傳輸的服務。
郵件槽只支持單向數據傳輸,也就是服務器只能接收數據,而客戶端只能發送數據,
何為服務端?何為客戶端?
服務端就是創建郵槽的那一端,而客戶端就是已存在的郵件槽的那一端。
還有需要提及的一點是,客戶端在使用郵槽發送數據的時候只有當數據的長度 < 425 字節時,
才可以被廣播給多個服務器,如果消息的長度 > 425 字節的話,那么在這種情形下,
郵槽是不支持廣播通信的。
??????????????
??????????
郵槽的實現
首先是服務端調用?CreateMailslot?函數,這個函數會將創建郵件槽的請求傳遞給內核的系統服務,
也就是?NtCreateMailslot?函數,而?NtCreateMailslotFile?這個函數會到達底層的郵槽驅動程序,
也就是?msfs.sys?,然后一些創建郵槽的工作就交給郵槽驅動程序來完成了,對于底層驅動,這里不作介紹,
而在高層,我們也就只需要調用?CreateMailslot?函數就可以實現創建郵槽了。
????????????
?????????????
郵槽的創建
下面我們就來看看這個?CreateMailslot?函數了:
該函數利用指定的名稱來創建一個郵槽,然后返回所創建的郵槽的句柄。
HANDLE??? WINAPI?? CreateMailslot( ??????? __in????????? LPCTSTR lpName, ??????? __in????????? DWORD nMaxMessageSize, ??????? __in????????? DWORD lReadTimeout, ??????? __in_opt????? LPSECURITY_ATTRIBUTES lpSecurityAttributes );參數?lpName?指定了將要創建的郵槽的名稱,該名稱的格式必須為?\\.\mailslot\MailslotName。
在這里需要注意的是兩個斜杠后的那個?“.”,在這里使用圓點代表的是本地機器,
參數?nMaxMessageSize?用來指定可以被寫入到郵槽的單一消息的最大尺寸,
為了可以發送任意大小的消息,需要將該參數設置為?0?。
參數?lReadTimeOut?指定讀取操作的超時時間間隔,以毫秒作為單位。
讀取操作在超時之前可以等待一個消息被寫入到郵槽中,如果將這個值設置為?0?,那么若沒有消息可用的話,該函數將立即返回。
如果將該值設置為?MAILSLOT_WAIT_FOREVER,則該函數會一直等待,直到有消息可用。
參數?lpSecurityAttributes?一般設置為?NULL?即可,即采用?Windows?默認的針對于郵槽的安全性。
???????????????????????
??????????????
示例:郵槽實現進程間通信
服務端實現:(簡單 MFC 程序)
項目結構:
消息以及成員函數和成員變量的聲明:
// 實現 protected: HICON m_hIcon; ? // 生成的消息映射函數 virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedBtnExit(); afx_msg void OnBnClickedBtnRecv(); afx_msg void OnBnClickedBtnCreate(); ? //定義一個用來創建線程的成員函數 HANDLE CreateRecvThread(LPVOID lpParameter, DWORD threadFlag, LPDWORD lpThreadID); ? //控件變量:用來接收用戶輸入的數據 CEdit m_RecvEdit; ? //成員變量:用來保存創建的郵件槽句柄 HANDLE m_hMailslot;消息映射表定義:
//用來定義郵槽發送和接收的最大數據字節數 const int maxDataLen = 424; ? //用來接收由客戶端發送過來的數據 char * pStrRecvData; ? CMailSlotServerDlg::CMailSlotServerDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CMailSlotServerDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); ? m_hMailslot = NULL; ? //給用來接收數據的指針變量分配內存并清為 0 pStrRecvData = new char[maxDataLen]; memset(pStrRecvData, 0, maxDataLen); } ? void CMailSlotServerDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT_MAILSLOT, m_RecvEdit); } ? BEGIN_MESSAGE_MAP(CMailSlotServerDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(ID_BTN_EXIT, &CMailSlotServerDlg::OnBnClickedBtnExit) ON_BN_CLICKED(ID_BTN_RECV, &CMailSlotServerDlg::OnBnClickedBtnRecv) ON_BN_CLICKED(ID_BTN_CREATE, &CMailSlotServerDlg::OnBnClickedBtnCreate) END_MESSAGE_MAP()消息處理函數:
//退出按鈕的消息處理例程 void CMailSlotServerDlg::OnBnClickedBtnExit() { CDialogEx::OnOK(); } ? //創建按鈕的消息處理 void CMailSlotServerDlg::OnBnClickedBtnCreate() { //創建名為 ZacharyMailSlot 的郵槽 this->m_hMailslot = CreateMailslot(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"), 0, MAILSLOT_WAIT_FOREVER, NULL); ? if(INVALID_HANDLE_VALUE == this->m_hMailslot) { MessageBox(TEXT("創建郵槽失敗 ..."), TEXT("提示"), MB_ICONERROR); return; } } ? //接收按鈕的消息處理 void CMailSlotServerDlg::OnBnClickedBtnRecv() { CString cStrRecvData; DWORD dwRead; ? //創建接收數據的線程,將郵槽句柄傳遞給線程 CreateRecvThread((LPVOID)this->m_hMailslot, 0, NULL); ? cStrRecvData = pStrRecvData; ? this->m_RecvEdit.SetWindowText(cStrRecvData); ? UpdateData(FALSE); } ? //線程處理函數 DWORD WINAPI RecvThreadProc(LPVOID lpPrameter) { HANDLE hRecvMailSlot; DWORD dwRead; ? hRecvMailSlot = (HANDLE)lpPrameter; ? //利用傳進來的郵槽句柄接收收據,并將數據存放到 pStrRecvData 中 if(!ReadFile(hRecvMailSlot, pStrRecvData, maxDataLen, &dwRead, NULL)) { return NULL; } ? //關閉郵槽 CloseHandle(hRecvMailSlot); ? return NULL; } ? HANDLE CMailSlotServerDlg::CreateRecvThread(LPVOID lpParameter, DWORD threadFlag, LPDWORD lpThreadID) { //創建一個線程 return CreateThread(NULL, 0, RecvThreadProc, lpParameter, threadFlag, lpThreadID); }客戶端實現:(簡單 MFC 程序)
項目結構:
消息以及成員函數和成員變量的聲明:
// 實現 protected: HICON m_hIcon; ? // 生成的消息映射函數 virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedBtnExit(); afx_msg void OnBnClickedBtnSend(); CEdit m_SendEdit;消息映射表定義:
const int maxDataLen = 424;CMailSlotClientDlg::CMailSlotClientDlg(CWnd* pParent /*=NULL*/): CDialogEx(CMailSlotClientDlg::IDD, pParent) {m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }void CMailSlotClientDlg::DoDataExchange(CDataExchange* pDX) {CDialogEx::DoDataExchange(pDX);DDX_Control(pDX, IDC_EDIT_SEND, m_SendEdit); }BEGIN_MESSAGE_MAP(CMailSlotClientDlg, CDialogEx)ON_WM_PAINT()ON_WM_QUERYDRAGICON()ON_BN_CLICKED(ID_BTN_EXIT, &CMailSlotClientDlg::OnBnClickedBtnExit)ON_BN_CLICKED(ID_BTN_SEND, &CMailSlotClientDlg::OnBnClickedBtnSend) END_MESSAGE_MAP()消息處理函數:
//退出按鈕的消息處理例程 void CMailSlotClientDlg::OnBnClickedBtnExit() { CDialogEx::OnOK(); } ? ? //發送數據的消息處理例程 void CMailSlotClientDlg::OnBnClickedBtnSend() { UpdateData(); ? if(this->m_SendEdit.GetWindowTextLength() > 0 && this->m_SendEdit.GetWindowTextLength() < maxDataLen) { HANDLE hSendMailSlot; CString cStrSendData; DWORD dwWrite; char * pSendBuf; ? //打開由服務端創建的郵件槽 hSendMailSlot = CreateFile(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ? if(INVALID_HANDLE_VALUE == hSendMailSlot) { MessageBox(TEXT("打開郵槽失敗 ..."), TEXT("提示"), MB_ICONERROR); return; } ? this->m_SendEdit.GetWindowText(cStrSendData); ? //需要將 Unicode 字符轉換為 ASCII 字符發送 pSendBuf = new char[cStrSendData.GetLength() + 1]; memset(pSendBuf, 0, sizeof(cStrSendData.GetLength() + 1)); for(int i=0;i<cStrSendData.GetLength();i++) { pSendBuf[i] = cStrSendData.GetAt(i); } ? //通過郵件槽向服務端發送數據 if(!WriteFile(hSendMailSlot, pSendBuf, cStrSendData.GetLength(), &dwWrite, NULL)) { MessageBox(TEXT("寫入數據失敗 ..."), TEXT("提示"), MB_ICONERROR); ? CloseHandle(hSendMailSlot); return; } MessageBox(TEXT("寫入數據成功 ..."), TEXT("提示"), MB_ICONINFORMATION); } }效果展示:
首先啟動服務端進程并單擊創建按鈕:
然后啟動客戶端進程,并在客戶端程序文本框中輸入數據,然后單擊發送按鈕:
然后回到服務端程序中,并且單擊接收按鈕:
從上面的截圖中可以看出,通過郵槽確實實現了從客戶端進程向服務端進程發送數據。
當然上面的?Demo?中的服務端和客戶端都是在本地機器上實現的,
如果想要實現本地進程和遠程進程通信的話,
只需在客戶端調用?CreateFile?打開郵槽時,將下面截圖中標記的圓點置換為遠程服務器的名稱即可以實現了。
???????????
??????????
結束語
對于郵槽呢,其實還是蠻簡單的,
在服務端的話,也就只需要在服務端調用?CreateMailslot?創建一個郵槽,
然后再在服務端調用?ReadFile?來等待讀取數據即可以了,
而在客戶端的話,也就只需要調用?CreateFile?來打開一個已經在服務端創建好的郵槽,
然后再調用?WriteFile?往這個郵槽中寫入數據就可以了。
也就是說,對于郵槽的話,也就那么點東西需要介紹,
但是通過前面的介紹我們也很容易知道,對于通過利用郵槽來實現本地進程和遠程進程的通信還是有缺陷的,
缺陷就是對于郵槽來說,服務端只能接收來自客戶端的數據,而不能給客戶端發送數據,
而客戶端的話,則只能給服務端發送數據,而不能接收服務端發送過來的數據(事實上,服務端也發送不了)。
如果要實現客戶端可以發送數據給服務端,同時也能接收來自服務端的數據,
而服務端也可以發送數據給客戶端,并且服務端也可以接收到來自客戶端的數據的話,
那需要利用另外的進程間通信的手段了,對于這點,留到下一篇博文介紹。
最后的話,那就是今天是 2010? 年的最后一天了,在這里祝諸位節日快樂,2011 會更好 !!!
總結
以上是生活随笔為你收集整理的进程间通信 - 邮槽实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程间通信 - 命名管道实现
- 下一篇: 进程间通信 - 剪贴板实现