进程 通信,进程间通信代码
静态char client _ pipe _ name[PATH _ MAX 1]={ /0 };
static int client _ FD=-1;
static int client _ write _ FD=-1;
服务器端功能
接下来,我们需要讨论服务器端函数。下面的实验部分向我们展示了客户端打开和关闭一个已知管道以及读取消息的功能。第二个实验部分展示了打开、发送和关闭客户端管道的代码,这是基于进程ID的。
实验服务器功能
1 _ server _ starting例程创建一个命名管道,服务器将从该管道读取命令。然后打开这个管道进行阅读。打开操作将一直阻塞,直到客户端打开管道进行写入。我们使用阻塞模式,以便服务器可以在等待发送给它的命令时对管道执行阻塞读取。
int server_starting(void)
{
#if调试跟踪
printf(" % d:-server _ starting()/n ",getpid());
#endif
unlink(SERVER _ PIPE);
if (mkfifo(SERVER_PIPE,0777)==-1) {
fprintf(stderr,“服务器启动错误,未创建FIFO/n”);
return(0);
}
if ((server_fd=open(SERVER_PIPE,O_RDONLY))==-1) {
if (errno==EINTR)返回(0);
fprintf(stderr,“服务器启动错误,无FIFO打开/n”);
return(0);
}
返回(1);
}
当服务器完成时,他删除命名管道,这样客户端就可以检测到没有服务器在运行。
void server_ending(void)
{
#if调试跟踪
printf("%d :- server_ending()/n ",getpid());
#endif
(void)close(server _ FD);
(void)unlink(SERVER _ PIPE);
}
4以下代码中显示的read_request_from_client函数将阻止服务器管道中的读取,直到客户端向其写入消息:
int read_request_from_client(消息_数据库_ t *记录_指针)
{
int return _ code=0;
int读取字节数;
#if调试跟踪
printf(" % d:-read _ request _ from _ client()/n ",getpid());
#endif
if (server_fd!=-1) {
read_bytes=read(server_fd,rec_ptr,sizeof(* rec _ ptr));
.
}
返回(return _ code);
}
4当没有客户端打开管道进行写操作时,读操作将返回0;也就是说,客户端将检测EOF。然后服务器将关闭管道并重新打开它,这样它将在客户端打开管道之前阻塞管道。这类似于服务器第一次启动时的情况;我们已经重新初始化了服务器。将以下代码插入到前面的函数中:
if (read_bytes==0) {
(void)close(server _ FD);
if ((server_fd=open(SERVER_PIPE,O_RDONLY))==-1) {
如果(errno!=EINTR) {
fprintf(stderr,"服务器错误,FIFO打开失败/n ");
}
return(0);
}
read_bytes=read(server_fd,rec_ptr,sizeof(* rec _ ptr));
}
if(read _ bytes==sizeof(* rec _ ptr))return _ code=1;
服务器是单进程的,可以同时为多个客户端服务。因为每个客户端使用不同的管道来接收其响应,所以服务器需要编写不同的管道来向不同的客户端发送响应。因为文件描述符是一种受限资源,所以服务器只会在有数据要发送时打开客户端管道进行写入。
我们将打开、写入和关闭客户端管道分成三个独立的功能。当我们希望向搜索返回多个结果时,我们需要这样做,以便我们可以管道化一次,编写多个响应,然后关闭管道。
测试构建管道
1.首先,我们打开客户渠道:
int start_resp_to_client(常量消息_db_t mess_to_send)
{
#if调试跟踪
printf(" % d:-start _ resp _ to _ client()/n ",getpid());
#endif
(void)sprintf(客户端_管道_名称,客户端_管道,mess _ to _ send . CLIENT _ PID);
if((client _ FD=open(client _ pipe _ name,O_WRONLY))==-1)返回(0);
返回(1);
}
通过调用这个函数来发送消息。我们稍后将查看相应的客户端函数。
int send _ resp _ to _ client(const message _ db _ t mess _ to _ send)
{
int写入字节数;
#if调试跟踪
printf(" % d:-send _ resp _ to _ client()/n ",getpid());
#endif
if (client_fd==-1)返回(0);
write_bytes=write(client_fd,mess_to_send,sizeof(mess _ to _ send));
if (write_bytes!=sizeof(mess _ to _ send))return(0);
返回(1);
}
3.最后,我们关闭客户端管道:
void end _ resp _ to _客户端(无效)
{
#如果调试跟踪
printf(" % d:-end _ resp _ to _ client()/n ",getpid());
#endif
if (client_fd!=-1) {
(void)close(client _ FD);
client _ FD=-1;
}
}
客户端函数
填充服务器的是管道_进口。c中的客户端函数。他们与服务器端函数十分类似,所不同的就是发送邮件至服务器函数。
试验-客户端函数
一在检测服务器可以访问之后,客户端_启动函数初始化客户端管道:
int client_starting(void)
{
#如果调试跟踪
printf("%d :- client_starting/n ",getpid());
#endif
mypid=getpid();
if ((server_fd=open(SERVER_PIPE,O_WRONLY))==-1) {
fprintf(stderr,"服务器未运行/n ");
return(0);
}
(void)sprintf(客户端管道名称,客户端管道,mypid);
(void)unlink(client _ pipe _ name);
if (mkfifo(客户端管道名称,0777)==-1) {
fprintf(stderr,"无法创建客户端管道%s/n”,
客户端管道名称);
return(0);
}
返回(1);
}
2客户端_结束函数关闭文件描述符并且删除多余的有名管道:
空的客户端_结束(无效)
{
#如果调试跟踪
printf("%d :- client_ending()/n ",getpid());
#endif
if (client_write_fd!=-1)(void)close(client _ write _ FD);
if (client_fd!=-1)(void)close(client _ FD);
if (server_fd!=-1)(void)close(server _ FD);
(void)unlink(client _ pipe _ name);
}
3发送邮件至服务器函数通过服务器管道发送请求:
int send_mess_to_server
{
(同Internationalorganizations)国际组织写入字节数;
#如果调试跟踪
printf(" % d:-send _ mess _ to _ server()/n ",getpid());
#endif
if (server_fd==-1)返回(0);
mess _ to _ send.client _ pid=mypid
write_bytes=write(server_fd,mess_to_send,sizeof(mess _ to _ send));
if (write_bytes!=sizeof(mess _ to _ send))return(0);
返回(1);
}
与我们在前面所看到的服务器端函数类似,客户端使用三个函数由服务器得到返回的结果。
试验-得到服务器结果
一这个客户端函数来监听服务器响应。他使用只读取模式打开一个客户端管道,然后作为只写模式响应这个管道文件。
int开始响应来自服务器(空)
{
#如果调试跟踪
printf(" % d:-start _ resp _ from _ server()/n ",getpid());
#endif
if(客户端管道名称[0]==/0 )返回(0);
if (client_fd!=-1)返回(1);
client _ FD=open(client _ pipe _ name,仅O _ rd);
if (client_fd!=-1) {
client _ write _ FD=open(client _ pipe _ name,O _ WRONLY);
if (client_write_fd!=-1)返回(1);
(void)close(client _ FD);
client _ FD=-1;
}
return(0);
}
2下面是由服务器得到匹配数据库记录的主要阅读操作:
int read _ resp _ from _ server(message _ db _ t * rec _ ptr)
{
(同Internationalorganizations)国际组织读取字节数;
int return _ code=0;
#如果调试跟踪
printf(" % d:-read _ resp _ from _ server()/n ",getpid());
#endif
如果(!rec _ ptr)return(0);
if (client_fd==-1)返回(0);
read_bytes=read(client_fd,rec_ptr,sizeof(* rec _ ptr));
if(read _ bytes==sizeof(* rec _ ptr))return _ code=1;
返回(返回_代码);
}
3最后,下面的客户端函数标识服务器响应的结束:
void end_resp_from_server
{
#如果调试跟踪
printf(" % d:-end _ resp _ from _ server()/n ",getpid());
#endif
/*此函数在管道实现中为空*/
}
在从服务器开始响应中用于写入的客户端打开操作:
client _ write _ FD=open(client _ pipe _ name,O _ WRONLY);
用来在阻止当服务器需要快速响应客户端的多个请求所引起的竞争条件。
要详细的解释这一点,考虑下面的事件序列:
一客户端向服务器发送请求
2服务器读取请求,打开客户端管道并且发送响应,但是在他关闭客户端之前会被挂起。
3客户端打开他的管道用于读取,读取第一个响应然后关闭管道。
四然后客户端发送一个新的命令并且打开客户端管道用于读取。
5服务器继续运行,关闭其客户端管道。
不幸的时,此时客户端正尝试读取管道,查找其下一个请求的响应,但是阅读操作会返回0字节,因为并没有进程使得客户端打开用于写入。
我们可以通过允许客户打开管道进行读写来避免这种争用情况,这样就不需要重复地重新打开管道。请注意,客户端不写入管道,因此没有垃圾数据的危险。
现在我们把我们的光盘数据库程序分为客户端和服务器两部分,这样我们就可以独立开发用户界面和底层数据库技术。我们可以看到,一个定义良好的数据库接口可以使程序的每个主要元素充分利用计算机资源。如果我们深入研究,我们可以将管道实现改为网络实现,并使用数据库服务器。我们将在第15章学习更多关于网络的知识。
在本章中,我们学习了使用管道在进程间传输数据。首先,我们了解了无名管道,它是由popen和pipe调用创建的。然后我们讨论了如何使用管道和dup调用,我们可以将数据从一个程序传输到另一个程序的标准输入。然后我们学习了著名的管道以及如何在不相关的程序之间传输数据。最后,我们实现了一个简单的客户机/服务器示例。使用FIFO不仅提供了处理同步,还提供了双向数据流。