注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:17 进程间通信  

2013-03-03 20:39:28|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:17 进程间通信 - jasonyang9 - jasonyang9的博客
 ==========
进程间通信
==========

一个进程运行后,操作系统为其分配4GB的私有地址空间。在一个进程中的各线程共享同一地址空间,因此线程间通信很简单。然而,进程间通信相对比较困难。

进程间通信的四种方式
* 剪贴板(单主机)
* 匿名管道(单主机)
* 命名管道(跨网络,单播)
* 邮槽(跨网络,广播)

剪贴板
------

新建MFC AppWizard工程,Clipboard,Dialog based。
删除原有控件,增加编辑框(IDC_EDIT_SEND),编辑框(IDC_EDIT_RECV),按钮(IDC_BTN_SEND,“发送”),按钮(IDC_BTN_SEND, “接收”)。
实现功能:按下“发送”按钮将IDC_EDIT_SEND编辑框中的内容发送到剪贴板,按下“接收”按钮将剪贴板中的内容填入IDC_EDIT_RECV编辑框。

相关函数
BOOL CWnd::OpenClipboard();
如果函数打开了剪贴板则返回非0,如果已有其他程序打开了剪贴板则返回0。如果剪贴板被打开,除非调用了CloseClipboard,否则其他应用程序不能修改。当前的CWnd对象不会成为剪贴板的拥有者,直到EmptyClipboard被调用。
Nonzero if the Clipboard is opened via CWnd, or 0 if another application or window has the Clipboard open.
Opens the Clipboard. Other applications will not be able to modify the Clipboard until the CloseClipboard Windows function is called.
The current CWnd object will not become the owner of the Clipboard until the EmptyClipboard Windows function is called.

BOOL EmptyClipboard(void);
清空剪贴板,释放剪贴板中数据的句柄,将剪贴板的拥有者设置为当前打开剪贴板的窗口。
(因为剪贴板是所有进程都可以访问的,如果在使用剪贴板前已有其他程序将数据放入到剪贴板,在打开剪贴板后,就需要调用EmptyClipboard来清空这些数据)
The EmptyClipboard function empties the clipboard and frees handles to data in the clipboard.
The function then assigns ownership of the clipboard to the window that currently has the clipboard open.

HANDLE SetClipboardData(
UINT uFormat, // clipboard format
HANDLE hMem // data handle
);
以指定的剪贴板格式,将数据放置到剪贴板。窗口必须是剪贴板的拥有者,并且已经调用过OpenClipboard函数。
(在响应WM_RENDERFORMAT和WM_RENDERALLFORMATS消息前,剪贴板拥有者不必在SetClipboardData函数前再调用OpenClipboard函数)
The SetClipboardData function places data on the clipboard in a specified clipboard format.
The window must be the current clipboard owner, and the application must have called the OpenClipboard function.
(When responding to the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages, the clipboard owner must not call OpenClipboard before calling SetClipboardData.)
UINT uFormat [in],指定剪贴板格式。可以是一个已注册格式,或任何标准剪贴板格式(如CF_TEXT表示文本格式)。
Specifies a clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
HANDLE hMem [in],指定格式数据的句柄。可以是NULL,表示窗口提供指定格式的剪贴板数据。如果窗口延迟提交,则必须处理WM_RENDERFORMAT和WM_RENDERALLFORMATS消息。
延迟提交是指当由于一个提供数据的进程创建了剪贴板数据之后,直到其他进程获取剪贴板数据前,这些数据都会占据内存空间。如果在剪贴板中存放的数据量太大,就会浪费内存空间,降低对资源的利用率。为了避免浪费,先由数据提供进程创建指定数据格式的空剪贴板数据块,直到有其他进程需要数据,或自身进程终止之前才真正提交数据。
Handle to the data in the specified format. This parameter can be NULL, indicating that the window provides data in the specified clipboard format(renders the format) upon request. If a window delays rendering, it must process the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages.
在调用SetClipboardData后,系统拥有了hMem所标识的对象。应用程序可以读取数据,但不能释放数据的句柄或锁定数据,直到调用了CloseClipboard函数(在此之后应用程序可以访问这些数据)。如果hMem函数标识一个内存对象,该对象必须是用带有GMEM_MOVEABLE标记的GlobalAlloc函数分配的。
After SetClipboardData is called, the system owns the object identified by the hMem parameter. The application can read the data, but must not free the handle or leave it locked until the CloseClipboard function is called.(The Application can access the data after calling CloseClipboard). If the hMem parameter identifies a memory object, the object must have been allocated using the GlobalAlloc function with the GMEM_MOVEABLE flag.

