机器译文: http://support.microsoft.com/kb/871044
英文原文: http://support.microsoft.com/kb/871044/en-us/
这个篇文章内容详细, 特别是操作部分, 内容也简单易懂.
一. 概要
本文讨论如何在 MicrosoftVisualBasic.NET 或 Microsoft Visual Basic 2005 中使用命名管道进行进程间通信。 本文包含代码示例通过在 VisualBasic.NET 或 Visual Basic 2005 中使用命名管道, 说明客户 / 服务器通信。 本文介绍进程间通信来创建命名管道服务器和命名管道客户。 通过管道通信通过以下方式执行:
-
通过 CreateNamedPipe 函数创建命名管道。
-
通过使用 ConnectNamedPipe 函数阻塞服务器应用程序直到有客户端连接。
-
客户端通过使用 CallNamedPipe 函数连接到服务器。
-
调用 ReadFile 函数或 WriteFile 函数, 实现在管道上的通信。
-
当线程结束使用管道后, 调用 DisconnectNamedPipe 函数来关闭与客户端的连接。
-
当你完成在管道上的通信后, 调用 CloseHandle 函数来销毁该命名管道。
二. 介绍
命名管道是单向或双面管道管道服务器和一个或多个管道客户之间进行通信。 您可以命名管道用户能够提供同一计算机上进程之间或通过网络不同计算机上进程之间通讯。 术语 " 命名管道服务器 " 指向过程创建命名管道和术语 " 命名管道客户 " 指到一个进程, 连接到的命名管道实例。
您可使用 Microsoft Visual Basic .NET 或 Microsoft Visual Basic 2005 来创建应用程序通过命名管道, 与其他进程。 本文包含一个代码示例使用命名管道来两 VisualBasic.NET 或 2005 VisualBasicWindows 应用程序之间通信。
三. 要求
本文假定您已熟悉以下主题:
- Windows 应用程序
- VisualBasic.NET 编程
- 使用管道
以下列表概括了推荐硬件、 软件、 网络结构, 以及 ServicePack, 您需要:
- MicrosoftVisualStudio.NET 或 Microsoft Visual Studio 2005
- Microsoft.NET 框架
四. 创建命名管道服务器
创建命名管道服务器, 并通过管道, 然后与客户通信请按照下列步骤:
-
通过
CreateNamedPipe 函数创建命名管道。
-
通过使用
ConnectNamedPipe 函数阻塞服务器应用程序直到有客户端连接。
-
客户端通过使用
CallNamedPipe 函数连接到服务器。
-
调用
ReadFile 函数或
WriteFile 函数, 实现在管道上的通信。
-
当线程结束使用管道后, 调用
DisconnectNamedPipe 函数来关闭与客户端的连接。
-
当你完成在管道上的通信后, 调用
CloseHandle 函数来销毁该命名管道。
4.1 Windows 应用程序, 创建命名管道服务器
要设计 Windows 应用程序, 通过使用 VisualBasic.NET 或 Visual Basic 2005, 创建命名管道服务器请按照下列步骤操作:
- 启动 MicrosoftVisualStudio.NET 或 Microsoft Visual Studio 2005。
- 在 "文件" 菜单, 指向 "新建" , 然后单击 "项目" 。
- "ProjectTypes(项目类型)" 窗口下单击 "VisualBasic" 项目 , 并单击 "模板" 下 "WindowsApplication(Windows 应用程序)" 。
注意 对于 Visual Studio 2005, 单击 项目类型 下 VisualBasic 。
- 在 "名称" 框中, 键入 "MyServerApp" 然后单击 "确定" 。 默认情况下, 名为 Form 1 窗体创建。
- 向窗体 Form 1 添加三个 Button 控件。
- 在 视图 菜单上, 单击 属性窗口 。
- 将 Text 属性的 Button 控件为下列值:
收起该表格
展开该表格
名称 |
文本 |
Button 1 |
创建命名管道 |
Button 2 |
等待客户连接 |
Button3 |
断开服务器 |
-
向窗体 Form 1 添加一个 Label 控件。 Label 1 Label 控件被添加到窗体 Form 1。
4.2 将所有声明添加到 Windows 应用程序中的模块
为了使用命名管道实现进程间的通信, 添加的这些函数声明都是必要的, 请按照下列步骤:
- 在 "SolutionExplorer(解决方案资源管理器)" 中, 右击 "MyServerApp" , 指向 "添加" , 再点击 "模块" 。
- 在 "AddNewSolutionItem(添加新项)- MyServerApp" 对话框中, 单击 "添加" 。
- 把以下代码添加到 Module 1 模块中: Public Const FILE_ATTRIBUTE_NORMAL As Short = &H80S Public Const FILE_FLAG_NO_BUFFERING As Integer = &H20000000 Public Const FILE_FLAG_WRITE_THROUGH As Integer = &H80000000 Public Const PIPE_ACCESS_DUPLEX As Short = &H3S Public Const PIPE_READMODE_MESSAGE As Short = &H2S Public Const PIPE_TYPE_MESSAGE As Short = &H4S Public Const PIPE_WAIT As Short = &H0S Public Const INVALID_HANDLE_VALUE As Short = -1 Declare Function CreateNamedPipe Lib "kernel32" Alias "CreateNamedPipeA" _ (ByVal lpName As String, ByVal dwOpenMode As Integer, _ ByVal dwPipeMode As Integer, ByVal nMaxInstances As Integer, _ ByVal nOutBufferSize As Integer, ByVal nInBufferSize As Integer, _ ByVal nDefaultTimeOut As Integer, ByVal lpSecurityAttributes As IntPtr _ ) As Integer Declare Function ConnectNamedPipe Lib "kernel32" _ (ByVal hNamedPipe As Integer, ByVal lpOverlapped As Integer) As Integer Declare Function DisconnectNamedPipe Lib "kernel32" _ (ByVal hNamedPipe As Integer) As Integer Declare Function WriteFile Lib "kernel32" _ (ByVal hFile As Integer, ByRef lpBuffer() As Byte, _ ByVal nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer, _ ByVal lpOverlapped As Integer _ ) As Integer Declare Function ReadFile Lib "kernel32" _ (ByVal hFile As Integer, ByRef lpBuffer As Integer, _ ByVal nNumberOfBytesToRead As Integer, ByRef lpNumberOfBytesRead As Integer, _ ByVal lpOverlapped As Integer _ ) As Integer Declare Function FlushFileBuffers Lib "kernel32" _ (ByVal hFile As Integer) As Integer Declare Function CloseHandle Lib "kernel32" _ (ByVal hObject As Integer) As Integer
4.3 编写代码来创建一个命名管道服务器
创建命名管道后, 等待来自客户端的连接。 当客户端连接到服务器, 在管道上读取或写入数据。 要这样做, 请按照下列步骤操作:
- 在 "SolutionExplorer(解决方案资源管理器)" 右击 Form 1 , 再点击 "打开" 。
- 在 Form1 的设计窗口里, 双击 "创建命名管道" 按钮, 然后把下面的代码添加到 Button 1_Click 过程中: Dim openMode, pipeMode As Integer 'Create the named pipe openMode = PIPE_ACCESS_DUPLEX Or FILE_FLAG_WRITE_THROUGH pipeMode = PIPE_WAIT Or PIPE_TYPE_MESSAGE Or PIPE_READMODE_MESSAGE hPipe = CreateNamedPipe(pipeName, openMode, pipeMode, 10, 10000, 2000, 10000, IntPtr.Zero) Label1.Text = "Created the named pipe and waiting for the clients." Button1.Visible = False Button2.Visible = True Button3.Visible = True
- 添加下列代码在 Button 1 _ Click 过程前面: Private Const pipeName As String = "//./pipe/MyPipe" Private Const BUFFSIZE As Short = 10000 Private Buffer(BUFFSIZE) As Byte Private hPipe As Integer
- 在 "SolutionExplorer(解决方案资源管理器)" 里, 双击 Form 1 。
- 在 Form1 的设计窗口里, 双击 "等待客户连接" 按钮, 然后把下面的代码添加到 Button 2_Click 过程中: Dim byteCount, i, res, cbnCount As Integer For i = 0 To BUFFSIZE - 1 'Fill an array of numbers Buffer(i) = i Mod 256 Next i 'Wait for a connection, block until a client connects Label1.Text = "Waiting for client connections" Me.Refresh() Do res = ConnectNamedPipe(hPipe, 0) 'Read the data sent by the client over the pipe cbnCount = 4 res = ReadFile(hPipe, byteCount, Len(byteCount), cbnCount, 0) If byteCount > BUFFSIZE Then 'Client requested for byteCount bytes byteCount = BUFFSIZE 'but only send up to 20000 bytes End If 'Write the number of bytes requested by the client res = WriteFile(hPipe, Buffer, byteCount, cbnCount, 0) res = FlushFileBuffers(hPipe) 'Disconnect the named pipe. res = DisconnectNamedPipe(hPipe) 'Loop until the client makes no more requests for data. Loop Until byteCount = 0 Label1.Text = "Read or Write completed" Button2.Visible = False
- 在设计视图中, 双击 Form 1 , 然后将以下代码添加到 Form 1_Load 过程中: Button2.Visible = False Button3.Visible = False
- 在 "SolutionExplorer(解决方案资源管理器)" 里, 双击 Form 1 。
- 在 Form1 的设计窗口里, 双击 "断开服务器" 按钮, 然后把下面的代码添加到 Button 3_Click 过程中: Dim res As Integer 'Close the pipe handle when the client makes no requests CloseHandle(hPipe) Label1.Text = "Disconnected the named pipe"
- 在 "生成" 菜单上, 单击 BuildSolution 。vs2005 为 "生成myServerApp" ;
五. 创建命名管道客户
要创建与服务器通信的命名管道客户端, 请按照下列步骤操作:
- 调用 CreateFile 函数以创建到命名管道句柄。
- 调用 ReadFile 函数或 WriteFile 函数 在管道上通信。
- 利用 CreateFile 函数中创建的命名管道句柄, 调用 CloseHandle 函数。
你也可以使用命名管道事务用于客户/服务器 通信上。这个命名管道事务在一次单独的网络操作中结合了写操作和读操作。 命名管道事务只能用于双向, 消息模式的管道上. 进程能够调用 TransactNamedPipe 函数或 CallNamedPipe 函数实现命名管道事务.
在此代码示例, 使用 CallNamedPipe 函数可以连接到命名管道服务器, 把数据写入管道, 然后从管道读取数据。
5.1 设计与命名管道服务器通信的 Windows 应用程序
要设计用于连接到命名管道服务器,的VisualBasic.NET 或 Visual Basic 2005 Windows 应用程序, 请按照下列步骤操作:
- 启动 MicrosoftVisualStudio.NET 或 Microsoft Visual Studio 2005。
- 在 "文件" 菜单, 指向 "新建" , 然后单击 "项目" 。
- "ProjectTypes(项目类型)" 窗口下单击 "VisualBasic" 项目 , 并单击 "模板" 下 "WindowsApplication(Windows 应用程序)" 。
注意 对于 Visual Studio 2005, 单击 项目类型 下 VisualBasic 。
- 在 "名称" 框中, 键入 "MyClientApp", 然后单击 "确定" 。 默认情况下, 名, 是为 Form 1 窗体创建。
- 向窗体 Form 1 添加 Button 控件。
- 右键单击 Button 1 , 然后单击 属性 。
- 将 Text 属性设置为 "连接到服务器" 。
- 向窗体 Form 1 添加一个 Label 控件。 Label 1 标签 控件添加到 Form 1 表单。
- 将 Label 1 Label 控件的 Visible 属性设置为 False
- 将两个 TextBox 控件添加到 Form 1 窗。
5.2 编写代码连接到命名管道服务器
通过使用 CallNamedPipe 函数连接到命名管道服务器。 连接到服务器后, CallNamedPipe 函数把数据写入管道, 从管道读取数据, 然后关闭管道。 要连接到服务器, 然后读取或写入数据, 请按照下列步骤操作:
- 在设计视图, 双击 "连接到服务器" , 然后将以下代码添加到 Button 1_Click 过程: Dim i, res, cbRead,numBytes As Integer Dim bArray() As Byte Dim temp As String numBytes = CInt(TextBox1.Text) If numBytes < 0 Then MessageBox.Show("Value must be at least 0.", MsgBoxStyle.OKOnly) Exit Sub End If If numBytes = 0 Then Label1.Visible = True Label1.Text = "The connection to the server is disconnected." Button1.Visible = False TextBox1.Visible = False TextBox2.Visible = False End If If numBytes > BUFFSIZE Then numBytes = BUFFSIZE End If ReDim bArray(numBytes) 'Create the return buffer 'Call the CallNamedPipe function to do the transactions res = CallNamedPipe(pipeName, numBytes, Len(numBytes), bArray(0), numBytes, cbRead, 30000) 'Wait up to 30 seconds for a response 'Format the data received, and then display the data in the text box If res > 0 Then temp = Format(bArray(0), " 000") For i = 1 To cbRead - 1 If (i Mod 16) = 0 Then temp = temp & vbCrLf temp = temp & " " & Format(bArray(i), "000") Next i TextBox2.Text = temp Else MessageBox.Show("Error number " & Err.LastDllError & _ "while trying to call the CallNamedPipe function.", MsgBoxStyle.OKOnly) End If
- 添加下列代码在 Button 1 _ Click 过程前面: Private Const pipeName As String = "//./pipe/MyPipe" Private Const BUFFSIZE As Integer = 10000 Private hpipe As Integer Public Const INVALID_HANDLE_VALUE As Short = -1 Public Declare Function CallNamedPipe Lib "kernel32" Alias "CallNamedPipeA" _ (ByVal lpNamedPipeName As String, _ ByRef lpInBuffer As Integer, _ ByVal nInBufferSize As Integer, _ ByRef lpOutBuffer As Byte, _ ByVal nOutBufferSize As Integer, _ ByRef lpBytesRead As Integer, ByVal nTimeOut As Integer) As Integer
- 在 "生成" 菜单上, 单击 BuildSolution 。vs2005 为 "生成myClientApp" ;
六. 验证代码工作
要验证代码工作, 请按照下列步骤:
- 启动服务器应用程序, 单击 "调试" 菜单上的 "启动调试" MyServerApp 项目 。
- 在窗体 Form 1 上点击 "创建命名管道" , 然后点击 "等待客户连接" 。 应用程序现在被阻塞, 等待客户端以连接。
- 启动客户端应用程序, 单击 "调试" 菜单上的 "启动调试" MyClientApp 项目 。
- 在窗口 Form 1 上, 在 TextBox1 框中键入 10 , 然后单击 "连接到服务器" 。 您可以看到 TextBox 2 中接收到字节数组。
- 客户端要断开跟服务器的连接, 客户端应用程序中在 TextBox1 框中键入 0 , , 然后单击 "连接到服务器" 。
- 关闭客户端应用程序。
- 命名管道最后要断开, 然后关闭服务器应用程序, 单击服务器应用窗体 Form 1 上的 "断开服务器" 。
七. 参考
请, 有关访问下列 Microsoft Developer Network (MSDN) Web 站点:
命名管道
http://msdn2.microsoft.com/en-us/library/aa365590.aspx (http://msdn2.microsoft.com/en-us/library/aa365590.aspx)
命名管道上事务
http://msdn2.microsoft.com/en-us/library/aa365789.aspx (http://msdn2.microsoft.com/en-us/library/aa365789.aspx)
管道函数
http://msdn2.microsoft.com/en-us/library/aa365781.aspx (http://msdn2.microsoft.com/en-us/library/aa365781.aspx)
这篇文章中的信息适用于:
- Microsoft Visual Basic 2005
- Microsoft Visual Basic .NET 2003 Standard Edition
- Microsoft Visual Basic .NET 2002 Standard Edition
---------------------------------------------------------可爱的分割线-------------------------------------------------------------
我测试的系统是 XP sp3, IDE 是vs2005, 开发语言是VB.NET()
这里写一些我自己的补充, 主要是对客户端的.
1. 首先是 在Button1_Click事件中添加
If TextBox1.Text = "" Then
TextBox1.Text = 10
End If
我老是不注意输入 TextBox1 的值, 所以在代码里添加一个默认值, 在设计图里面加也行.
2. MessageBox.Show("Value must be at least 0.", MsgBoxStyle.OkOnly)
如果报错, 改为
MessageBox.Show("Value must be at least 0.", MsgBoxStyle.OkOnly.ToString)
类似情况同样处理
3. 如果想用CreateFile , ReadFile , WriteFile 和 CloseHandle 函数, 就要注意看我下面的内容啦(用了一天的时间呢, 以前都没学过VB)
一些常量的声明:
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000
Private Const FILE_SHARE_WRITE = &H2
Private Const FILE_SHARE_READ = &H1
Private Const OPEN_ALWAYS = 4
Private Const FILE_ATTRIBUTE_NORMAL = &H80
Private Const OPEN_EXISTING = 3
Private Const CREATE_ALWAYS = 2
函数的声明:
Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, _
ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, _
ByVal lpSecurityAttributes As Integer, ByVal dwCreationDisposition As Integer, _
ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As Integer) As Integer
Declare Function ReadFile Lib "kernel32" (ByVal hFile As Integer, ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToRead As Integer, ByRef lpNumberOfBytesRead As Integer, _
ByVal lpOverlapped As Integer ) As Integer
Declare Function WriteFile Lib "kernel32"(ByVal hFile As Integer, ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer, _
ByVal lpOverlapped As Integer ) As Integer
Declare Function CloseHandle Lib "kernel32"(ByVal hObject As Integer) As Integer
其中要注意几点, ReadFile 函数的第二个参数类型定义跟服务器的有点不同, 其他都一样; 如果你用的开发语言是VB, 而非VB.NET, 请把里面所有的 Integer 改为 Long. ReadFile 和 WriteFile 函数的第二个参数ByRef lpBuffer As Byte , 可以改为其他类型的, 如Integer 或 String等, 显而易见就是传送和接收的数据的类型可以多样的, 可以重载多个ReadFile 和 WriteFile 函数. 如:
Declare Function WriteFile Lib "kernel32"(ByVal hFile As Integer, ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer, _
ByVal lpOverlapped As Integer ) As Integer
Declare Function WriteFile Lib "kernel32"(ByVal hFile As Integer, ByRef lpBuffer As Integer, _
ByVal nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer, _
ByVal lpOverlapped As Integer ) As Integer
函数的应用:
管道句柄 = CreateFile(管道名, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0) '参数不能乱改, OPEN_EXISTING 这一项是固定的
管道句柄 为 -1 的话, 表示连接不成功
res = ReadFile(管道句柄 , bArray(0)(已经定义的一个字节数组, 用于接收数据), numBytes(读取的最大字节数), cbRead(已经读取的字节数), 0)
res 为 0 的话, 表示读取数据出错
如果字节数组没初始化, 用 ReDim bArray(numBytes)
res = WriteFile(管道句柄, Buffer(已经定义的一个字节数组, 用于发送的数据), byteCount(写入的最大字节数), cbnCount(已写入的字节数), 0)
res = FlushFileBuffers(管道句柄) '把数据立即发送出去, 不用等到缓冲区满, 直到所有数据发送完这个方法才返回
CloseHandle(管道句柄) '关闭管道, 结束与服务器的连接; 服务器端调用这个方法, 将结束整个命名通道服务.
openMode = PIPE_ACCESS_DUPLEX Or FILE_FLAG_WRITE_THROUGH
pipeMode = PIPE_WAIT Or PIPE_TYPE_MESSAGE Or PIPE_READMODE_MESSAGE
hPipe = CreateNamedPipe(pipeName, openMode, pipeMode, 10, 10000, 2000, 10000, IntPtr.Zero)
dwOpenMode:为命名管道打开的模式,有PIPE_ACCESS_DUMPLEX(双向)、PIPE_ACCESS_INBOUND(输入)、PIPE_ACCESS_OUTBOUND(输出)这三种,这些标志还可以和一些附加的I/O控制和安全模式的常数组合使用,详细可参考MSDN。
dwPipeMode:为管道传输模式,有前面所述的PIPE_TYPE_BYTE(字节模式)和PIPE_TYPE_MESSAGE(消息模式)两种,可以和PIPE_READMODE_BYTE和PIPE_READMODE_MESSAGE常数组合使用以限定客户端的读取模式。可以使用PIPE_TYPE_MESSAGE 和 PIPE_READMODE_BYTE组合来指定发送者以消息模式向管道发送数据,而接收者一次可以读取任意数量的字节。注意不可将PIPE_TYPE_BYTE和PIPE_READMODE_MESSAGE组合使用,这样会导致CreateNamedPipe()函数调用失败,因为字节模式没有边界,在接收端用消息模式读取的时候无法判断消息的边界。
--------------------------------------------------可爱的分割线------------------------------------------------------------------------------
呵呵, 这个例子重点是告诉你怎么在两个进程间建立命名管道进行通信, 对通信的结果没提及到.
我是看不懂结果的, 开始的时候连想做简单的字符串通信都做不到. 抛开其他的不说, 如果连传送"你好, hello world!" 都做不到那怎么行呢! 第一次传送测试结果我用字符串显示是乱码, 依我的经验这个应该是编码的问题, 但是我不知道他原来的字符串的编码, 用Default的Encoding都不行.
如果是这样的话, 那我自己指定编码(gb2312, Default也可以)就行了, 传送还是用字节数组, 接收也是用字节数组, 传送方可以把传送的字节的数组的长度先传送过去, 接收方根据这个长度接收数据(现在的情况是数据是顺序传输的, 如果在某些情况下不是这样, 可以就要一个一个字节的传了).
传送方传送数据
Dim msg As String = ""
Dim msgBuffer(1024) As Byte
Dim msgLength As Integer
msg = "你好, hello world!"
msgBuffer = System.Text.Encoding.GetEncoding("gb2312").GetBytes(msg)
msgLength = msgBuffer.Length '注意不要用msg.length, 如果有中文, 长度将不正确
发送方接收数据
res = ReadFile(hpipe, bArray(0), msgLength, cbRead, 0)
msg = System.Text.Encoding.GetEncoding("gb2312").GetString(bArray)
一个一个字节地读, 直到换行或者接收结束
Do
Array.Clear(tempBuffer, 0, tempBuffer.Length)
res = ReadFile(hpipe, tempBuffer(0), 1, cbRead, 0)
If res = 0 Then
isEnd = False
Exit Do
End If
If res = 0 Then
Continue Do
End If
bArray(iii) = tempBuffer(0)
iii += 1
msg = System.Text.Encoding.Default.GetString(bArray)
If (msg.IndexOf("/r/n") > -1) Then
isLine = True
msg = msg.Substring(0, msg.IndexOf("/r/n"))
Exit Do
End If
Loop Until (isLine Or isEnd)
呵呵, 搞定! 上面的基本是用学的java 经验写的, 对于 vb.net 不是很了解, 可能有更好的方法呢