命名管道实践

命名管道技术实验

管道介绍

管道(Pipe)是一种进程间的通信机制,Windows、Linux和UNIX都使用这种机制。

管道是通过I/O接口存取的字节流创建管道后,通过使用操作系统的任何读或写I/O系统调用来读或者写它。创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。Linux管道是通过返回两个文件描述符来实现双向I/O,而Windows管道是使用单一句柄(类似于Linux文件描述符)支持双向I/O的,比Linux管道要复杂得多。

管道分类

  • 匿名管道

匿名管道只能用于相关进程(如父子进程、兄弟进程)之间的通信,并且它建立在内存区域。进程终止后,匿名管道也就消失了。匿名管道使得关联的进程可以互相传送信息,通常匿名管道用于重定向子进程的标准输入输出,以便于和父进程交换数据。要双向交换数据必须创建两个匿名管道。父进程使用写句柄写数据到一个管道,子进程使用读句柄从管道中读取数据,相应的子进程使用写句柄写数据到另一个管道,父进程使用读句柄从管道中读取数据。匿名管道是同一台计算机的关联进程的子进程重定向标准输入输出的一种有效方法,但不能用于网络环境,也不能用于非关联的进程间。

  • 命名管道

命名管道是在管道服务器和一台或多台管道客户机之间进行单向或双向通信的一种命名的管道。一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存与句柄,并且为客户—服务通信提供一个分离的管道。实例的使用保证了多个管道客户能够在同一时间使用同一个命名管道。命名管道用于在非关联进程和不同计算机上的进程间传送数据。通常命名管道服务器进程创建使用一个众所周知的名字或客户机知道名字的命名管道,知道管道名字的命名管道客户机进程在管道另一端打开管道,并服从服务器进程指定的访问限制。在服务器和客户机都连接到管道后,就可以在管道上使用读写操作来交换数据。命名管道在进程间提供一个传送数据的简单的编程接口,不管进程是否在同一台计算机上。

命名管道

命名规范

