`
javasee
  • 浏览: 925692 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

进程间通信(九)

 
阅读更多

管道

下面是管道实现文件,pipe_imp.c,其中有客户端与服务器端函数。

试验--管道实现头文件

1 首先是#include:

#include "cd_data.h"
#include "cliserv.h"

2 我们定义一些在此文件的其他函数中所需要的值:

static int server_fd = -1;
static pid_t mypid = 0;
static char client_pipe_name[PATH_MAX + 1] = {‘\0’};
static int client_fd = -1;
static int client_write_fd = -1;

服务器端函数

下面我们需要探讨服务器端函数。下面的试验部分向我们展示了打开与关闭有名管道以及由客户端读取消息的函数。第二个试验部分则显示了打开,发送与关闭客户端管道的代码,客户端管道基于进程ID。

试验--服务器函数

1 server_starting例程创建服务器要由其中读取命令的有名管道。然后打开这个管道用于读取。open操作将会阻塞,直到一个客户端打开这个管道用于写入。我们使用阻塞模式,从而服务器在等待发送给他的命令时可以执行在管道上执行阻塞读取。

int server_starting(void)
{
#if DEBUG_TRACE
printf(“%d :- server_starting()\n”, getpid());
#endif
unlink(SERVER_PIPE);
if (mkfifo(SERVER_PIPE, 0777) == -1) {
fprintf(stderr, “Server startup error, no FIFO created\n”);
return(0);
}
if ((server_fd = open(SERVER_PIPE, O_RDONLY)) == -1) {
if (errno == EINTR) return(0);
fprintf(stderr, “Server startup error, no FIFO opened\n”);
return(0);
}
return(1);
}

2 当服务器结束时,他移除有名管道,从而客户端可以检测到没有服务器在运行。

void server_ending(void)
{
#if DEBUG_TRACE
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(message_db_t *rec_ptr)
{
int return_code = 0;
int read_bytes;
#if DEBUG_TRACE
printf(“%d :- read_request_from_client()\n”, getpid());
#endif
if (server_fd != -1) {
read_bytes = read(server_fd, rec_ptr, sizeof(*rec_ptr));
...
}
return(return_code);
}

4 在没有客户端打开管道用于写入的情况下,read操作会返回0;也就是说,客户端会检测到EOF。然后服务器会关闭管道并重新打开,从而他会在客户端打开这个管道之前阻塞。这与服务器第一次启动时的情况相类似;我们已经重新初始化了服务器。在前面的函数中插入下面这段代码:

if (read_bytes == 0) {
(void)close(server_fd);
if ((server_fd = open(SERVER_PIPE, O_RDONLY)) == -1) {
if (errno != EINTR) {
fprintf(stderr, “Server error, FIFO open failed\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(const message_db_t mess_to_send)
{
#if DEBUG_TRACE
printf(“%d :- start_resp_to_client()\n”, getpid());
#endif
(void)sprintf(client_pipe_name, CLIENT_PIPE, mess_to_send.client_pid);
if ((client_fd = open(client_pipe_name, O_WRONLY)) == -1) return(0);
return(1);
}

2 消息都是通过调用这个函数来发送的。我们会在稍后来看一下相对应的客户端函数。

int send_resp_to_client(const message_db_t mess_to_send)
{
int write_bytes;
#if DEBUG_TRACE
printf(“%d :- send_resp_to_client()\n”, getpid());
#endif
if (client_fd == -1) return(0);
write_bytes = write(client_fd, &mess_to_send, sizeof(mess_to_send));
if (write_bytes != sizeof(mess_to_send)) return(0);
return(1);
}

3 最后,我们关闭客户端管道:

void end_resp_to_client(void)
{
#if DEBUG_TRACE
printf(“%d :- end_resp_to_client()\n”, getpid());
#endif
if (client_fd != -1) {
(void)close(client_fd);
client_fd = -1;
}
}

客户端函数

填充服务器的是pipe_imp.c中的客户端函数。他们与服务器端函数十分类似,所不同的就是send_mess_to_server函数。

试验--客户端函数

1 在检测服务器可以访问之后,client_starting函数初始化客户端管道:

int client_starting(void)
{
#if DEBUG_TRACE
printf(“%d :- client_starting\n”, getpid());
#endif
mypid = getpid();
if ((server_fd = open(SERVER_PIPE, O_WRONLY)) == -1) {
fprintf(stderr, “Server not running\n”);
return(0);
}
(void)sprintf(client_pipe_name, CLIENT_PIPE, mypid);
(void)unlink(client_pipe_name);
if (mkfifo(client_pipe_name, 0777) == -1) {
fprintf(stderr, “Unable to create client pipe %s\n”,
client_pipe_name);
return(0);
}
return(1);
}

2 client_ending函数关闭文件描述符并且删除多余的有名管道:

void client_ending(void)
{
#if DEBUG_TRACE
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 send_mess_to_server函数通过服务器管道发送请求:

int send_mess_to_server(message_db_t mess_to_send)
{
int write_bytes;
#if DEBUG_TRACE
printf(“%d :- send_mess_to_server()\n”, getpid());
#endif
if (server_fd == -1) return(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);
return(1);
}

与我们在前面所看到的服务器端函数类似,客户端使用三个函数由服务器得到返回的结果。

试验--得到服务器结果

1 这个客户端函数来监听服务器响应。他使用只读取模式打开一个客户端管道,然后作为只写模式响应这个管道文件。

int start_resp_from_server(void)
{
#if DEBUG_TRACE
printf(“%d :- start_resp_from_server()\n”, getpid());
#endif
if (client_pipe_name[0] == ‘\0’) return(0);
if (client_fd != -1) return(1);
client_fd = open(client_pipe_name, O_RDONLY);
if (client_fd != -1) {
client_write_fd = open(client_pipe_name, O_WRONLY);
if (client_write_fd != -1) return(1);
(void)close(client_fd);
client_fd = -1;
}
return(0);
}

2 下面是由服务器得到匹配数据库记录的主要read操作:

int read_resp_from_server(message_db_t *rec_ptr)
{
int read_bytes;
int return_code = 0;
#if DEBUG_TRACE
printf(“%d :- read_resp_from_server()\n”, getpid());
#endif
if (!rec_ptr) return(0);
if (client_fd == -1) return(0);
read_bytes = read(client_fd, rec_ptr, sizeof(*rec_ptr));
if (read_bytes == sizeof(*rec_ptr)) return_code = 1;
return(return_code);
}

3 最后,下面的客户端函数标识服务器响应的结束:

void end_resp_from_server(void)
{
#if DEBUG_TRACE
printf(“%d :- end_resp_from_server()\n”, getpid());
#endif
/* This function is empty in the pipe implementation */
}

工作原理

在start_resp_from_server中用于写入的客户端open操作:

client_write_fd = open(client_pipe_name, O_WRONLY);

用来在阻止当服务器需要快速响应客户端的多个请求所引起的竞争条件。

要详细的解释这一点,考虑下面的事件序列:

1 客户端向服务器发送请求
2 服务器读取请求,打开客户端管道并且发送响应,但是在他关闭客户端之前会被挂起。
3 客户端打开他的管道用于读取,读取第一个响应然后关闭管道。
4 然后客户端发送一个新的命令并且打开客户端管道用于读取。
5 服务器继续运行,关闭其客户端管道。

不幸的时,此时客户端正尝试读取管道,查找其下一个请求的响应,但是read操作会返回0字节,因为并没有进程使得客户端打开用于写入。

通过允许客户打开其管道用于读写,从而移除了重复重新打开管道的需要,我们可以避免这个竞争条件。注意,客户端并没有写入管道,从而并没有垃圾数据的危险。

程序总结


现在我们将我们的CD数据库程序分为客户端与服务器两部分,从而可以使得我们独立开发用户界面与底层数据库技术。我们可以看到一个定义良好的数据库接口可以使得程序的每一个主要元素都充分利用计算机资源。如果我们更为深入一些,我们可以将管道实现改变为网络实现,并且使用一个数据库服务器。我们将会在第15章了解更多的关于网络的内容。

小结

在这一章,我们了解了在进程之间使用管道来传递数据。首先,我们了解了无名管道,使用popen与pipe调用来进行创建,然后讨论了如何使用管道与dup调用,我们可以将数据由一个程序传递到另一个的标准输入。然后我们了解了有名管道,并且了解了如何在无关的程序之间传递数据。最后,我们实现了一个简单的客户端/服务器例子,使用FIFO提供给我们的不仅是处理同步,还有双向的数据流。

分享到:
评论

相关推荐

    LINUX高级程序设计(中文第二版)第九章 System V进程间通信

    俺花了N个大洋买来的,现在免费提供给大家

    unix环境高级编程

    第1章 UNIX基础知识 第2章 UNIX标准化及实现 ...第十四章 进程间通信 第十五章 高级进程间通信 第十六章 一个数据库函数库 第十七章 与PostScript打印机通信 第十八章 调制解调器拨号器 第十九章 伪终端 附录

    linux网络编程

    23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 ...

    嵌入式Linux ARM开发课件第九讲

    ARM7~ARM9体系结构体系结构介绍 ARM7(9)TDMI处理器内核...进程间通信: 进程通信的基本概念,管道、信号、消息队列、信号量、共享内存。 网络通讯接口,socket通信编程。 串口通讯程序和编程实践 多线程程序设计 等。

    C++教程网《Linux网络编程》视频百度云地址

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输...

    2018年C++教程网的linux网络编程视频共41集百度云下载链接.rar

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元...

    Linux网络编程 视频 教程

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输...

    C++教程网视频:linux网络编程

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输...

    c++教程网的linux网络编程视频下载

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输...

    [免费]2018年C++教程网的linux网络编程视频百度云下载链接.rar

    Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输...

    IPC消息队列

    该程序是我写的博客“一起talk C栗子吧(第九十八回:C语言实例--使用消息队列进行进程间通信二)”的配套程序,共享给大家使用

    linux环境下的网络编程

    第四章进程间通信; 第五章通信协议简介; 第六章 Berkeley 套接字; 第七章网络安全; 第八章 Ping 例; 第九章 tftp 例程; 第十章远程命令执行; 第十一章远程注; 第十二章远程过程调; 第十三章远程磁带的访问...

    tcp/udp通信实验报告

    实现进程间的通信。两种通信方式。tcp/udp面向连接和无连接的通信

    Linux内核源代码情景分析 pdf

    第一章 预备知识 第二章 存储管理 第三章 中断、异常和系统调用 ...第六章 传统的UNIX进程间通信 第七章 基于socket的进程间通信 第八章 设备驱动 第九章 多处理器SMP系统结构 第十章 系统引导和初始化

    linux 网络编程

    第四章 进程间通信....... 第五章 通信协议简介 第六章 Berkeley 套接字. 第七章 网络安全性.... 第八章 Ping 例程.. 第九章 tftp 例程.... 第十章 远程命令执行 第十一章 远程注册.... 第十二章 远程过程调用 第十...

    UNIX环境高级编程

    (第十三章)IPC——进程间通信 (第十四、十五章)实例—一个数据库的函数库 (第十六章)、与Postscrip打印机的通信 (第十七章)、调制解调器拨号程序 (第十八章)以及使用伪终端(第十九章)

    Linux网络编程smallsh 源码 (可直接运行)

    第四章 进程间通信 第五章 通信协议简介 第六章 Berkeley 套接字 第七章 网络安全性 第八章 Ping 例程 第九章 tftp 例程 第十章 远程命令执行 第十一章 远程注册 第十二章 远程过程调用 第十三章 远程磁带的访问 第...

    Linux操作系统实验报告

    实验一 熟悉Linux常用命令 实验二 Linux下程序设计基础 实验三 Linux下进程间管道通信 实验四 IPC进程间共享内存通信 实验五 IPC信号量使用 实验六 Linux内存基本原理 实验八 设备驱动程序 实验九 Linux下socket网络...

Global site tag (gtag.js) - Google Analytics