命名管道实践

命名管道技术实验

管道介绍

管道(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`则使应用程序能够惟必定义及标识一个命名管道的名字,并且能够设置多级目录。this

通讯模式

命名管道提供了两种基本的通讯模式:字节模式和消息模式,可在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;
相关文章
相关标签/搜索