HGLOBAL GlobalAlloc(
UINT uFlags, // allocation attributes
SIZE_T dwBytes // number of bytes to allocate
);
在堆上分配指定大小的内存。Win32内存管理没有提供分开的本地堆和全局堆。
The GlobalAlloc function allocates the specified number of bytes from the heap. Win32 memory management does not provide a separate local heap and global heap.
UINT uFlags [in],标记,指定如何分配内存。GMEM_MOVEABLE分配可移动的内存。Win32中内存块在物理内存中总是不可移动的,但可在缺省的堆中移动。返回内存对象的句柄,用GlobalLock函数将句柄转换为指针。

LPVOID GlobalLock(
HGLOBAL hMem // handle to global memory object
);
对全局内存对象加锁,返回内存块第一个字节的指针。
每个内存对象的内部数据结构包含了一个初始化为0的锁计数,对于可移动的内存对象,GlobalLock函数对锁计数+1,GlobalUnlock函数对锁计数-1。一个进程每次调用GlobalLock后都必须调用GlobalUnlock。加锁的内存不能移动或废弃,除非调用GlobalReAlloc重新分配内存对象。被锁定的内存块保持锁定,直到锁计数为0,才可被移动或废弃。
The internal data structures for each memory object include a lock count that is initially zero. For moveable memory object, GlobalLock increments the count by one, and the GlobalUnlock function decrements the count by one. For each call that a process makes to GlobalLock for an object, it must eventually call GlobalUnlock.
Locked memory will not be moved or discarded, unless the memory object is reallocated by using the GlobalReAlloc function. The memory block of a locked memory object remains locked until its lock count is decremented to zero, at which time it can be moved or discarded.
用GMEM_FIXED分配的内存对象的锁计数总是0,这些对象返回的指针等于指定的句柄值。
Memory objects allocated with GMEM_FIXED always have a lock count of zero. For these objects, the value of the returned pointer is equal to the value of the specified handle.

HANDLE GetClipboardData(
UINT uFormat // clipboard format
);
从剪贴板接收指定格式的数据。剪贴板必须已经打开。(注意:返回的是剪贴板对象的句柄,有必要时用GlobalLock转换为指针)
The GetClipboardData function retrieves data from the clipboard in a specified format. The clipboard must have been opened previously.

BOOL IsClipboardFormatAvailable(
UINT format // clipboard format
);
判断剪贴板上是否包含了指定格式的数据。(如果有,返回非0;否则返回0)
The IsClipboardFormatAvailable function determines whether the clipboard contains data in the specified format.

void CClipboardDlg::OnBtnSend() // 发送数据到剪贴板
{
if (OpenClipboard()) // 尝试打开剪贴板
{
CString str; // CString对象
HANDLE hClip; // 句柄对象
char *pBuf; // 指向字符的指针

EmptyClipboard(); // 清空剪贴板,获取所有权
GetDlgItemText(IDC_EDIT_SEND, str); // 得到发送编辑框中的数据
hClip = GlobalAlloc(GMEM_MOVEABLE, str.GetLength() + 1); // 在堆上分配内存(注意GMEM_MOVEABLE,并多分配一个字节,否则最后一个字母会丢失)
pBuf = (char*)GlobalLock(hClip); // 对内存对象加锁,并返回内存地址(需要强制类型转换将LPVOID转为char*)
strcpy(pBuf, str); // 拷贝str中的数据到内存中
GlobalUnlock(hClip); // 解锁
SetClipboardData(CF_TEXT, hClip); // 将数据放置到剪贴板
CloseClipboard(); // 不要忘记CloseClipboard,以便让其他程序可以打开剪贴板
}
}

