能实现局域网上两台主机间文件拷贝的方法有很多种,这里介绍的“命名管道”(Named Pipe )是一种比较可靠的进程间通信机制,可用在同一台计算机不同进程间,也可用在不同计算机的不同进程间,可以是单向的,也可以是双向的,Windows NT、Windows 2000、Windows 95和Windows 98均提供了对它的支持,而且在Unix下也有类似的概念。它是在Microsoft LAN管理器和IBMLAN服务器网络操作系统上实现的。
命名管道使用了MSNP(微软网络提供者)重定向器,这样应用程序便可以不用了解网络协议的细节而利用该机制实现网络上的数据传输。它采用“命名管道文件系统”(Named Pipe File System)接口,其命名是采用UNC(通用命名规范)格式的:
\\ServerName\Pipe\[pipename]
\\ServerName指明命名管道是在那个服务器上创建的,ServerName既可以是一个实际的计算机名,也可以是小数点(“.”)以指明是在本机上创建;\Pipe是一个硬编码(Hardcode)不用区分大小写的字符串用以指明这是一个管道名,该文件名从属于NPFS;[pipename]是实际的自定义的管道名,该名称在前面指定的服务器上必须是唯一的,该名称可以包含多级目录,但目录名必须不是已经创建的管道名,例:
\\.\Pipe\xyPipe’这是一个合法管道名
\\.\Pipe\xyPipe\Pipe’这不是一个合法的管道名,因为前面的目录\\.\Pipe\xyPipe是一个已经创建的管道名了。
\\.\Pipe\xxyPipe\Pipe’这也是一个合法的文件名
命名管道有两种基本通信模式:字节模式和消息模式。在字节模式中,数据是以字节流的形式在管道种传输,数据之间没有边界,在管道写入和读出操作中是以字节流即数据块为基本单位操作的,这适合传输大容量数据;在消息模式中,数据是以一条条不连续的消息为基本传输单元,消息和消息之间有边界,在管道写入和读出操作中也是以消息为单位进行操作的,这种方式适合传输量小的数据。因为现在的文件大小常常有几百K甚至更大,所以程序中选择使用字节模式。
下面详细介绍一下CreateNamedPipe()这个函数,该函数C原型如下:
HANDLE CreateNamedPipe(
LPCTSTR lpName, // pointer to pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size, in bytes
DWORD nInBufferSize, // input buffer size, in bytes
DWORD nDefaultTimeOut, // time-out time, in milliseconds
LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to security attributes
);
lpName:为前面所述的命名管道名。
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()函数调用失败,因为字节模式没有边界,在接收端用消息模式读取的时候无法判断消息的边界。
nMaxInstances:管道最大的连接实例句柄,其范围在1到255之间。
nOutBufferSize和nInBufferSize分别指明管道输出和输入缓冲区的大小,如设为0则使用系统默认大小。
nDefaultTimeOut以毫秒为单位设定客户机等待同命名管道建立连接的最长时间。
LpSecurityAttruibutes为一个安全描述符,设为Null表示使用系统默认的描述符,同时句柄不可继承。
要注意的是在程序中命名管道的写操作中一次最大只能写64K字节的数据,
下面是服务器端程序:
(模块中):
Public Declare Function CreateNamedPipe Lib "kernel32" Alias "CreateNamedPipeA" (ByVal lpName As String, ByVal dwOpenMode As Long, ByVal dwPipeMode As Long, ByVal nMaxInstances As Long, ByVal nOutBufferSize As Long, ByVal nInBufferSize As Long, ByVal nDefaultTimeOut As Long, ByVal lpSecurityAttributes As Long) As Long
Public Declare Function ConnectNamedPipe Lib "kernel32" (ByVal hNamedPipe As Long, ByVal lplong As Long) As Long
Public Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lplong As Long) As Long
Public Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, ByVal lplong As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public Declare Function WaitNamedPipe Lib "kernel32" Alias "WaitNamedPipeA" (ByVal lpNamedPipeName As String, ByVal nTimeOut As Long) As Long
Public Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Public Declare Function DisconnectNamedPipe Lib "kernel32" (ByVal hNamedPipe As Long) As Long
Public Declare Function GetFileSize Lib "kernel32" (ByVal hFile As Long, lpFileSizeHigh As Long) As Long
Public Const PIPE_ACCESS_DUPLEX = &H3
Public Const PIPE_ACCESS_INBOUND = &H1
Public Const PIPE_ACCESS_OUTBOUND = &H2
Public Const PIPE_CLIENT_END = &H0
Public Const PIPE_NOWAIT = &H1
Public Const PIPE_READMODE_BYTE = &H0
Public Const PIPE_READMODE_MESSAGE = &H2
Public Const PIPE_SERVER_END = &H1
Public Const PIPE_TYPE_BYTE = &H0
Public Const PIPE_TYPE_MESSAGE = &H4
Public Const PIPE_UNLIMITED_INSTANCES = 255
Public Const PIPE_WAIT = &H0
Public Const FILE_SHARE_READ = &H1
Public Const FILE_SHARE_WRITE = &H2
Public Const GENERIC_READ = &H80000000
Public Const GENERIC_WRITE = &H40000000
Public Const GENERIC_EXECUTE = &H20000000
Public Const GENERIC_ALL = &H10000000
Public Const OPEN_EXISTING = 3
Public Const ERROR_PIPE_BUSY = 231&
Public Const ERROR_PIPE_CONNECTED = 535&
Public Const ERROR_PIPE_LISTENING = 536&
Public Const ERROR_PIPE_NOT_CONNECTED = 233&
Public Const ERROR_NO_DATA = 232&
Public Const BufferSize& = 51200
Public hNamePipe&, hFile&, strNamePipe$
Form中有三个按钮,分别是“创建命名管道”(CreateNPipe)、“发送文件”(SendFile)、“关闭命名管道”(CloseNamePipe),窗口中还有一个CommonDialog控件,命名为“CDlg1”。Form中代码:
Dim outBuffer() As Byte, inBuffer() As Byte, BytesRead As Long, BytesWrite As Long, BytesReaded As Long, BytesWrited As Long
Private Sub CloseNamePipe_Click()
DisconnectNamedPipe hNamePipe
CloseHandle hNamePipe
CreateNPipe.Enabled = True
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub
Private Sub CreateNPipe_Click()
Dim hReturn&
strNamePipe = "\\.\pipe\xyvanPipe"
hNamePipe = CreateNamedPipe(strNamePipe, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE Or PIPE_READMODE_BYTE, 1, 0, 0, 0, 0)
If hNamePipe <> -1 Then
hReturn = ConnectNamedPipe(hNamePipe, 0)
If hReturn = 0 Then
MsgBox "管道无法等待客户端的连接!", vbInformation Or vbOKOnly
Unload Me
Else
Label1 = "已同客户机连接上!"
End If
CreateNPipe.Enabled = False
SendFile.Enabled = True
CloseNamePipe.Enabled = True
Else
MsgBox "无法创建命名管道!", vbInformation Or vbOKOnly
Unload Me
End If
End Sub
Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "请选择要传输的文件:"
.filename = ""
.Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNFileMustExist Or cdlOFNPathMustExist
.InitDir = "d:\"
End With
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
DisconnectNamedPipe hNamePipe
CloseHandle hFile
CloseHandle hNamePipe
End Sub
Private Sub SendFile_Click()
On Error Resume Next
Dim strFileName$, lpFileSize&, lpFileSizeHigh&, lpFileSizeLeast&, byteEnd() As Byte
Dim strShortName$
CDlg1.ShowOpen
If Err.Number = 32755 Then Exit Sub
strFileName = CDlg1.filename
strShortName = CDlg1.FileTitle
hFile = CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0)
If hFile = -1 Then
MsgBox "无法打开文件" & strFileName, vbInformation Or vbOKOnly
Exit Sub
End If
lpFileSize = GetFileSize(hFile, lpFileSizeHigh)
If lpFileSize = 0 Then
MsgBox "该文件大小为零,不用发送!", vbInformation Or vbOKOnly
CloseHandle hFile
Exit Sub
End If
lpFileSizeLeast = lpFileSize
byteEnd() = StrConv(strShortName, vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, byteEnd(0), UBound(byteEnd) + 1, BytesWrited, 0 '发送短文件名
ReDim inBuffer(5)
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0 '读取客户端对话信息
If StrConv(inBuffer, vbUnicode) = "Cancel" Then
MsgBox "客户端保存时选择了取消,发送终止!", vbInformation Or vbOKOnly
CloseHandle hFile
Exit Sub
End If
Label1.Caption = "正在传输中…"
While lpFileSize > 0
If lpFileSize > BufferSize Then
ReDim outBuffer(BufferSize - 1)
ReadFile hFile, outBuffer(0), BufferSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), BytesReaded, BytesWrited, 0
Else
ReDim outBuffer(lpFileSize - 1)
ReadFile hFile, outBuffer(0), lpFileSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), lpFileSize, BytesWrited, 0
End If
lpFileSize = lpFileSize - BytesReaded
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0
Wend
byteEnd() = StrConv("EOF", vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, outBuffer(0), 3, BytesWrited, 0
CloseHandle hFile
Label1 = "传送文件完毕!"
End Sub
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim I As Long
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub
客户端程序(模块中程序和服务器端是一样的,这里省略不写了),Form中有一个Text框,用以输入要打开连接的服务器端的命名管道的名称,一个CommonDialog(CDlg1)控件,另还有一“连接命名管道”(Connect)按钮和“断开连接”(Disconnect)按钮,程序如下:
Dim inBuffer() As Byte, BytesRead&, BytesReaded&, BytesWrited&, strFileName$
Private Sub Connect_Click()
Dim hRes&
strNamePipe = Text1
hRes = WaitNamedPipe(strNamePipe, -1)
If hRes = 0 Then
MsgBox "没有可用的命名管道以供连接!", vbInformation Or vbOKOnly
Exit Sub
End If
hNamePipe = CreateFile(strNamePipe, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hNamePipe = 0 Then
MsgBox "无法打开指定的命名管道进行读写!", vbInformation Or vbOKOnly
Exit Sub
End If
FileSave
End Sub
Private Sub Disconnect_Click()
CloseHandle hFile
CloseHandle hNamePipe
End Sub
Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "保存为:"
.FileName = ""
' .Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNOverwritePrompt
.InitDir = "d:\"
End With
End Sub
Private Sub FileSave()
BytesRead = 51200
Dim AckByte() As Byte
ReDim inBuffer(BytesRead - 1)
On Error Resume Next
Do
ReadFile hNamePipe, inBuffer(0), BytesRead, BytesReaded, 0
If BytesReaded < 258 Then
strFileName = Trim(StrConv(inBuffer, vbUnicode))
strFileName = Left(strFileName, InStr(strFileName, Chr(0)) - 1)
If strFileName Like "EOF*" And BytesReaded = 3 Then
CloseHandle hFile
MsgBox "文件接收完毕!", vbInformation Or vbOKOnly Or vbSystemModal
Exit Sub
Else
CDlg1.Filter = UCase(GetExtension(strFileName)) & "文件(*." & GetExtension(strFileName) & ")|*." & GetExtension(strFileName)
CDlg1.FileName = Left(strFileName, InStr(strFileName, ".") - 1)
ReSelect: CDlg1.ShowSave
If Err.Number = 32755 Then
AckByte() = StrConv("Cancel", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1, BytesWrited, 0
MsgBox "你选择了取消键!", vbInformation Or vbOKOnly
Exit Sub
End If
hFile = CreateFile(CDlg1.FileName, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = -1 Then
MsgBox "无法创建指定文件,请重新选择文件名!", vbInformation Or vbOKOnly
GoTo ReSelect
End If
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1, BytesWrited, 0
End If
Else
WriteFile hFile, inBuffer(0), BytesReaded, BytesWrited, 0
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1, BytesWrited, 0
End If
Loop Until 1 = 0
End Sub
Private Function GetExtension(ByVal FileName$) As String
GetExtension = Right(FileName, Len(FileName) - InStr(FileName, "."))
End Function
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim i&
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
CloseHandle hFile
CloseHandle hNamePipe
End Sub
该程序在VB5、Windows NT 4.0上调试通过。
在处理网络事务上,命名管道接口比Net BIOS要好,而且只需使用一个简单的调用就可达到目的,而无需通过Net BIOS执行许多操作。然而,命名管道接口并不提供Net BIOS的一些特征,如无连接数据报服务和允许向一个组发送消息的命名功能。
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/