命名管道也许是三种方式中最复杂的了,但是事实上它也是比较简单的一种,它类似于socket编程,需要在服务器端创建一个线程不停的监听是否有数据到来。
这个演示程序中封装了一个CNamedPipe的类,用于创建管道、发送数据接收数据等等,其实弄懂了这个类,这个程序就没有什么疑问了,下面还是来看看这个类吧。
bool CNamedPipe::Initialize(bool bAsServer,
string szStopListnenCmd,
void (WINAPI *fCallBack)(string buf))
{
if (bAsServer && (fCallBack == NULL))
return false;
if (bAsServer)
{ // This instance is a server, hence create the pipes.
m_hPipe = CreateNamedPipe(
GetPipeName().c_str(),
PIPE_ACCESS_INBOUND,
PIPE_NOWAIT,
1,
PIPE_BUF_SIZE,
PIPE_BUF_SIZE,
PIPE_TIMEOUT,
NULL);
if (m_hPipe == NULL || m_hPipe == INVALID_HANDLE_VALUE)
return false;
ListnerParam* pLP = new ListnerParam;
pLP->funcCallBack = fCallBack;
pLP->hPipe = m_hPipe;
pLP->szStopCmd = szStopListnenCmd;
m_hListner = CreateThread(
NULL,
0,
&ListnerProc,
(LPVOID)pLP,
0,
&m_dwListnerThreadId);
if (m_hListner == NULL || m_hListner == INVALID_HANDLE_VALUE)
return false;
}
else
{ // obviously a client, just open existing pipes.
m_hPipe = CreateFile(
GetPipeName().c_str(),
GENERIC_WRITE,
0, //No Share
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
ASSERT(m_hPipe !=INVALID_HANDLE_VALUE);
if (m_hPipe == NULL || m_hPipe == INVALID_HANDLE_VALUE)
return false;
}
return true;
}
这个函数是用来初始化一个命名管道,首先判断是为服务器创建命名管道还是客户端打开命名管道,注意在这里客户端仅仅需要做的是打开一个命名管道,而在服务器端,不仅需要创建一个命名管道,还需要创建一个新的线程不停的监听这个命名管道,还有一个值得注意的事项,就是回调函数,在线程是在CNamedPipe里面创建的,而最终显示接收数据的是在主界面中,这其实也可以认为是一个线程间通信的问题,设置一个回调函数后,新线程将接收到的数据传给这个回调函数就可以了,然后在回调函数中将接收到的数据显示在主界面中就可以了,下面来看看这个新的线程入口函数是怎么写的:
DWORD WINAPI CNamedPipe::ListnerProc(LPVOID lpParameter)
{
ListnerParam* pLP = (ListnerParam*)lpParameter;
if (pLP == NULL)
return 1;
DWORD dwRetVal = 0;
bool bContinue = true;
while (bContinue && dwRetVal == 0)
{
char buf[PIPE_BUF_SIZE];
DWORD dwRead;
BOOL bOK = ReadFile(pLP->hPipe,buf,PIPE_BUF_SIZE,&dwRead,NULL);
if (dwRead == 0)
{
Sleep(100);
continue;
}
if (!bOK) //读文件失败
dwRetVal = 2;
string szMsg = buf;
if (szMsg == pLP->szStopCmd)//读到终止符
bContinue = false;
pLP->funcCallBack(szMsg);
Sleep(100);
}
delete pLP;
return dwRetVal;
}
线程函数的入口应该是一个全局函数,这里怎么是一个成员函数呢?是的,不过这个成员函数还有一个特点,它是static的,所以可以作为线程的入口函数,在这个函数中没有什么好说的,就是不停的ReadFile,第一个参数是创建的管道的句柄,可以发现这是在创建线程的时候传进来的参数,读到数据的时候调用pLP->funcCallBack(szMsg);进行回调函数的调用。
那么发送数据的函数呢?看看下面的代码:
bool CNamedPipe::Send(string szMsg)
{
DWORD dwSent;
BOOL bOK = WriteFile(m_hPipe,szMsg.c_str(),szMsg.length()+1,&dwSent,NULL);
if (!bOK || (szMsg.length()+1) != dwSent)
return false;
return true;
}
发送数据的代码是很简单的,一个简单的调用WriteFile就可以了。
好了,CNamesPipe的类就创建好了,接下来是怎么使用它的问题,很明显,思路是首先初始化命名管道,然后在客户端往里面写数据,服务器端从里面读数据就可以了。我觉得关键的内容还是在那个回调函数了,由于回调函数也必须是全局的或者是static的,那么它是不好直接让接收到的数据显示在主界面中,因此在这里用到了消息机制:
void WINAPI CNamedPipeDlg::funcCallBack(string buf)
{
::SendMessage(*AfxGetMainWnd(),WM_USER+1,0,(LPARAM)buf.c_str());//
}
回调函数完成的功能就是向用户主窗口发送一个消息,然后再在用户主界面中拦截这个消息就可以了:
ON_MESSAGE(WM_USER+1,CallUpdateData)
这是消息响应。
afx_msg LRESULT CNamedPipeDlg::CallUpdateData(WPARAM wP, LPARAM lP)
{
CString str = (char*)lP;
m_szRX = m_szRX+" "+str;
GetDlgItem(IDC_STATIC_RX)->SetWindowText(m_szRX);
MessageBeep(-1);//The MessageBeep function plays a waveform sound.
return 0;
}
这是消息响应的实现,很简单吧。
好了,关键的代码就在这里了,下面来看看运行结果吧:
但是可以看到的,这其实是一种阻塞的通信方式,服务器端在ReadFile时会阻塞,会造成资源的浪费,以后用Socket的时候,就可以解决这个问题了,用命名管道还不知道能不能实现异步的通信呢
本文深入讲解了命名管道的实现原理及应用,通过一个CNamedPipe类的实例展示了如何创建命名管道、发送和接收数据,并利用回调函数和消息机制实现数据展示。
2624

被折叠的 条评论
为什么被折叠?