void CClipboardDlg::OnBtnRecv() // 从剪贴板接收数据
{
if (OpenClipboard()) // 尝试打开剪贴板
{
// 注意:接收时不要调用EmptyClipboard,否则数据被清空
if (IsClipboardFormatAvailable(CF_TEXT)) // 判断剪贴板上是否有CF_TEXT(文本)格式的数据
{
HANDLE hClip; // 句柄变量
char *pBuf; // 指向字符的指针

hClip = GetClipboardData(CF_TEXT); // 接收指定格式的数据
pBuf = (char*)GlobalLock(hClip); // 加锁,转换内存对象句柄为指针(需要强制类型转换将void*转为char*)
GlobalUnlock(hClip); // 解锁
SetDlgItemText(IDC_EDIT_RECV, pBuf); // 将接收到的内容放到接收编辑框中
CloseClipboard(); // 不要忘记CloseClipboard,以便让其他程序可以打开剪贴板
}
}
}

使用剪贴板实现进程间通信,比较简单,代码也比较少。

匿名管道
--------

相关函数

BOOL CreatePipe(
PHANDLE hReadPipe, // read handle
PHANDLE hWritePipe, // write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // security attributes
DWORD nSize // pipe size
);
创建一个匿名管道,返回读取和写入的句柄。
hReadPipe [out],返回读取句柄。
hWritePipe [out],返回写入句柄。
lpPipeAttributes [in],安全属性,判断返回的对象句柄是否可被继承。(这里不可像前几次课程中那样传入NULL,因为匿名管道只能够在父子进程间通讯,子进程只能从父进程继承匿名管道的句柄,然后利用句柄和父进程通信)
nSize [in],指定管道缓冲区大小,只是一个建议值。

BOOL CreateProcess(
LPCTSTR lpApplicationName, // name of executable module(指向空终止的字符串,可执行模块(程序)的名字,可以包括完整路径,也可以是相对路径,但不会搜索其他的路径和PATH变量,如果该参数为空,则取lpCommandLine中空格前的部分为模块名。必须指定扩展名,系统不会自动增加)
LPTSTR lpCommandLine, // command line string(指向空终止的字符串,是一个命令行,会搜索其他的路径和PATH变量,可以不加扩展名)
LPSECURITY_ATTRIBUTES lpProcessAttributes, // sp(新进程内核对象的安全性)
LPSECURITY_ATTRIBUTES lpThreadAttributes, // sp(新进程中主线程内核对象的安全性)
BOOL hInheritHandles, // handle inheritance option(指定新进程是否可以从调用函数的进程继承句柄)
DWORD dwCreationFlags, // creation flags(指定控制优先级类和进程创建的附加标记)
LPVOID lpEnvironment, // new environment block(指向环境块的指针)
LPCTSTR lpCurrentDirectory, // current directory name(指定当前的路径名称)
LPSTARTUPINFO lpStartupInfo, // startup information(指向一个STARTUPINFO结构体的指针,指定新进程如何被启动等)
LPPROGRESS_INFORMATION lpProcessInformation // process information(指向一个PROCESS_INFORMATION结构体的指针,用来接收新进程的标识信息)
);
创建一个新的进程和其主线程。

typedef struct _STARTUPINFO {
DWORD cb; // 指定结构体大小(在使用结构体时,如果有类似的参数就应该为其赋值)
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags; // 指定使用那些成员,而其他成员被忽略(如STARTF_USESTDHANDLES,则hStdInput、hStdOutput和hStdError用于传递标准输入、输出和错误句柄,其他参数被忽略)
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 新创建的进程句柄
HANDLE hThread; // 新创建的进程的主线程句柄
DWORD dwProcessId; // 全局进程标识符,用来标识一个进程
DWORD dwThreadId; // 全局线程标识符,用来标识一个线程
} PROCESS_INFORMATION;

进程标识符和线程标识符在程序运行时由系统分配,在运行中,它们在系统中是唯一的,但程序终止后,这些标识符可能被其他进程使用。


typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; // 结构体的大小
LPVOID lpSecurityDescriptor; // 安全描述符的指针
BOOL bInheritHandle; // 指定所返回的句柄是否可被继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

新建一个MFC AppWizard,Parent,Single Document。
增加菜单“匿名管道”,“创建管道”(IDM_PIPE_CREATE),“读取数据”(IDM_PIPE_READ),“写入数据”(IDM_PIPE_WRITE),在View类中增加命令响应函数。
在View类中增加HANDLE hRead, hWrite; 句柄变量。

