用命名管道实现局域网上两台主机间的文件拷贝

    技术2022-05-11  75

    能实现局域网上两台主机间文件拷贝的方法有很多种,这里介绍的“命名管道”(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的一些特征,如无连接数据报服务和允许向一个组发送消息的命名功能。 

    最新回复(0)