采用的UNC格式:\\Server\Pipe\[Path]Name,其中,第一部分\\Server指定了服务器的名字,命名管道服务即在此服务器创建,其字串部分可表示为一个小数点(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分\Pipe与邮槽的\Mailslot一样是一个不可变化的硬编码字串,以指出该文件是从属于NTFS;第三部分`[Path]Name`则使应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录。

通信模式

命名管道提供了两种基本的通信模式:字节模式和消息模式,可在CreateNamePipe()创建命名管道时分别用PIPE_TYPE_BYTEPIPE_TYPE_MESSAGE标志进行设定。在字节模式中,信息以连续字节流的形式在客户与服务器之间流动。这也就意味着对于客户机应用和服务器应用在任何一个特定的时间段内都无法准确知道有多少字节从管道中读出或写入。在这种通信模式中,一方在向管道写入某个数量的字节后并不能保证管道的另一方能读出等量的字节。对于消息模式,客户机和服务器则是通过一系列不连续的数据包进行数据的收发。从管道发出的每一条消息都必须作为一条完整的消息读入。在此建议使用消息模式。

实现方法

要想实现一个命名管道服务器必须开发一个应用程序,通过它创建命名管道的一个或多个“实例”,再由客户机进行访问。对服务器来说,管道实例实际就是一个句柄,用于从本地或远程客户机的应用程序接受一个连接请求。按照下面的步骤,可以写出一个基本的服务器应用程序。

  1. 使用API函数CreateNamedPipe创建一个命名管道实例句柄。

  2. 使用API函数ConnectNamedPipe在命名管道实例上监听客户机的连接请求。

  3. 分别使用API函数ReadFileWriteFile从客户机接收数据或将数据发送给客户机。

  4. 使用API函数DisconnectNamedPipe关闭命名管道的连接。

  5. 使用API函数CloseHandle关闭命名管道实例句柄

实现一个命名管道客户机时要开发一个应用程序,令其建立与某个命名管道服务器的连接。注意客户机不可创建命名管道实例,它可打开来自服务器的现成的实例。按照下面步骤,可以编写一个最基本的客户机应用程序。

  1. 使用API函数WaitNamePipe等待一个命名管道实例供自已使用。

  2. 使用API函数CreateFile建立与命名管道的连接。

  3. 使用API函数WriteFileReadFile分别向服务器发送数据或从中接收数据。

  4. 使用API函数CloseHandle关闭打开的命名管道会话。

C++语言版

服务器

m_hPipe=CreateNamedPipe(“\\\\.\\Pipe\\Test”,PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE/|PIPE_READMODE_BYTE,    1,0,0,1000,NULL);//创建命名管道
if(m_hPipe==INVALID_HANDLE_VALUE)
    m_sMessage=“Errorcreatepipe”;
else
{
    m_sMessage=“success creat pipe”;
    AfxBeginThread(ReadProc,this);//开启线程
}

由于ConnectNamedPipe()函数在没有客户机连接到服务器时会无限的等待下去,因此为避免由此引起的主线程的阻塞,而开辟了一个子线程ReadProc:

UINT ReadProc(LPVOIDlpVoid)
{
    char buffer[1024];//数据缓存
    DWORD ReadNum;
    CServerView* pView=(CServerView*)lpVoid;//获取
    视句柄
    if(ConnectNamedPipe(pView->m_hPipe,NULL)==FALSE)//等待客户机的连接
    {
        CloseHandle(pView->m_hPipe);//关闭管道句柄
        pView->m_sMessage=“error connect”;
        pView->Invalidate();
        return 0;
    }
    else
    {
        pView->m_sMessage=“success connect”;
        pView->Invalidate();
        //从管道读取数据
        if(ReadFile(pView->m_hPipe,buffer,sizeof(buffer),
            &ReadNum,NULL)==FALSE)
        {
            CloseHandle(pView->m_hPipe);//关闭管道句柄
            pView->m_sMessage=“read error”;
            pView->Invalidate();
        }
        else
        {
            buffer[ReadNum]='\0';//显示接收到的信息
            pView->m_sMessage=CString(buffer);
            pView->Invalidate();
        }
        return1;
    }
}

在客户同服务器建立连接后,ConnectNamedPipe()才会返回,其下语句才得以执行。随后的ReadFile()
将负责把客户写入管道的数据读取出来。在全部操作完成后,服务器可以通过调用函数DisconnectNamedPipe()而终止连接:

if(DisconnectNamedPipe(m_hPipe)==FALSE)//终止连接
    m_sMessage=“error terminate connect”;
else
{
    CloseHandle(m_hPipe);//关闭管道句柄
    m_sMessage=“success terminate connect”;
}

客户端

客户端连接服务器并发送数据

CString Message=“[testdata,fromclient]”;//要发送的数据
DWORD WriteNum;//发送的是数据长度
//等待与服务器的连接
if (WaitNamedPipe (“\\\\.\\Pipe\\Test“, NMPWAIT_WAIT_FOREVER)==FALSE)
{
    m_sMessage=“error waiting connect”;
    Invalidate();
    return;
}
//打开已创建的管道句柄
HANDLE hPipe=CreateFile(“\\\\.\\Pipe\\Test”,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hPipe==INVALID_HANDLE_VALUE)
{
    m_sMessage=“erroropenpipe”;
    Invalidate();
    return;
}
else
{
    m_sMessage=“successopenpipe”;
    Invalidate();
}
//向管道写入数据
if(WriteFile(hPipe,Message,Message.GetLength(),    &WriteNum,NULL)==FALSE)
{
    m_sMessage="error data write";
    Invalidate();
}
else
{
    m_sMessage=“success write”;
    Invalidate();
}
CloseHandle(hPipe);

客户端接收数据

static UINT ReceiveFromPipe(LPVOID pArgs)
{
    try
    {
        CNamedPipeClientCDlg* pDlg = (CNamedPipeClientCDlg*)pArgs;
        char szBuf[1024]={0};
        memset(szBuf,0,sizeof(szBuf));
        DWORD dwRead,dwWrite;
        while(1)
        {
            if(!ReadFile(pDlg->hlPC,szBuf,1024,&dwRead,0))
                break;
            SetWindowText((HWND)pDlg->m_ReceivedEdit,szBuf);
            memset(szBuf,0,1024);
        }
    }
    catch (CMemoryException* e)
    {
        
    }
    catch (CFileException* e)
    {
    }
    catch (CException* e)
    {
    }
    
    return 0;
}

由于客户端需要不断接收服务器端的消息,所以需要在新线程中执行,防止阻塞主线程。

hlThread = AfxBeginThread(ReceiveFromPipe,this)

其中,hlThread的定义为

HANDLE hlThread;

C#语言版

客户端

在工程中引用AsyncPipe,定义管道对象

private NamedPipeStreamClient pipeClient = new NamedPipeStreamClient("TestC");

发送消息

pipeClient.SendMessage(Encoding.Default.GetBytes(tbSend.Text));

接收消息

1.定义委托

pipeClient.MessageReceived += new MessageEventHandler(pipeClient_MessageReceived);

2.定义回调函数

void pipeClient_MessageReceived(object sender, MessageEventArgs args)
{
    string content = Encoding.Default.GetString(args.Message);
    this.tbReceive.Text += content;
}

Delphi语言版

客户端

定义管道对象

pipeNameStr:='\\.\Pipe\TestC';
if(WaitNamedPipe(pchar(pipeNameStr),NMPWAIT_WAIT_FOREVER)=FALSE) then
begin
   ShowMessage(format('WaitNamedPipe failed with error %d',[GetLastError()]));
   exit;
end;
pipeHandle := CreateFile(pchar(pipeNameStr),GENERIC_READ or GENERIC_WRITE,FILE_SHARE_WRITE,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,0);
if pipeHandle = INVALID_HANDLE_VALUE then
begin
   ShowMessage(format('CreateFile failed with error %d',[GetLastError()]));
   exit;
end;

其中,pipeHandlepipeNameStr的定义为

pipeHandle:HWND;
pipeNameStr:string;

发送消息

if WriteFile(pipeHandle,pchar(EditMessageToSend.Text)^,length(EditMessageToSend.Text),bytesWrite,nil)= False then
begin
   ShowMessage(format('WriteFile failed with error %d',[GetLastError()]));
   exit;
end;

ShowMessage(format('write %d Bytes',[bytesWrite]));

接收消息

  function ReadFromPipe(p:Pointer):DWORD;stdcall;
begin
  if pipeHandle <> INVALID_HANDLE_VALUE then
  begin
    while TRUE DO
    begin
      if ReadFile(pipeHandle,buffer,sizeof(buffer),bytesRead,nil)=FALSE then
      begin
        ShowMessage(format('ReadFile failed with error %d',[GetLastError()]));
        Application.Destroy;
      end;
      SendMessage(integer(p),WM_MYMSG,1,1);
    end;
  end ;
  result := 0;
end ;

其中,WM_MYMSG的定义为

const
  WM_MYMSG = WM_USER+1024;

对应的消息处理函函数声明为

procedure display(var msg:TMessage);message WM_MYMSG;

实现为

procedure TForm1.display(var msg:TMessage);
begin
  EditMessageReceived.Text := EditMessageReceived.Text + buffer;
end;

由于客户端需要不断接收服务器端的消息,所以需要在新线程中执行,防止阻塞主线程。

threadHandle := CreateThread(nil,0,@ReadFromPipe,Ptr(form1.Handle),0,threadId);

其中,threadHandlethreadId的定义为

threadHandle:HWND;
threadId:Cardinal;
阅读 6.9k

推荐阅读
天地一码农
用户专栏

分享个人平时工作、学习、生活心得体会;记录人情冷暖,事事非非

0 人关注
4 篇文章
专栏主页