CParentView::CParentView()
{
hRead = NULL; // 在View类构造函数中初始化句柄
hWrite = NULL;
}

CParentView::~CParentView()
{
if (hRead) // 在View类析构函数中关闭句柄
CloseHandle(hRead);
if (hWrite)
CloseHandle(hWrite);
}

void CParentView::OnPipeCreate()
{
SECURITY_ATTRIBUTES sa; // 定义安全结构体

sa.bInheritHandle = TRUE; // 句柄可继承
sa.lpSecurityDescriptor = NULL; // 安全描述符默认
sa.nLength = sizeof(SECURITY_ATTRIBUTES); // 长度

if (!CreatePipe(&hRead // 创建匿名管道,第一个参数是读取句柄
&hWrite, // 写入句柄
&sa, // 安全属性
0)) // 管道缓冲区大小(0表示默认)
{
MessageBox("创建匿名管道失败!"); // 如失败则提示
return;
}

// 如果创建匿名管道成功,则接着创建子进程,然后将读写句柄传递给它

STARTUPINFO sui; // STARTUPINFO结构体变量
PROCESS_INFORMATION pi; // PROCESS_INFORMATION结构体变量

ZeroMemory(&sui, sizeof(STARTUPINFO)); // 将sui置零
sui.cb = sizeof(STARTUPINFO); // 结构体大小
sui.dwFlags = STARTF_USESTDHANDLES; // 结构体中的标准输入、输出和错误句柄成员有效,其他忽略
sui.hStdInput = hRead; // 将子进程的标准输入句柄设为匿名管道的读取句柄(当用第5个参数是TRUE的CreateProcess创建子进程时,子进程将继承父进程中所有可被继承的打开的句柄,但父进程中的具体哪个句柄被继承为哪个句柄需要在此处指定,这里将子进程的标准输入句柄设置为管道的读句柄,标准输出句柄设置为管道的写句柄)
sui.hStdOutput = hWrite; // 将子进程的标准输出句柄设为匿名管道的写入句柄
sui.hStdError = GetStdHandle(STD_ERROR_HANDLE); // 将子进程的标准错误句柄设为父进程的标准错误句柄(虽然这里不会使用,但要清楚具体是哪个句柄)

if(!CreateProcess("..\\Child\\Debug\\Child.exe", // 创建子进程,第一个参数是子进程程序的路径名称(这里用了相对路径)
NULL, // 由于已经使用第一个参数来指定可执行模块,第二个参数就不需要了
NULL, // 进程安全属性
NULL, // 线程安全属性
TRUE, // 可以从调用函数的进程继承句柄
0, // 默认优先级和进程创建的附加标记
NULL, // 环境块(默认)
NULL, // 当前路径(默认,和父进程相同)
&sui, // 一个STARTUPINFO结构体的指针,指定新进程如何被启动等
&pi)) // 接收新进程的标识信息
{
CloseHandle(hRead); // 关闭句柄
CloseHandle(hWrite); // 关闭句柄
hRead = NULL; // 设置为NULL(避免在析构函数中再次被关闭)
hWrite = NULL;
MessageBox("创建子进程失败!"); // 如果失败则提示
}
else
{
CloseHandle(pi.hProcess); // 关闭子进程的句柄(由于在创建新进程时,系统建立进程和线程内核对象,其中有一个使用计数,创建初始为1。在CreateProcess返回前,它打开进程对象和线程对象,将他们的句柄放入PROCESS_INFORMATION结构体变量pi的hProcess和hThread中,这将使得使用计数递增为2。在父进程中如果不需要使用这2个句柄应将其关闭,使用计数递减为1,当子进程终止运行时,系统再次递减使用计数为0,那么这2个内核对象就可以被释放)
CloseHandle(pi.hThread); // 关闭子进程中主线程的句柄
}
}

void CParentView::OnPipeRead()
{
char buf[100]; // 字符数组,用于存放读取进来的数据
DWORD dwRead; // 保存实际读取的字节数

if (!ReadFile(hRead, buf, 100, &dwRead, NULL)) // 从hRead读取100个字节到buf,实际读取的字节数保存到dwRead(最后一个参数是重叠结构体)
{
MessageBox("读取数据失败!"); // 读取失败则提示
return; // 并返回
}

MessageBox(buf); // 读取成功则显示数据
}

