📄 17.3.2 服务器端程序.txt
字号:
17.3.2 服务器端程序
下面,我们就利用命名管道实现进程间的通信。首先实现服务器端程序,新建一个单文档类型的 MFC
应用程序,工程取名为: NamedPipeSrv。然后,为该工程增加一个子菜单,名称为"命名管道"。接
着,为该子菜单添加三个菜单项,并分别为它们添加相应的命令响应函数,本例选择
CNamedPipeSrvView类接收这些命令响应函数。各菜单项的 E、名称,以及响应函数如表 17.13所示。
ID 菜单名称 响应函数
IDM_PIPE_CREATE 创建管道 OnPipeCreate
IDM_PIPE_READ 读取数据 OnPipeRead
IDM_PIPE_WRITE 写入数据 OnPipeWrite
接下来,为 CNamedPipeSrvView类增加一个句柄变量,用来保存创建的命名管道实例的句柄。
private:
HANDLE hPipe;
在 CNamedPipeSrvView类的构造函数中将其初始化为 NULL:
CNamedPipeSrvView :: CNamedPipeSrvView ()
// TODO: add construction code here
hPipe =NULL ;
然后在 CNamedPipeSrvView类的析构函数中,如果判断该句柄有值,则调用 CloseHandle函数关闭
该句柄 :
CNarnedPipeSrvView:: -CNamedPipeSrvView ()
if(hPipe)
CloseHandle(hPipe) ;
1.创建命名管道
接下来,在 OnPipeCreate函数中就可以调用 CreateNamedPipe函数创建命名管道了。具体代码如例
17-9所示。
例17-9 ,
void CNamedPipeSrvView : :OnPipeCreate()
// TODO: Add your cornrnand handler code here
//创建命名管道
hPipe=CreateNamedPipe("\\\\ .\\pipe\\MyPipe" , PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
0, 1 , 1024 , 1024 , 0,NULL) ;
if(INVALID_HANDLE_VALUE==hPipe)
MessageBox ( "创建命名管道失败 ! ") ;
hPipe=NULL ;
return;
//创建匿名的人工重置事件对象
HANDLE hEvent;
hEvent=CreateEvent(NULL, TRUE , FALSE ,NULL) ;
if ( !hEvent)
MessageBox("创建事件对象失败!");
CloseHandle(hPipe);
hPipe=NULL;
return;
}
OVERLAPPED ovlap;
ZeroMemory(&ovlap , sizeof(OVERLAPPED));
ovlap.hEvent=hEvent;
//等待客户端请求的到来
if(!ConnectNamedPipe(hPipe, &ovlap))
if(ERROR_IO_PENDING!=GetLastError())
MessageBox ("等待客户端连接失败!");
CloseHandle(hPipe) ;
CloseHandle(hEvent);
hPipe=NULL;
return;
}
if(WAIT_FAILED==WaitForSingleObject(hEvent , INFINITE) ) {
MessageBox("等待对象失败!");
CloseHandle(hPipe);
CloseHandle(hEvent) ;
hPipe=NULL;
return;
CloseHandle(hEvent);
,如果想要指定两个反斜杠,那么在代码中就需要输入四个反斜杠。
所以,在如例 17-9所示代码中调用 CreateNamedPipe函数时,将管道的名称指定为:
"\\.\pipe\hPipe";管道访问的模式设定为 PIPE_ACCESS_DUPLEX,即双向模式,服
务器进程和客户端进程都可以从管道读取数据和向管道中写入数据,同时指定
FILE_FLAG_OVERLAPPED标志,允许重叠方式:第三个参数用来指定管道类型、读取
和等待方式,本例将其值设为 0,即默认为字节类型和字节读方式:第四个参数用来指定
管道实例的最大数目,本例设置为1,因为本程序是一个测试程序,只需要一个客户端连
接就可以了:第五个和第六个参数分别用来指定输出缓冲区大小和输入缓冲区大小,本例
都设置为 1024;第七个参数指定超时值,本例设为 0;最后一个参数指定安全属性,本例
设置为 NULL,让管道句柄使用默认的安全性。
如果 CreateNamedPipe函数调用成功,它将返回一个有效的管道句柄;否则返回
INVALID_HANDLE_VALUE,可以调用 GetLas tError函数获得更多的错误信息。因此在程序中可以对
CreateNamedPipe函数的返回值进行判断,如果失败,则提示用户:"创建命名管道失败!",接着,将
管道句柄变量 ChPipe)设置为 NULL,这样是为了避免程序失败时在 CNamedPipeSrvView对象的析构
函数中再次调用 CloseHandle函数关闭这个句柄,然后让 OnPipeCreate函数直接返回:如果成功创
建了命名管道的实例,就可以调用 ConnectNamedPipe函数,等待客户端请求的到来。这个函数允许
一个服务端进程等待一个客户端进程连接到一个命名管道的一个实例上。这个函数的命名不太好,
给人的直觉好像去连接服务器端的命名管道,实际上这个函数的作用是让服务器等待客户端的连接
请求的到来。该函数声明如下所示:
BOOL ConnectNamedPipe(HANDLE hNamedPipe , LPOVERLAPPED lpOverlapped);
ConnectNamedPipe函数有两个参数,其含义分别如下所述。
. hNamedPipe
指向一个命名管道实例的服务器的句柄,该句柄由 CreateNamedPipe函数返回。
. lpOverlapped
指向一个 OVERLAPPED结构的指针,如果 hNamedPipe参数所标识的管道是用 FILE-FLAG-OVERLAPPED
标记打开的,则这个参数不能是 NULL,必须是一个有效的指向一个 OVERLAPPED结构的指针;否则该
函数可能会错误地执行。如果hNamedPipe参数所标识的管道是用 FILE-FLAG-OVERLAPPED标记打开
的,并且这个参数不是 NULL,则这个参数所指向的 OVERLAPPED结构体中必须包含人工重置事件对
象句柄。
于是,上述 OnPipeCreate函数调用 CreateEvent的数创建了一个匿名的人工重置事件对象句柄(注
意:第二个参数一定要指定为 TRUE) o CreateEvent函数如果调用失败,将返回 NULL,所以对该函
数的返回值进行判断,如果调用失败,则提示用户:"创建事件对象失败!",并在调用 return语句让
OnPipeCreate函数返回之前,调用 CloseHandle函数关闭命名管道的句柄,然后将其设置为 NULL,
原因前面已经提过了,主要是为了避免程序关闭时,在 CNamedPipeSrvView对象的析构函数中再次
调用 CloseHandle函数关闭这个句柄。
如果成功创建了匿名的人工重置事件对象,那么接下来就定义一个 OVERLAPPED结构体类型的变量:
ovlap,虽然程序中片需要使用到该变量的事件对象句柄成员 C hEvent ),但是首先应该将 ovlap
变量中所有成员都设置为 0,以免它们影响函数运行的结果,然后将 hEvent成员设置为刚刚创建的
一个有效的人工重置事件对象句柄。
接着就可以调用 Connec tN amedPipe函数等待客户端请求的到来,该函数的第一个参数就是前面调
用 CreateNamedPipe函数返回的一个有效的命名管道句柄,第二个参数就是指向 OVERLAPPED结构体
变量的指针,即 ovlap变量的地址。
如果 ConnectNamedPipe函数调用失败,它将返回 O值,但其中有一种特殊情况并不表明等待连接事
件失败了,也就是说,如果这时调用 GetLastError函数返回 ERROR_
IO_PENDING.那么并不表示 ConnectNamedPipe函数失败了,只是表明这个操作是一个未决的操作,
在随后的某个时间这个操作可能能够完成。因此在程序中,当 ConnectNamedPipe函数返回 O时,还
应调用 GetLastError函数,并对其返回值进行判断,如果不是 ERROR_IO_PENDING.才说明
ConnectNamedPipe函数调用失败,这时提示用户:"等待客户端连接失败!",然后调用 CloseHandle
函数分别关闭管道句柄和事件对象句柄,井将管道句柄设置为 NULL.之后调用 retum语句返回。
如果上述操作都成功了,那么这时调用 WaitForSingleObject函数等待事件对象 (hEvent)变为有信
号状态。读者应注意,前面我们己将该事件对象句柄赋给了。vlap变量的hEvent成员,也就是说,
这两个变量: hEvent和 ovlap.hEvent.现在标识的是同一个对象,因此在调用
WaitForSingleObject函数时,采用这两个对象中的任一个都是可以的。本例将
WaitForSingleObject函数的第二个参数设置为 INFINITE,即让线程永远等待,直到所等待的事件
对象变为有信号状态。
同样的,应该对 WaitForSingleObject函数的返回值进行判断,如果调用失败,则提示用户"等待事
件对象失败!",然后关闭相关的句柄,并将管道旬柄设置为 NULL.之后调用 retum语句返回。
最后,当请求到所等待的事件对象后,也就是当该事件对象变成有信号状态时,说明已经有一个客
户端连接到命名管道的实例上了。这时,不再需要该事件对象句柄了,可以调用 CloseHandle函数
将它关闭。
2.读
对于命名管道的数据读取操作,与上面匿名管道的读取操作是一样的,因此可以直接复制己实现的
代码,然后将 ReadFile函数的第一个参数修改为本例创建的命名管道的句柄即可,结果如例 17-10
所示。
例 17-10
void CNamedPipeSrvView: :OnPipeRead()
(
// TODO: Add your command handler code here
char buf [1 00] ;
DWORD dwRead;
if(!ReadFile(hPipe, buf , 100 , &dwRead,NULL))
MessageBox ( "读取数据失败!");
return;
MessageBox(buf);
3.写入数据
对于命名管道的数据写入操作,与上面匿名管道的写入操作是一样的,所以可以直接复制己实现的
代码,然后将 WriteFi1e函数的第一个参数修改为本例创建的命名管道的句柄
即可,结果如例 17-11所示。
1?IJ 17-11
void CNarnedPipeSrvView: :OnPipeWrite()
11 TODO : Add your cornrnand handler code here
char buf [1 = ''http ://www . sunxin. org" ;
DWORD dwWrite;
if(!WriteFile(hPipe, buf, strlen(buf ) +1, &dwWrite, NULL) )
MessageBox ( "写入数据失败!");
return;
至此我们就完成了利用命名管道实现进程间通信的服务器端程序,利用 Build命令生成
NamedPipeSrv程序。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -