多线程编程实例----进程通信之命名管道

本文深入讲解了命名管道的实现原理及应用,通过一个CNamedPipe类的实例展示了如何创建命名管道、发送和接收数据,并利用回调函数和消息机制实现数据展示。

命名管道也许是三种方式中最复杂的了,但是事实上它也是比较简单的一种,它类似于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的时候,就可以解决这个问题了,用命名管道还不知道能不能实现异步的通信呢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值