void CParentView::OnPipeWrite()
{
char buf[] = "http://www.sunxin.org"; // 字符数组,内容是要发送的数据
DWORD dwWrite; // 保存实际发送的字节数

if (!WriteFile(hWrite, buf, strlen(buf) + 1, &dwWrite, NULL)) // 对hWrite写入buf长度+1个字节,实际写入的字节数保存到dwWrite(最后一个参数是重叠结构体)
{
MessageBox("写入数据失败!"); // 写入失败则提示
return; // 并返回
}
}

以上就是父进程的全部内容,主要是调用OnPipeCreate创建管道和子进程,并将子进程的标准输入句柄设为匿名管道的读句柄、标准输出句柄设为匿名管道的写句柄,在OnPipeRead中通过匿名管道的hRead句柄从子进程读取数据,在OnPipeWrite中通过匿名管道的hWrite句柄对子进程发送数据。

新建一个MFC AppWizard,Child,Single Document。
增加菜单“匿名管道”,“读取数据”(IDM_PIPE_READ),“写入数据”(IDM_PIPE_WRITE),在View类中增加命令响应函数。
在View类中增加HANDLE hRead, hWrite; 句柄变量。
对View类增加虚函数OnInitialUpdate(这是一个在窗口完全创建完成后第一个调用的函数,可在其中初始化变量)。

CChildView::CChildView()
{
hRead = NULL; // 在View类构造函数中将2个句柄初始化为NULL
hWrite = NULL;
}

CChildView::~CChildView()
{
if (hRead) // 在View类析构函数中关闭句柄
CloseHandle(hRead);
if (hWrite)
CloseHandle(hWrite);
}

void CChildView::OnInitialUpdate()
{
CView::OnInitialUpdate();

hRead = GetStdHandle(STD_INPUT_HANDLE); // hRead赋值为标准输入句柄(也就是管道的读取句柄,这个在父进程创建该子进程时已设置)
hWrite = GetStdHandle(STD_OUTPUT_HANDLE); // hWrite赋值为标准输出句柄(也就是管道的写入句柄)
}

void CChildView::OnPipeRead()
{
char buf[100]; // 字符数组,用于存放读取进来的数据
DWORD dwRead; // 保存实际读取的字节数

if (!ReadFile(hRead, buf, 100, &dwRead, NULL)) // 从hRead读取100个字节到buf,实际读取的字节数保存到dwRead(最后一个参数是重叠结构体)
{
MessageBox("读取数据失败!"); // 读取失败则提示
return; // 并返回
}

MessageBox(buf); // 读取成功则显示数据
}

void CChildView::OnPipeWrite()
{
char buf[] = "匿名管道测试程序"; // 字符数组,内容是要发送的数据
DWORD dwWrite; // 保存实际发送的字节数

if (!WriteFile(hWrite, buf, strlen(buf) + 1, &dwWrite, NULL)) // 对hWrite写入buf长度+1个字节,实际写入的字节数保存到dwWrite(最后一个参数是重叠结构体)
{
MessageBox("写入数据失败!"); // 写入失败则提示
return; // 并返回
}
}

至此,子进程的代码也写完了。但在启动父子进程时要注意,匿名管道只能在利用CreateProcess创建的父子进程间进行通信,如果子进程是单独启动的,则它们之间实际上不存在父子关系,无法利用匿名管道来通信。
应先启动父进程(Parent),利用其“匿名管道”|“创建管道”菜单来启动子进程。(正是由于匿名管道没有名字,只能通过这种CreateProcess的方法使父进程将匿名管道的读写句柄传递给子进程,以实现通信)

1、调用父进程中的“写入数据”后就可在子进程中用“读取数据”来接收到父进程通过匿名管道发送过来的“http://www.sunxin.org”;
2、调用子进程中的“写入数据”后也可在父进程中用“读取数据”来接收到子进程通过匿名管道发送过来的“匿名管道测试程序”;
3、调用父进程中的“写入数据”后还可在父进程中用“读取数据”来接收到自己通过匿名管道发送的“http://www.sunxin.org”;(??)
4、第3条对子进程同样适用。(??)

疑问(??):如此看来匿名管道中数据的流向比较混乱,父进程中设置sui.hStdInput = hRead,sui.hStdOutput = hWrite,对应子进程中hRead = GetStdHandle(STD_INPUT_HANDLE),hWrite = GetStdHandle(STD_OUTPUT_HANDLE)。在父进程中对hWrite写入数据,在子进程中竟然可以通过hRead句柄读出数据,反之亦然。另外同一个进程对hWrite写入的数据也可以自己的hRead读出?
提示:ReadFile是一个同步函数,如果管道中没有数据,则函数不会返回,线程阻塞;WriteFile是一个同步函数,如果管道缓冲区已满,则函数不会返回,线程阻塞。

命名管道
--------

关于命名管道
* 命名管道是通过网络(不仅在本地,也可以跨网络)来完成进程间的通信,它屏蔽了底层的网络协议细节,在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信。
* 命名管道充分利用了Windows NT和Windows 2000内建的安全机制。
* 在将命名管道作为一种网络编程方案时,它实际上建立了一个客户机/服务器通信体系,并在其中可靠地传输数据。
* 命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统(Named Pipe File System,NPFS)”接口,因此,客户机和服务器可以利用标准的Win32文件系统函数(如:ReadFile和WriteFile)来进行数据的收发。
* 命名管道服务器和客户机的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接收管道客户机的连接请求。而客户机只能同一个现有的命名管道服务器建立连接。
* 命名管道服务器只能在Windows NT或Windows 2000上创建,所以,无法在两台Windows 95或Windows 98计算机之间利用管道进行通信。但客户机可以是Windows 95或Windows 98计算机,来和Windows NT或Windows 2000计算机(服务器)进行连接通信。
* 命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式,在客户机和服务器之间流动。在消息模式中,客户机和服务器通过一系列不连续的数据单位进行数据的收发,每次在管道上发出了一条消息后,它必须作为一条完整的消息读入。

相关函数

HANDLE CreateNamedPipe(
LPCTSTR lpName, // pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size
DWORD nInBufferSize, // input buffer size
DWORD nDefaultTimeOut, // time-out interval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD
);
创建一个命名管道实例,返回管道的句柄。或者创建一个已有命名管道的新的实例。

lpName [in],指向空终止的字符串,唯一的管道标识。必须是这种形式:\\.\pipe\pipename。
dwOpenMode [in],指定管道访问、重叠、写入和安全的模式。
dwPipeMode [in],指定管道的模式(字节模式或消息模式,消息模式有定界符,字节模式没有)。
nMaxInstances [in],指定管道能够创建的实例的最大数(所有的实例必须指定同样的值)。
nOutBufferSize [in],为输出缓冲区保留的字节。
nInBufferSize [in],为输入缓冲区保留的字节。
nDefaultTimeOut [in],指定缺省的超时值(所有实例必须指定同样的值)。
lpSecurityAttributes [in],安全属性。

BOOL ConnectNamedPipe(
HANDLE hNamedPipe, // handle to named pipe
LPOVERLAPPED lpOverlapped // overlapped structure
);
允许一个命名管道服务器进程等待客户端进程连接。(注意:不要被函数名误导,这个函数实际上是让服务器上的命名管道等待客户端的连接)
hNamedPipe [in],命名管道的句柄
lpOverlapped [in],指向OVERLAPPED结构体的指针。(如果命名管道是用FILE_FLAG_OVERLAPPED标记打开的,则必须指向一个有效的OVERLAPPED结构体的指针,且这个结构体中必须包含一个指向人工重置的事件对象的句柄)

BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
DWORD nTimeOut // time-out interval
);
等待命名管道,直到可以连接(命名管道的服务器进程在等待连接)。
lpNamedPipeName [in],命名管道的名称。
nTimeOut [in],超时(NMPWAIT_USE_DEFAULT_WAIT使用服务器端指定的超时设定,NMPWAIT_WAIT_FOREVER永远等待直到可以连接)。

新建一个MFC AppWizard,NamedPipeSrv,Single Document。
新建菜单“命名管道”,“创建管道”(IDM_PIPE_CREATE),“读取数据”(IDM_PIPE_READ),“写入数据”(IDM_PIPE_WRITE),在View类中增加命令响应。
在View类增加成员变量HANDLE hPipe;。

CNamedPipeSrvView::CNamedPipeSrvView()
{
hPipe = NULL; // 在View类构造函数中初始化
}

CNamedPipeSrvView::~CNamedPipeSrvView()
{
if (hPipe) // 在View类析构函数中判断并CloseHandle
CloseHandle(hPipe);
}

void CNamedPipeSrvView::OnPipeCreate()
{
hPipe = CreateNamedPipe("\\\\.\\pipe\\MyPipe", // 创建命名管道,第一个参数是唯一的管道标识
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // 双向管道(读、写)和重叠模式
0, // 模式(0表示PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,即字节模式)
1, // 管道实例的最大数目
1024, // 输出缓冲区大小
1024, // 输入缓冲区大小
0, // 超时
NULL); // 安全属性

if (INVALID_HANDLE_VALUE == hPipe) // 如果创建失败
{
MessageBox("创建命名管道失败!"); // 提示
hPipe = NULL; // 将hPipe置为NULL,以防止在析构函数中调用CloseHandle
return;
}

HANDLE hEvent; // 事件对象句柄

hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建一个没有安全属性的、人工重置的、初始化为非信号状态的、匿名的事件对象

if (!hEvent)
{
MessageBox("创建事件对象失败!");
CloseHandle(hPipe);
hPipe = NULL;
return;
}

OVERLAPPED ovlap; // OVERLAPPED结构体变量

ZeroMemory(&ovlap, sizeof(OVERLAPPED)); // 置零
ovlap.hEvent = hEvent; // 将结构体中的变量hEvent赋值为事件对象的句柄

if (!ConnectNamedPipe(hPipe, &ovlap)) // 等待客户端的连接
{
if (ERROR_IO_PENDING != GetLastError()) // 如果ConnectNamedPipe返回0,但GetLastError返回ERROR_IO_PENDING表示没有失败(Overlapped I/O operation is in progress.)
{
MessageBox("等待客户端连接失败!");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}
}

if (WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE)) // 等待事件对象变为有信号状态
{
MessageBox("等待对象失败!");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}

CloseHandle(hEvent); // 等到了事件对象则表示已有客户端连接命名管道实例(关闭事件对象句柄)
}

void CNamedPipeSrvView::OnPipeRead()
{
char buf[100];
DWORD dwRead;

if (!ReadFile(hPipe, buf, 100, &dwRead, NULL))
{
MessageBox("读取数据失败!");
return;
}

MessageBox(buf);
}

void CNamePipeSrvView::OnPipeWrite()
{
char buf[] = "http://www.sunxin.org";
DWORD dwWrite;

if (!WriteFile(hPipe, buf, strlen(buf) + 1, &dwWrite, NULL))
{
MessageBox("写入数据失败!");
return;
}
}

以上是命名管道服务器端程序代码。

新建一个MFC AppWizard,NamedPipeClt,Single Document。
增加菜单“命名管道”,“连接管道”(IDM_PIPE_CONNECT),“读取数据”(IDM_PIPE_READ),“写入数据”(IDM_PIPE_WRITE),在View类中增加命令响应。
在View类中增加成员变量HANDLE hPipe;。

CNamedPipeCltView::CNamedPipeCltView()
{
hPipe = NULL;
}

CNamedPipeCltView::~CNamedPipeCltView()
{
if (hPipe)
CloseHandle(hPipe);
}

void CNamedPipeCltView::OnPipeConnect()
{
if (!WaitNamedPipe("\\\\.\\pipe\\MyPipe", NMPWAIT_WAIT_FOREVER)) // 等待服务器端的命名管道可以连接
{
MessageBox("当前没有可利用的命名管道实例!");
return;
}

hPipe = CreateFile("\\\\.\\pipe\\MyPipe", // 用CreateFile打开命名管道,一个参数是管道的名字
GENERIC_READ | GENERIC_WRITE, // 读、写访问
0, // 共享(不共享)
NULL, // 安全属性
OPEN_EXISTING, // 创建方式为打开已有
FILE_ATTRIBUTE_NORMAL, // 文件属性为默认
NULL); // 无临时文件

if (INVALID_HANDLE_VALUE == hPipe) // 如果打开失败
{
MessageBox("打开命名管道失败!");
hPipe = NULL;
return;
}
}

void CNamedPipeCltView::OnPipeRead()
{
char buf[100];
DWORD dwRead;

if (!ReadFile(hPipe, buf, 100, &dwRead, NULL))
{
MessageBox("读取数据失败!");
return;
}

MessageBox(buf);
}

void CNamedPipeCltView::OnPipeWrite()
{
char buf[] = "命名管道测试程序";
DWORD dwWrite;

if (!WriteFile(hPipe, buf, strlen(buf) + 1, &dwWrite, NULL))
{
MessageBox("写入数据失败!");
return;
}
}

采用命名管道通信的进程不需要任何关系,单独启动这两个进程即可进行通信。先在服务器端进程创建管道,再到客户端进程连接管道,然后就可以相互通信了。
注意:如果服务器端和客户端程序不在同一台主机上,则命名管道名称中的.需要改为对方主机的主机名称。

邮槽
----

关于邮槽
* 邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。
* 邮槽是一种单项通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。
* 为保证邮槽在各种Windows平台下都能够正常工作,在传输消息的时候,消息长度应限制在424字节以下。

相关函数

HANDLE CreateMailslot(
LPCTSTR lpName, // mailslot name
DWORD nMaxMessageSize, // maximum message size
DWORD lReadTimeout, // read time-out interval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // inheritance option
);
用指定的名字创建邮槽,返回句柄。
lpName [in],空终止的字符串,指定邮槽的名字,必须是这种形式:\\.\mailslot\[path]name
nMaxMessageSize [in],指定可被写入邮槽的单一消息最大尺寸(0表示任意)。
lReadTimeout [in],读取超时。
lpSecurityAttributes [in],安全属性。

新建一个MFC AppWizard,MailslotSrv,Single Document。
增加菜单“接收数据”(IDM_MAILSLOT_RECV),对View类增加命令响应。

void CMailslotSrvView::OnMailslotRecv()
{
HANDLE hMailslot;

hMailslot = CreateMailslot("\\\\.\\mailslot\\MyMailslot", // 创建邮槽
0, // 消息长度任意
MAILSLOT_WAIT_FOREVER, // 超时为一直等待
NULL); // 安全属性

if (INVALID_HANDLE_VALUE == hMailslot)
{
MessageBox("创建邮槽失败!");
return;
}

char buf[100]; // 创建成功则读取数据
DWORD dwRead;

if (!ReadFile(hMailslot, buf, 100, &dwRead, NULL))
{
MessageBox("读取数据失败!");
CloseHandle(hMailslot);
return;
}

MessageBox(buf);
CloseHandle(hMailslot);
}

新建一个MFC AppWizard,MailslotClt,Single Document。
增加菜单“发送数据”(IDM_MAILSLOT_SEND),对View类增加命令响应。

void CMailslotCltView::OnMailslotSend()
{
HANDLE hMailslot;

hMailslot = CreateFile("\\\\.\\mailslot\\MyMailslot", // 用CreateFile打开邮槽,第一个参数是邮槽的名字
GENERIC_WRITE, // 写访问(邮槽客户端只能写)
0, // 共享(不共享)
NULL, // 安全属性
OPEN_EXISTING, // 创建方式为打开已有
FILE_ATTRIBUTE_NORMAL, // 文件属性为默认
NULL); // 无临时文件

if (INVALID_HANDLE_VALUE == hMailslot)
{
MessageBox("打开邮槽失败!");
return;
}

char buf[] = "http://www.sunxin.org"; // 打开成功则写入数据
DWORD dwWrite;

if (!WriteFile(hMailslot, buf, strlen(buf) + 1, &dwWrite, NULL))
{
MessageBox("写入数据失败!");
CloseHandle(hMailslot);
return;
}

CloseHandle(hMailslot);
}

将服务器端和客户端程序分别编译运行后,先在服务器端程序上点击“接收数据”,再到客户端程序上“发送数据”,即可实现通讯。但这只是单向的通讯,要实现双向通讯,应在两个程序中都实现服务器端和客户端。用服务器端代码接收数据,客户端代码发送数据。
注意:如果服务器端和客户端程序不在同一台主机上,则邮槽的名称中的.需要改为对方主机的主机名称。
提示:用邮槽实现广播通信非常方便。
  评论这张
 
阅读(387)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017