-
2021-05-12 14:22:02
《Linux进程通信实验报告》由会员分享,可在线阅读,更多相关《Linux进程通信实验报告(5页珍藏版)》请在人人文库网上搜索。
1、Linux进程通信实验报告一、 实验目的和要求1. 进一步了解对进程控制的系统调用方法。2. 通过进程通信设计达到了解UNIX或Linux系统中进程通信的基本原理。二、 实验内容和原理1. 实验编程,编写程序实现进程的管道通信(设定程序名为pipe.c)。使用系统调用pipe()建立一条管道线。而父进程从则从管道中读出来自于两个子进程的信息,显示在屏幕上。要求父进程先接受子进程P1发来的消息,然后再接受子进程P2发来的消息。2. 可选实验,编制一段程序,使其实现进程的软中断通信(设定程序名为softint.c)。使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键。
2、盘上来的中断信号(即按Del键),当父进程接受这两个软中断的其中一个后,父进程用系统调用kill()向两个子进程分别发送整数值为16和17的软中断信号,子进程获得对应软中断信号后分别输出相应信息后终止。三、 实验环境一台安装了Red Hat Linux 9操作系统的计算机。四、 实验操作方法和步骤进入Linux操作系统,利用vi编辑器将程序源代码输入并保存好,然后打开终端对程序进行编译运行。五、 实验中遇到的问题及解决六、 实验结果及分析基本实验可选实验七、 源代码Pipe.c#includestdio.h#includeunistd.hmain()int i,j,fd2;char S100;。
3、pipe(fd);if(i=fork=0)sprintf(S,child process 1 is sending a message n);write(fd1,S,50);sleep(3);return;if(j=fork()=0)sprintf(S,child process 2 is sending a message n);write(fd1,S,50);sleep(3);return;elsewait(0);read(fd0,S,50);printf(%s,S);read(fd0,S,50);printf(%s,S);return;Softint.c#includestdio.h#i。
4、ncludeunsitd.hmain()int i,j,fd2;char S100;pipe(fd);if(i=fork=0)sprintf(S,child process 1 is sending a message n);write(fd1,S,50);sleep(3);return;if(j=fork()=0)sprintf(S,child process 2 is sending a message n);write(fd1,S,50);sleep(3);return;elsewait(0);read(fd0,S,50);printf(%s,S);read(fd0,S,50);printf(%s,S);return。
更多相关内容 -
操作系统实验四 进程的管道通信 实验报告
2018-10-23 10:52:07编写一段程序,实现进程的管道通信。 使用系统调用pipe( )建立一条管道,创建两个子进程P1和P2。让P1和P2分别向管道各写一句话: child process P1 is sending messages! child process P2 is sending messages! 父... -
实验一 进程通信——管道和信号实验报告.doc
2021-05-13 13:58:35进程之间通讯实验报告,包含 1.(1)进程的创建 2.(2)进程控制 3.(3)进程间信号通信 4.(4)进程的管道通信 编写程序,创建两个子进程。当此程序运行时,系统中有一个父进程和两个子进程。父进程在屏幕上显示... -
实验四:Linux下进程管道通信.docx
2021-04-13 13:27:59操作系统实验四:Linux下进程管道通信 任务1:使用Pipe创建管道,创建一个子进程,子进程向父进程发送消息“I am your son!”,父进程接收到子进程的消息后,显示在屏幕上,并向子进程发送“I am your father!”。子... -
进程的管道通信 实验报告
2011-11-03 11:20:18编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message! 父进程从管道中读出二个来自子进程的信息并... -
Linux管道通信(附实验报告)(附源代码)
2018-03-07 15:56:16编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message! 父进程从管道中读出二个来自子进程的信息并... -
基于有名管道的进程间通信实验报告
2012-07-01 11:27:09这个文档是基于有名管道进程间通信的一些实验,可以从本质上理解进程间通信的有名管道的机理。并附有源码,可以自己复制下来,进行调试。 -
Linux软中断通信实验报告_进程的软中断通信实验报告
2020-12-16 10:36:56实验2 Linux软中断通信 1.实验目的 通过本实验掌握软中断的基本原理掌握中断信号的使用进程的创建以及系统计时器的使用 2.实验内容上交的实验2统一取名为test2) 由父进程创建两个子进程通过终端输入Crtl+\组合键向父... -
操作系统实验报告9:进程间通信—管道和 socket 通信
2021-07-13 19:20:57实验内容:进程间通信—管道和 socket 通信。 编译运行课件 Lecture11 例程代码: alg.11-3-socket-input-2.c alg.11-4-socket-connector-BBS-2.c alg.11-5-socket-server-BBS-3.c 实验环境 架构:Intel x86...操作系统实验报告9
实验内容
- 实验内容:进程间通信—管道和 socket 通信。
- 编译运行课件 Lecture11 例程代码:
- alg.11-3-socket-input-2.c
- alg.11-4-socket-connector-BBS-2.c
- alg.11-5-socket-server-BBS-3.c
- 编译运行课件 Lecture11 例程代码:
实验环境
- 架构:Intel x86_64 (虚拟机)
- 操作系统:Ubuntu 20.04
- 汇编器:gas (GNU Assembler) in AT&T mode
- 编译器:gcc
技术日志
实验内容:进程间通信—管道和 socket 通信
- 验证实验Lecture11 例程代码
分析:
实验内容原理:
进程间通信—管道和socket通信常用的函数一般在头文件
#include<sys/socket.h>
中里,其中有:int socket(int domain, int type, int protocol)
socket()
函数的作用是创建一个socket的描述符其中参数
domain
是协议族,常用的协议有AF_INET(IPV4协议)、AD_INET6(IPV6协议)、AF_UNIX(单一Unix系统中进程间通信)、AF_LOCAL、AF_ROUTE等type
是套接口类型,常用值有SOCK_STREAM(流套接字,对应TCP协议)、SOCK_DGRAM(数据报套接字,对应UDP协议)、SOCK_RAW(原始套接字,提供原始网络协议存取)protocol
是协议类型,一般取为0,会自动选择type
类型对应的协议,常用值有IPPROTO_TCP(对应TCP)、IPPROTO_UDP(对应UDP)、IPPROTO_ICMP(对应ICMP)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
bind()
函数的作用是将一个地址族中的特定地址绑定给一套接字其中参数
sockfd
代表要绑定的套接字的描述符,是之前通过socket()
函数创建的;addr
表示服务器的通信地址,代表指向绑定给sockfd
的协议地址的结构体;addrlen
是参数addr
的长度,因为addr
可以接受多种类型的结构体,所以需要addrlen
额外指定结构体长度int getsockname(int sockfd, struct sockaddr *restrict_addr, socklen_t *restrict_addrlen)
getsockname()
函数的作用获取一个套接字的名字其中参数
sockfd
代表需要获取名称的套接字的标识符restrict_addr
是存放所获取的套接字名称的结构体restrict_addrlen
是参数addr
的长度,因为addr
可以接受多种类型的结构体,所以需要addrlen
额外指定结构体长度int listen(int sockfd, int backlog)
在使用socket()、bind()函数建立套接字并绑定后,套接字默认是主动类型,
listen()
函数的作用是监听这个套接字,并把这个套接字变成被动类型,等待来自客户端的连接请求其中参数
sockfd
代表监听的套接字的描述符backlog
表示相应的套接字可以排队的最大连接个数int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
connect()
函数的作用是客户端可以通过调用这个函数和服务器发出连接请求建立连接其中参数
sockfd
代表客户端的socket的描述符serv_addr
表示服务器的socket地址addrlen
是套接字地址的长度int accept(int sockfd, struct sockaddr restrict *addr, socklen_t restrict *addrlen)
accept()
函数的作用是服务器通过listen()
函数监听到客户端通过connect()
函数发出的连接请求后,可以建立连接其中参数
sockfd
代表服务器的套接字的描述符,也叫监听套接字addr
表示客户端的通信地址,存放在一个结构体里addrlen
是参数addr
的长度ssize_t send(int sockfd, const void *buf, socklen_t len, int flags)
send()
函数的作用是向一个处在连接状态的套接字发送数据其中参数
sockfd
代表要发送给的套接字的描述符buf
表示存放要发送数据的一个缓冲区len
表示缓冲区中要发送的数据的字节数flags
是调用执行方式,一般是0,常用值有MSG_CONFIRM(用来告诉链路层),MSG_DONTWAIT(启用非阻塞操作),MSG_DONTROUTE(只发送到直接连接的主机上)等。ssize_t recv(int sockfd, void *buf, socklen_t len, int flags)
recv()
函数的作用是令服务端或客户端可以从另一端接收数据其中参数
sockfd
代表接收端的套接字的描述符buf
表示存放接收来的数据的一个缓冲区len
表示缓冲区中数据的字节数flags
表示调用执行方式,一般置0int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
setsockopt()
函数的作用是设置一个套接口的选项值其中参数
sockfd
代表要设置的socket的描述符level
代表选项值定义的层次,值包括SOL_SOCKET
和IPPROTO_TCP
optname
要设置的套接口的选项optval
是指向存放设置的选项值的缓冲区,是一个指针optlen
是之前的参数optval
的长度下面两个函数依赖的头文件为
#include <sys/types.h>
和#include <ifaddrs.h>
int getifaddrs(struct ifaddrs **ifap)
getifaddrs()
函数的作用是获取本地网络接口信息,储存在一个链表中,链表的头结点的地址保存在参数*ifap
中void freeifaddrs(struct ifaddrs *ifap)
freeifaddrs()
函数的作用是将之前使用getifaddrs()
函数动态分配内存获得的参数ifap
释放掉网络编程的基本流程为:
这次的BBS程序中,服务端部分:
在
alg.11-5-socket-server-BBS-3.c
程序中,首先使用一个socket()函数创建了一个使用IPV4和TCP协议的套接字server_fd,设置服务端的IP地址和端口号后,使用bind()函数将服务器的地址绑定给套接字,接着使用listen()函数监听是否有来自客户端的连接请求,有请求后,使用accept()函数接收客户端的连接请求并建立连接,建立连接后,使用recv_send_data()函数与客户端使用命名管道进行发送和接收信息,最后使用close()函数关闭管道。客户端部分:
在
alg.11-4-socket-connector-BBS-2.c
程序中,也是首先使用一个socket()函数创建了一个使用IPV4和TCP协议的套接字connect_fd,设置这个客户端的IP地址和端口号后,使用connect()函数向服务端发出连接请求并成功连接之后,创建一个子进程,实现发送消息和接收消息的进程同步进行,同步收发数据,当在alg.11-3-socket-input-2.c
程序运行后,在其终端输入信息,信息会通过管道传输到connector中,当输入的数据是"#0"或者接收到的数据是"Console: #0"时,退出收发消息的循环,使用close()函数关闭套接字,并退出客户端的进程。程序总体结构:
首先运行
alg.11-5-socket-server-BBS-3.c
程序设置好端口号后建立一个server,server和自己设计的结构控制台Console使用一个命名管道进行通信,管道文件名为test,控制台Console用来输出改名等等信息。然后建立三个客户端,分别运行
alg.11-4-socket-connector-BBS-2.c
程序和alg.11-3-socket-input-2.c
程序三次,一个connector和一个input对应,并使用命名管道进行connector和input通信,三个客户端的命名管道文件分别为test1、test2、test3,当有消息从input输入后,会通过命名管道传输到对应的connector中,这三个客户端的connector与服务端server建立连接后进行收发数据,server通过recv_send_data()函数使用匿名管道传送消息,使用的是fd_msg
,在pipe_data()函数处理完管道信息后,把结果又通过匿名管道fn[sn]
使用recv_send_data()函数传递到第sn个客户端中,实现聊天通信。执行程序命令:
gcc -o server.o alg.11-5-socket-server-BBS-3.c gcc -o connector.o alg.11-4-socket-connector-BBS-2.c gcc -o input.o alg.11-3-socket-input-2.c ./server.o test ./connector.o test1 ./input.o test1 ./connector.o test2 ./input.o test2 ./connector.o test3 ./input.o test3
执行截图:
首先在一个终端输入编译命令获得server、connector、input的.o文件,输入
./server.o test
,弹出input server port number:
的提示后输入9000
,使BBS的服务端运行起来,可以看到,输出显示Bind success! Listening ...
,服务端正在等待客户端发送消息然后在另外两个终端分别输入
./connector.o test1
和./input.o test1
,分别作为同一个客户端的connector
和input
,在一个终端输入./connector.o test1
,提示Input server's hostname/ipv4:
时,输入服务端显示的ipv4地址,这里是192.168.244.128
,然后弹出提示Input server's port number:
,输入之前在服务端确定的端口号9000
,客户端就与服务端实现连接,这个connector
对应的input
就可以输入消息了。在第一个
connector
对应的input
中,首先输入#1xiaoming
,可以看到,服务端中显示第一个input
对应的nickname
由匿名Anonymous
改为了xiaoming
,同时向第一个input
对应的connector
发送nickname
已经被修改的信息,并在前面加上Console:
,提示是控制台信息,不是用户发送的聊天信息在第二个和第三个
connector
中,按照之前开启第一个客户端的步骤开启第二个和第三个客户端,并把第二个客户端的nickname
命名为xiaohong
,第三个客户端的nickname
命名为xiaogang
。在第一个
input
中,输入聊天信息Hello World!
,可以看到,服务端中显示SN-1: Hello World!
,表示用户1xiaoming
发送的信息Hello World!
到达了服务端,然后服务端把信息储存在缓冲区send_buf
中,接着以广播形式向所有的客户端转发,可以看到,在所有客户端的connector
中,都显示了信息xiaoming: Hello World!
测试BBS系统的私聊功能,即输入
@
+客户的nickname
+消息,可以单独指定这个客户发送消息,其它用户不会收到消息,在第三个input
中,输入@xiaoming sayhello!
,表示第三个客户xiaogang
向客户nickname
为xiaoming
的用户发送私聊消息sayhello!
,可以看到,只有客户1xiaoming
收到了客户3xiaogang
发来的sayhello!
的消息。测试BBS的修改昵称功能,在第三个
input
中,输入#1xiaowei
,表示第三个客户xiaogang
的nickname
改为xiaowei
,可以看到,服务端在收到改名的请求后,完成改名后,发送消息告诉第三个客户nickname
已经改为了xiaowei
。测试BBS的客户端退出功能,在第一个
input
中,输入#0
,表示第一个客户端退出聊天程序,可以看到,第一个connector
退出进程,说明断开连接,即使这时再在第一个input
中输入信息,服务端不会有任何显示,其它客户端也不会收到信息。实现细节解释:
// 文件alg.11-3-socket-input-2.c,充当输入信息并通过管道将信息传输给客户端connector的input角色 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #define BUFFER_SIZE 1024 /* input terminal, data packed without '\n' */ /* write to pipe_data() through a named pipe */ int main(int argc, char *argv[]) { char fifoname[80], write_buf[BUFFER_SIZE]; int fdw, flags, ret, i; if(argc < 2) { printf("Usage: ./a.out pathname\n"); return EXIT_FAILURE; } strcpy(fifoname, argv[1]); // access()函数用来判断是否有读取文件的权限,其中参数fifoname代表FIFO文件路径名,F_OK用来判断FIFO文件是否存在,如果返回值为-1管道文件不存在,那么创建文件 if(access(fifoname, F_OK) == -1) { // mkfifo()函数用来创建一个命名管道,权限为可读可写,如果返回值不为0,说明创建失败,进行异常处理 if(mkfifo(fifoname, 0666) != 0) { perror("mkfifo()"); exit(EXIT_FAILURE); } else printf("new fifo %s created ...\n", fifoname); } // 用open()函数打开一个FIFO文件,其中fifoname是FIFO文件路径名,O_RDWR代表权限可读可写,默认情况下以阻塞模式进行读写,当管道满时阻塞,返回一个FIFO的文件描述符给fdw fdw = open(fifoname, O_RDWR); // 如果fdw小于0,说明打开管道失败,进行错误处理 if(fdw < 0) { perror("pipe open()"); exit(EXIT_FAILURE); } // 否则打开管道成功 else { // 使用fcntl()函数设置已打开的FIFO文件的文件性质,其中fdw是文件描述符,F_GETFL表示取得fdw对应的文件的打开方式状态标志,最后一个参数可以默认为0 flags = fcntl(fdw, F_GETFL, 0); // 使用fcntl()函数把文件标识符为fdw的FIFO文件的写设置为非阻塞模式,其中F_SETFL表示设置文件打开方式为flags | O_NONBLOCK方式,即之前方式的基础上加上非阻塞方式,当管道满时直接返回-1 fcntl(fdw, F_SETFL, flags | O_NONBLOCK); // 进入循环用于输入信息 while (1) { printf("Enter some text (#0-quit | #1-nickname): \n"); // 先把用来存放写入信息的缓冲区write_buf清空 memset(write_buf, 0, BUFFER_SIZE); // 从标准输入流即键盘向缓冲区write_buf写入信息 fgets(write_buf, BUFFER_SIZE, stdin); // 在缓冲区的末尾设置0,截断可能溢出的信息 write_buf[BUFFER_SIZE-1] = 0; // 把缓冲区中出现换行符的位置设置为0即结尾,过滤掉换行符 for (i = 0; i < BUFFER_SIZE; i++) { if(write_buf[i] == '\n') { write_buf[i] = 0; } } // 使用非阻塞方式将缓冲区write_buf中的信息写入命名管道中,并将write()函数的返回值赋给ret ret = write(fdw, write_buf, BUFFER_SIZE); // 如果ret小于等于0,说明管道已满,暂时阻塞写入,进程睡眠1s后再尝试把信息写入管道 if(ret <= 0) { perror("write()"); printf("Pipe blocked, try again ...\n"); sleep(1); } } } //使用close()函数关闭管道的文件标识符 close(fdw); exit(EXIT_SUCCESS); }
// 文件alg.11-4-socket-connector-BBS-2.c,充当客户端connector角色,用于接收input输入的信息,并与服务端进行收发信息的通信 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/signal.h> #include <fcntl.h> #include <sys/stat.h> #define BUFFER_SIZE 1024 #define NICKNAME_L 11 #define MSG_SIZE BUFFER_SIZE+NICKNAME_L+4 #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) /* asynchronous send-receive version; separated input terminal*/ int main(int argc, char *argv[]) { char fifoname[80], nickname[80]; int fdr, connect_fd; char ip_name_str[INET_ADDRSTRLEN]; uint16_t port_num; char stdin_buf[BUFFER_SIZE], msg_buf[MSG_SIZE]; int sendbytes, recvbytes, ret; char clr; struct hostent *host; struct sockaddr_in server_addr, connect_addr; socklen_t addr_len; pid_t childpid; if(argc < 2) { printf("Usage: ./a.out pathname\n"); return EXIT_FAILURE; } strcpy(fifoname, argv[1]); // access()函数用来判断是否有读取文件的权限,其中参数fifoname代表FIFO文件路径名,F_OK用来判断FIFO文件是否存在,如果返回值为-1管道文件不存在,那么创建文件 if(access(fifoname, F_OK) == -1) { // mkfifo()函数用来创建一个命名管道,权限为可读可写,如果返回值不为0,说明创建失败,进行异常处理 if(mkfifo(fifoname, 0666) != 0) { perror("mkfifo()"); exit(EXIT_FAILURE); } else printf("new fifo %s named pipe created\n", fifoname); } // 用open()函数打开一个FIFO文件,其中fifoname是FIFO文件路径名,O_RDWR代表权限可读可写,默认情况下以阻塞模式进行读写,返回一个FIFO的文件描述符给fdr fdr = open(fifoname, O_RDWR); if(fdr < 0) { perror("pipe read open()"); exit(EXIT_FAILURE); } // www.baidu.com or an ipv4 address printf("Input server's hostname/ipv4: "); // 输入服务端的主机名或ipv4地址到缓冲区stdin_buf中 scanf("%s", stdin_buf); // 清空输入缓冲区 while((clr = getchar()) != '\n' && clr != EOF); printf("Input server's port number: "); // 输入端口号到port_num scanf("%hu", &port_num); while((clr = getchar()) != '\n' && clr != EOF); // 使用gethostbyname()函数通过域名或主机名获取IP地址 if((host = gethostbyname(stdin_buf)) == NULL) { printf("invalid name or ip-address\n"); exit(EXIT_FAILURE); } // 打印规范名 printf("server's official name = %s\n", host->h_name); char** ptr = host->h_addr_list; for(; *ptr != NULL; ptr++) { inet_ntop(host->h_addrtype, *ptr, ip_name_str, sizeof(ip_name_str)); printf("\tserver address = %s\n", ip_name_str); } // 建立客户端的套接字,使用IPV4和TCP协议 if((connect_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { ERR_EXIT("socket()"); } // 设置IP地址类型为IPV4 server_addr.sin_family = AF_INET; // 设置端口号,htons()函数用于将端口号从主机字节顺序变成网络字节顺序 server_addr.sin_port = htons(port_num); // 设置IP地址,从host的成员变量h_addr中获取 server_addr.sin_addr = *((struct in_addr *)host->h_addr); // 将server_addr的成员变量sin_zero的前八个字节清空 bzero(&(server_addr.sin_zero), 8); addr_len = sizeof(struct sockaddr); // 使用connect()函数使客户端可以和服务端发出请求建立连接 ret = connect(connect_fd, (struct sockaddr *)&server_addr, addr_len); if(ret == -1) { close(connect_fd); ERR_EXIT("connect()"); } // 客户端和服务端建立连接之后,客户端会被分配一个端口 addr_len = sizeof(struct sockaddr); // 使用getsockname()函数获取客户端的套接字的名字 ret = getsockname(connect_fd, (struct sockaddr *)&connect_addr, &addr_len); if(ret == -1) { close(connect_fd); ERR_EXIT("getsockname()"); } // 获取客户端被分配的端口号,使用ntohs()函数将一个16位数由网络字节顺序转换为主机字节顺序 port_num = ntohs(connect_addr.sin_port); // 获取客户端的IP地址,使用inet_ntoa()函数将网络地址转换为"."点隔的字符串格式 strcpy(ip_name_str, inet_ntoa(connect_addr.sin_addr)); printf("Local port: %hu, IP addr: %s\n", port_num, ip_name_str); // 获取服务端的IP地址 strcpy(ip_name_str, inet_ntoa(server_addr.sin_addr)); // 创建子进程 childpid = fork(); if(childpid < 0) ERR_EXIT("fork()"); // 在父进程中 if(childpid > 0) { // 进入一个发数据的循环中 while(1) { /* sending cycle */ // 以阻塞方式读从命名管道中读取数据,当管道满时阻塞,数据从终端中输入 ret = read(fdr, stdin_buf, BUFFER_SIZE); if(ret <= 0) { perror("read()"); break; } stdin_buf[BUFFER_SIZE-1] = 0; // 使用send()函数以阻塞方式向客户端的套接字发送数据 sendbytes = send(connect_fd, stdin_buf, BUFFER_SIZE, 0); if(sendbytes <= 0) { printf("sendbytes = %d. Connection terminated ...\n", sendbytes); break; } // 如果输入的数据是"#0",那么清空输入缓冲区,以阻塞方式向客户端的套接字发送消息"I quit ... ",然后退出 if(strncmp(stdin_buf, "#0", 2) == 0) { memset(stdin_buf, 0, BUFFER_SIZE); strcpy(stdin_buf, "I quit ... "); sendbytes = send(connect_fd, stdin_buf, BUFFER_SIZE, 0); break; } } // 关闭管道 close(fdr); // 关闭套接字 close(connect_fd); // 结束子进程 kill(childpid, SIGKILL); } // 在子进程中 else { // 进入一个接收数据的循环中 while(1) { // 使用recv()函数接收套接字的数据并存放到msg_buf中 recvbytes = recv(connect_fd, msg_buf, MSG_SIZE, 0); if(recvbytes <= 0) { printf("recvbytes = %d. Connection terminated ...\n", recvbytes); break; } msg_buf[MSG_SIZE-1] = 0; // 打印接收到的数据 printf("%s\n", msg_buf); // 如果传送来的消息是"Console: #0",那么退出循环 ret = strncmp(msg_buf, "Console: #0", 11); if(ret == 0) { break; } } // 关闭套接字 close(connect_fd); // 终止当前进程 kill(getppid(), SIGKILL); } return EXIT_SUCCESS; }
// 文件alg.11-5-socket-server-BBS-3.c,服务端server角色 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <ifaddrs.h> #include <sys/shm.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/wait.h> #include <ctype.h> #define BUFFER_SIZE 1024 /* each pipe has at least 64 blocks for this sze */ #define NICKNAME_L 11 /* 10 chars for nickname */ #define MSG_SIZE BUFFER_SIZE+NICKNAME_L+4 /* msg exchanged between pipe_data() and recv_send_data() */ #define MAX_QUE_CONN_NM 5 /* length of ESTABLISHED queue */ #define MAX_CONN_NUM 10 /* cumulative number of connecting processes */ #define STAT_EMPTY 0 #define STAT_NORMAL 1 #define STAT_ENDED -1 #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) /* one server, m clients BBS, with private chatting */ struct { int stat; char nickname[NICKNAME_L]; } sn_attri[MAX_CONN_NUM+1]; int connect_sn, max_sn; /* from 1 to MAX_CONN_NUM */ int server_fd, connect_fd[MAX_CONN_NUM+1]; int fd[MAX_CONN_NUM+1][2]; /* ordinary pipe: pipe_data() gets max_sn from main() by fd[0][0] recv_send_data(sn) get send_buf from pipe_data() by fd[sn][0], 0<sn<MAX_CONN_NUM+1 */ int fd_stat[2]; /* ordinary pipe: pipe_data() gets stat of connect_sn from main() */ int fd_msg[2]; /* ordinary pipe: pipe_data() gets message of connect_sn from recv_send_data() */ int fdr; /* named pipe: pipe_data() gets stdin_buf from input terminal */ struct sockaddr_in server_addr, connect_addr; // 获取ipv4地址,如果是ipv6地址的话也可以获取ipv6地址 int getipv4addr(char *ip_addr) { struct ifaddrs *ifaddrsptr = NULL; struct ifaddrs *ifa = NULL; void *tmpptr = NULL; int ret; ret = getifaddrs(&ifaddrsptr); if(ret == -1) ERR_EXIT("getifaddrs()"); for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) { if(!ifa->ifa_addr) { continue; } if(ifa->ifa_addr->sa_family == AF_INET) { /* IP4 */ tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; char addr_buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN); printf("%s IPv4 address %s\n", ifa->ifa_name, addr_buf); if(strcmp(ifa->ifa_name, "lo") != 0) strcpy(ip_addr, addr_buf); /* return the ipv4 address */ } else if(ifa->ifa_addr->sa_family == AF_INET6) { /* IP6 */ tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; char addr_buf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN); printf("%s IPv6 address %s\n", ifa->ifa_name, addr_buf); } } if(ifaddrsptr != NULL) { freeifaddrs(ifaddrsptr); } return EXIT_SUCCESS; } // 处理管道数据 void pipe_data(void) { /* get sidin_buf from input terminal update max_sn from main() update sn_stat from main() - STAT_EMPTY->STAT_NORMAL update sn_stat from recv_send_data() STAT_NORMAL->STAT_ENDED update sn_nickname from recv_send_data() select connect_sn by the descritor @**** in start of send_buf */ char send_buf[BUFFER_SIZE], stat_buf[BUFFER_SIZE], stdin_buf[BUFFER_SIZE]; char msg_buf[MSG_SIZE]; /* sn(4)nickname(10)recv_buff(BUFFER_SIZE) */ int flags, sn, ret, i, new_stat; char nickname[NICKNAME_L]; // 使用fcntl()函数获得fd[0][0]的文件状态描述符 flags = fcntl(fd[0][0], F_GETFL, 0); // 设置fd[0][0]使用非阻塞状态读取管道,当管道满时直接返回-1 fcntl(fd[0][0], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking read ord-pipe */ // 使用fcntl()函数获得fd_stat[0]的文件状态描述符 flags = fcntl(fd_stat[0], F_GETFL, 0); // 设置fd_stat[0]使用非阻塞状态读取管道,当管道满时直接返回-1 fcntl(fd_stat[0], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking read ord-pipe */ // 使用fcntl()函数获得fd_msg[0]的文件状态描述符 flags = fcntl(fd_msg[0], F_GETFL, 0); // 设置fd_msg[0]使用非阻塞状态读取管道,当管道满时直接返回-1 fcntl(fd_msg[0], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking read ord-pipe */ // 使用fcntl()函数获得fdr的文件状态描述符 flags = fcntl(fdr, F_GETFL, 0); // 设置fdr使用非阻塞状态读取管道,当管道满时直接返回-1 fcntl(fdr, F_SETFL, flags | O_NONBLOCK); /* set to non-blocking read nam-pipe */ while(1) { while (1) { /* get the last current max_sn from main() */ ret = read(fd[0][0], &sn, sizeof(sn)); /* non-blocking read ord-pipe from main() */ if(ret <= 0) { /* pipe empty */ break; } max_sn = sn; printf("max_sn changed to: %d\n", max_sn); } while (1) { /* update sn_stat from main() */ ret = read(fd_stat[0], stat_buf, BUFFER_SIZE); /* non-blocking read ord-pipe from main() */ if(ret <= 0) { /* pipe empty */ break; } sscanf(stat_buf, "%d,%d", &sn, &new_stat); printf("SN stat changed: sn = %d, stat: %d -> %d\n", sn, sn_attri[sn].stat, new_stat); sn_attri[sn].stat = new_stat; } while (1) { /* update sn_stat and nickname from recv_send_data(), or brocast msg to all sn */ ret = read(fd_msg[0], msg_buf, MSG_SIZE); /* non-blocking read ord-pipe from recv_send_data() */ if(ret <= 0) { /* pipe empty */ break; } sscanf(msg_buf, "%4d%s", &sn, stat_buf); if(msg_buf[4] == '#') { if(msg_buf[5] == '0') { /* #0: terminating the connect_fd */ new_stat = STAT_ENDED; printf("SN stat changed: sn = %d, stat: %d -> %d\n", sn, sn_attri[sn].stat, new_stat); sn_attri[sn].stat = new_stat; } if(msg_buf[5] == '1') { /* #1name: renaming the nickname */ strncpy(nickname, &msg_buf[6], NICKNAME_L); for (i = 0; i < NICKNAME_L-1; i++) { if(nickname[i] == ' ') { nickname[i] = '_'; } if(nickname[i] == '\n') { nickname[i] = 0; } } nickname[i] = 0; printf("SN stat changed: sn = %d, nickname: %s -> %s\n", sn, sn_attri[sn].nickname, nickname); for (i=0; i<=max_sn; i++) { /* sn_attri[0].nickname = "Console" */ ret = strcmp(sn_attri[i].nickname, nickname); if(ret == 0) { memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "Console: this nickname occupied: %s", nickname); ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } break; } } if(i > max_sn) { strncpy(sn_attri[sn].nickname, nickname, NICKNAME_L); memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "Console: your nickname changed to %s", sn_attri[sn].nickname); ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } } } /* ignore the message from recv_send_data() otherwise */ } // 私聊功能,输入@+要私聊的用户的nickname,再输入信息即可 else if(msg_buf[4] == '@') { for (i = 0; i < NICKNAME_L-1; i++) { nickname[i] = msg_buf[5+i]; if(msg_buf[5+i] == 0 || msg_buf[5+i] == ' ') { break; } } nickname[i] = 0; if(msg_buf[5+i] == ' ') { i++; } strcpy(stdin_buf, &msg_buf[5+i]); memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%s@: %s", sn_attri[sn].nickname, stdin_buf); for (sn = 1; sn <= max_sn; sn++) { /* message sent to all sn's by ord-pipes fd[sn][1] */ if(sn_attri[sn].stat == STAT_NORMAL && strcmp(sn_attri[sn].nickname, nickname) == 0) { flags = fcntl(fd[sn][1], F_GETFL, 0); fcntl(fd[sn][1], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking write ord-pipe */ ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } } } } else { strcpy(stdin_buf, &msg_buf[4]); memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%s: %s", sn_attri[sn].nickname, stdin_buf); for (sn = 1; sn <= max_sn; sn++) { /* message sent to all sn's by ord-pipes fd[sn][1] */ if(sn_attri[sn].stat == STAT_NORMAL) { flags = fcntl(fd[sn][1], F_GETFL, 0); fcntl(fd[sn][1], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking write ord-pipe */ ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } } } } } while (1) { /* read from input terminal and brocast to all sn */ ret = read(fdr, stdin_buf, BUFFER_SIZE); /* non-blocking read nam-pipe from input terminal */ if(ret <= 0) { break; } if(stdin_buf[0] == '@') { sn = atoi(&stdin_buf[1]); if(sn > 0 && sn <= max_sn && sn_attri[sn].stat == STAT_NORMAL) { for (i = 1; isdigit(stdin_buf[i]); i++) ; if(stdin_buf[i] == '#' && stdin_buf[i+1] == '0') { /* #0: terminating the connect_fd */ new_stat = STAT_ENDED; printf("SN stat changed: sn = %d, stat: %d -> %d\n", sn, sn_attri[sn].stat, new_stat); sn_attri[sn].stat = new_stat; memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%s: %s", sn_attri[0].nickname, "#0 your connection terminated!"); ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } ; } else { flags = fcntl(fd[sn][1], F_GETFL, 0); fcntl(fd[sn][1], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking write ord-pipe */ memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%s: %s", sn_attri[0].nickname, &stdin_buf[i]); ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } } } ; /* invalid connect_sn ignored */ } else { memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%s: %s", sn_attri[0].nickname, stdin_buf); for (sn = 1; sn <= max_sn; sn++) { /* message sent to all sn's by ord-pipes fd[sn][1] */ if(sn_attri[sn].stat == STAT_NORMAL) { flags = fcntl(fd[sn][1], F_GETFL, 0); fcntl(fd[sn][1], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking write ord-pipe */ ret = write(fd[sn][1], msg_buf, MSG_SIZE); /* non-blocking write ord-pipe */ if(ret <= 0) { printf("sn = %d write error, message missed ...\n", sn); } } } } } } return; } void recv_send_data(int sn) { char recv_buf[BUFFER_SIZE], send_buf[BUFFER_SIZE]; char msg_buf[MSG_SIZE]; /* sn(4)nickname(10)recv_buff(BUFFER_SIZE) */ int recvbytes, sendbytes, ret, flags; int stat; flags = fcntl(connect_fd[sn], F_GETFL, 0); fcntl(connect_fd[sn], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking mode to socket recv */ flags = fcntl(fd[sn][0], F_GETFL, 0); fcntl(fd[sn][0], F_SETFL, flags | O_NONBLOCK); /* set to non-blocking mode to ord-pipe read */ while(1) { /* receiving and sending cycle */ recvbytes = recv(connect_fd[sn], recv_buf, BUFFER_SIZE, MSG_DONTWAIT); /* non-blocking socket recv */ if(recvbytes > 0) { printf("===>>> SN-%d: %s\n", sn, recv_buf); memset(msg_buf, 0, MSG_SIZE); sprintf(msg_buf, "%4d%s", sn, recv_buf); ret = write(fd_msg[1], msg_buf, MSG_SIZE); /* blocking write ord-pipe to pipe_data() */ if(ret <= 0) { perror("fd_stat write() to pipe_data()"); break; } } ret = read(fd[sn][0], msg_buf, MSG_SIZE); /* non-blocking read ord-pipe from pipe_data() */ if(ret > 0) { printf("sn = %d send_buf ready: %s\n", sn, msg_buf); sendbytes = send(connect_fd[sn], msg_buf, MSG_SIZE, 0); /* blocking socket send */ if(sendbytes <= 0) { break; } } sleep(1); /* heart beating */ } return; } int main(int argc, char *argv[]) { socklen_t addr_len; pid_t pipe_pid, recv_pid, send_pid; char stdin_buf[BUFFER_SIZE], ip4_addr[INET_ADDRSTRLEN]; uint16_t port_num; int ret; char fifoname[80], clr; int stat; if(argc < 2) { printf("Usage: ./a.out pathname\n"); return EXIT_FAILURE; } strcpy(fifoname, argv[1]); // access()函数用来判断是否有读取文件的权限,其中参数fifoname代表FIFO文件路径名,F_OK用来判断FIFO文件是否存在,如果返回值为-1管道文件不存在,那么创建文件 if(access(fifoname, F_OK) == -1) { // mkfifo()函数用来创建一个命名管道,权限为可读可写,如果返回值不为0,说明创建失败,进行异常处理 if(mkfifo(fifoname, 0666) != 0) { perror("mkfifo()"); exit(EXIT_FAILURE); } else { printf("new fifo %s named pipe created\n", fifoname); } } // 用open()函数打开一个FIFO文件,其中fifoname是FIFO文件路径名,O_RDWR代表权限可读可写,默认情况下以阻塞模式进行读写,返回一个FIFO的文件描述符给fdr fdr = open(fifoname, O_RDWR); // 如果fdw小于0,说明打开管道失败,进行错误处理,否则打开管道成功 if(fdr < 0) { perror("named pipe read open()"); exit(EXIT_FAILURE); } // 为fd数组中每个元素即管道标识符建立一个管道,如果建立失败,打印错误原因 for (int i = 0; i <= MAX_CONN_NUM; i++) { ret = pipe(fd[i]); if(ret == -1) { perror("fd pipe()"); } } // 为fd_stat建立一个管道 ret = pipe(fd_stat); if(ret == -1) { perror("fd_stat pipe()"); } // 为fd_msg建立一个管道 ret = pipe(fd_msg); if(ret == -1) { perror("fd_msg pipe()"); } // 所有sn_attri数组元素的状态都被初始化为STAT_EMPTY空状态,昵称为"Anonymous"匿名 for (int i = 0; i <= MAX_CONN_NUM; i++) { sn_attri[i].stat = STAT_EMPTY; strcpy(sn_attri[i].nickname, "Anonymous"); } // 第一个sn_attri数组元素的昵称为"Console"控制台 strcpy(sn_attri[0].nickname, "Console"); // 使用socket()函数创建一个socket的描述符返回给server_fd,AF_INET代表使用IPV4协议,SOCK_STREAM对应TCP协议,0为自动选择协议类型,这里对应TCP server_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建失败,就退出程序 if(server_fd == -1) { ERR_EXIT("socket()"); } printf("server_fd = %d\n", server_fd); // 获取IPV4地址到ip4_addr中 getipv4addr(ip4_addr); // 输入不超过5位数的服务器端口号到port_num printf("input server port number: "); memset(stdin_buf, 0, BUFFER_SIZE); fgets(stdin_buf, 6, stdin); stdin_buf[5] = 0; port_num = atoi(stdin_buf); // 设置服务端的IP地址类型为IPV4 server_addr.sin_family = AF_INET; // 设置服务端的端口号,htons()函数用于将端口号从主机字节顺序变成网络字节顺序 server_addr.sin_port = htons(port_num); // 设置服务端的IP地址,inet_addr()函数用于将一个点分十进制的IP转换成一个长整型整数 server_addr.sin_addr.s_addr = inet_addr(ip4_addr); // 将server_addr的成员变量sin_zero的前八个字节清空 bzero(&(server_addr.sin_zero), 8); int opt_val = 1; // 使用setsockopt()函数设置服务端的套接字使得相同的地址和端口可以被重用 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); // 使用bind()函数将服务端的地址绑定给套接字 addr_len = sizeof(struct sockaddr); ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len); // 绑定失败,关闭服务端,退出程序 if(ret == -1) { close(server_fd); ERR_EXIT("bind()"); } printf("Bind success!\n"); // 监听与服务器地址绑定的套接字,等待来自客户端的连接请求,可以排队的最大连接个数为MAX_QUE_CONN_NM ret = listen(server_fd, MAX_QUE_CONN_NM); // 绑定失败,关闭服务端,退出程序 if(ret == -1) { close(server_fd); ERR_EXIT("listen()"); } printf("Listening ...\n"); // 创建管道子进程,实现进程同步 pipe_pid = fork(); if(pipe_pid < 0) { close(server_fd); ERR_EXIT("fork()"); } // 在子进程中使用管道处理数据 if(pipe_pid == 0) { pipe_data(); exit(EXIT_SUCCESS); /* ignoring all the next statements */ } max_sn = 0; connect_sn = 1; while (1) { // 当连接的客户端进程数超过最大值时,循环结束,退出聊天程序 if(connect_sn > MAX_CONN_NUM) { printf("connect_sn = %d out of range\n", connect_sn); break; } // 每次accept()函数被调用时都要分配一次addr_len的大小 addr_len = sizeof(struct sockaddr); // 服务端通过accept()函数接收第connect_sn个客户端的连接请求后建立连接 connect_fd[connect_sn] = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len); if(connect_fd[connect_sn] == -1) { perror("accept()"); continue; } // 获取要连接的客户端的端口号到port_num,ntoh()函数用于将一个16位数由网络字节顺序转换为主机字节顺序 port_num = ntohs(connect_addr.sin_port); // 获取要连接的客户端的地址到ip4_addr,inet_ntoa()函数用于将网络地址转换为"."点隔的字符串格式 strcpy(ip4_addr, inet_ntoa(connect_addr.sin_addr)); printf("New connection sn = %d, fd = %d, IP_addr = %s, port = %hu\n", connect_sn, connect_fd[connect_sn], ip4_addr, port_num); // 将服务端的状态设置为已经和有的客户端建立连接且还可以继续其它客户端建立连接的状态 stat = STAT_NORMAL; sprintf(stdin_buf, "%d,%d", connect_sn, stat); // 用缓冲区stdin_buf中的内容设置客户端的状态 ret = write(fd_stat[1], stdin_buf, sizeof(stdin_buf)); /* blocking write ordinary pipe to pipe_data() */ if(ret <= 0) { perror("fd_stat write() from recv_send_data() to pipe_data()"); } // 申请一个用来传输数据的子进程 recv_pid = fork(); if(recv_pid < 0) { perror("fork()"); break; } if(recv_pid == 0) { // 子进程用来向客户端接收和发送数据 recv_send_data(connect_sn); exit(EXIT_SUCCESS); } ret = max_sn = connect_sn; // 以阻塞方式将数据写入管道,当管道满时阻塞 write(fd[0][1], &max_sn, sizeof(max_sn)); if(ret <= 0) { perror("fd_stat write() from recv_send_data() to pipe_data()"); } // 连接的客户端数加一 connect_sn++; // 此时父进程继续监听是否有新的客户端请求连接 } wait(0); // 关闭所有客户端用到的管道 for (int sn = 1; sn <= max_sn; sn++) { close(connect_fd[sn]); } // 关闭服务端 close(server_fd); exit(EXIT_SUCCESS); }
改进代码:
在实验中,要用到语句
bzero(&(server_addr.sin_zero), 8)
来将server_addr的成员变量sin_zero的前8个字节变成0,但是bzero并不是一个ANSI C函数,而在POSIX.1-2001标准里面,bzero函数已经被标记为了遗留函数,所以不推荐使用,在某些平台或环境下运行可能会出现问题,和memset函数比起来,它唯一的好处是只有两个参数,便于记忆,但是在写程序时,为了更好地遵从规范,最好还是用memset函数代替bzero函数,可以使用宏定义替换程序各处出现的bzero:#define bzero(s, n) memset(s, 0, n)
- 实验内容:进程间通信—管道和 socket 通信。
-
实验二进程通信-Linux实验报告.docx
2021-05-17 15:04:26实验二进程通信-Linux实验报告.docx实验报告学号_ 姓名 _ 成绩 实验二 进程通信 【实验目的和要求】1、了解进程通信的概念及方法;2、了解信号量、管道;3、掌握信号量、管道和命名管道编程方法。【实验内容】1、...实验二进程通信-Linux实验报告.docx
实验报告学号_ 姓名 _ 成绩 实验二 进程通信 【实验目的和要求】1、了解进程通信的概念及方法;2、了解信号量、管道;3、掌握信号量、管道和命名管道编程方法。【实验内容】1、利用命名管道实现单机QQ聊天;2、撰写实验报告;【实验原理】1、信号量(semaphore)是为那些访问相同资源的进程以及同一进程不同线程之间提供的一个同步机制。它不是用于传输数据,而只是简单地协调对共享资源的访问。信号量包含一个计数器,表示某个资源正在被访问和访问的次数,用来控制多进程对共享数据的访问。一旦成功拥有了一个信号量,对它所能做的操作只有两种请求和释放。当执行释放操作时,系统将该信号值减1(如果小于零,则设置为零);当执行请求操作时,系统将该信号值加1,如果加1后的值大于设定的最大值,那么系统将会挂起处理进程,直到信号值小于最大值为止。Tuxedo用信号量来确保在某一时刻只有一个进程对某一块共享内存进程访问。信号量配置太低会导致Tuxedo系统应用程序无法启动。2、管道分为两种管道和命名管道。管道是UNIX系统IPC的最古老形式,并且所有的UNIX系统都提供这种通信机制。可以在有亲缘关系(父子进程或者是兄弟进程之间)进行通信,管道的数据只能单向流动,如果想双向流动,必须创建两个管道。管道应用的一个重大缺陷就是没有名字,因此只能用于亲缘进程之间的通信。后来以管道为基础提出命名管道(named pipe,FIFO)的概念,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out)规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则是把数据添加到末尾。它们不支持诸如lseek等文件定位操作。【程序代码】1、lucy.cincludestdio.hincludesys/stat.hincludestring.hincludesys/types.hincludeerrno.hincludefcntl.hincludeunistd.hincludestdlib.hchar write_fifo_nameltop_fifoptol_fifoLucyPetermkfifowrite_fifo_name,S_IRUSR|S_IWUSR;mkfiforead_fifo_name,S_IRUSR|S_IWUSR;printfhello,I am lucyn;write_fdopenwrite_fifo_name,O_WRONLY;ifwrite_fd0perroropen_w;exit-1;read_fdopenread_fifo_name,O_RDONLY;ifread_fd0perroropen_r;exit-1;pid_t pid;pidfork;ifpid0 while1 char bufw256s,writer; iffgetsbufw,256,stdinNULL exit1; bufwstrlenbufw-10 ifstrncmpbufw,bye,30 closewrite_fd; unlinkwrite_fifo_name; exit1; else while1 char bufr256 printfrssn,reader,bufr; ifstrncmpbufr,bye,30 writewrite_fd,bye,4; closeread_fd; unlinkread_fifo_name; exit1; printfs,writer; fstdout; return 0;2、peter.cincludestdio.hincludesys/stat.hincludestring.hincludesys/types.hincludeerrno.hincludefcntl.hincludeunistd.hincludestdlib.hchar write_fifo_nameptol_fifoltop_fifoPeterLucymkfifowrite_fifo_name,S_IRUSR|S_IWUSR;mkfiforead_fifo_name,S_IRUSR|S_IWUSR;printfhello,I am petern;read_fdopenread_fifo_name,O_RDONLY;ifread_fd0perroropen_r;exit-1;write_fdopenwrite_fifo_name,O_WRONLY;ifwrite_fd0perroropen_w;exit-1;pid_t pid;pidfork;ifpid0 while1 char bufw256s,writer; iffgetsbufw,256,stdinNULL exit1; bufwstrlenbufw-10 ifstrncmpbufw,bye,30 closewrite_fd; unlinkwrite_fifo_name; exit1; else while1 char bufr256 printfrssn,reader,bufr; ifstrncmpbufr,bye,30 writewrite_fd,bye,4; closeread_fd; unlinkread_fifo_name; exit1; printfs,writer; fstdout; return 0;【实验步骤】1、 程序lucy.c创建了FIFO write_fifo用于向程序peter.c发送消息;peter.c程序创建了FIFO read_fifo用于向lucy.c发送消息;同时,lucy.c能够通过打开peter.c创建的FIFO来得到的peter.c发来的消息,peter.c能够通过打开lucy.c创建的FIFO来得到lucy.c发来的消息。因此两者就能互相通信了,两者必须在线才能进行通信聊天,这个与qq的聊天功能有些类似。2、 将lucy.c和peter.c的代码编写完后,在一个终端调试即gcc lucy.c o lucy,无错误后运行./lucy;在另外一个新终端上调试即gcc peter.c o peter,无错误后运行./peter;开始进行聊天,若想结束聊天,则在一个终端上输入bye,俩个终端就会同时关闭。【实验结果】【实验体会】传统的进程间通信方式无名管道pipe、有名管道fifo和信号signal。管道的编程1、无名管道创建一个简单的管道,可以使用系统调用pipe。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。系统调用pipe;注意fd0用于读取管道,fd1用于写入管道。该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。一般文件的I/O函数都可以用于管道,如close、read、write等等。2、有名管道创建有名管道用mkfifo()。创建有名管道用mkfifo()。头文件include sys/stat.hinclude sys/types.h函数原型int mkfifoconst char * pathname, mode_t mode函数传入值Pathname要创建的的管道Mode设置管道权限函数返回值若成功则为0,若出错返回-1FIFO相关出错信息EACCES(无存取权限)EEXIST(指定文件不存在)ENAMETOOLONG(路径名太长)ENOENT(包含的目录不存在)ENOSPC(文件系统余空间不足)ENOTDIR(文件路径无效)EROFS(指定的文件存在于只读文件系统中)3、信号发送和捕捉信号发送kill和raise kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组,它不仅可以中止进程(实际上发出SIGKILL信号),也可以向进程发送其他信号。 kill函数语法头文件include signal.hinclude sys/types.h函数原型int killpid_t pid,int sig函数传入值Pid正数要发送信号的进程号0信号被发送到所有的当前进程在同一进程组的进程-1信号发给所有的进程表中的进程-1信号发给进程组号为-pid的每一个进程Sig信号函数返回值若成功则为0,若出错返回-1。4、fstdout目的是清空缓冲,强制结果马上显示到屏幕上。
-
电子科大操作系统实验课Linux通过管道实现进程间通信
2021-01-05 16:11:52操作系统实验完整报告 在Linux系统中使用系统调用fork()创建两个子进程,使用系统调用pipe()建立一个管道,两个子进程分别向管道各写一句话: Child process 1 is sending a message! Child process 2 is sending a... -
操作系统上机实验报告-进程的管道通信
2012-07-18 20:11:44在Linux系统下实现进程从创建到终止的全过程,从中体会进程的创建过程、父进程和子进程之间的关系、进程状态的变化、进程之间的互斥、同步机制、进程调度的原理和以管道为代表的进程间的通信方式的实现。 2. 内容及... -
线程和进/线程管道通信实验(操作系统实验报告二)
2014-08-06 14:34:48通过 Linux 系统中线程和管道通信机制的实验,加深对于线程控制和管道通信概念的理解,观察和体验并发进/线程间的通信和协作的效果 ,练习利用无名管道进行进/线程间通信的编程和调试技术 -
实验2-进程和进程通信实验报告
2012-10-27 20:36:04操作系统的第二次作业进程和进程通信,里面截图中的姓名与学号自己PS改一下吧 -
管道通信实验报告
2018-07-02 13:25:53掌握利用管道实现进程间通信的方法编写一段程序,实现进程的管道通信:1.使用系统调用pipe()建立一条管道线。两个子进程p1和p2分别向管道各写一句话: Child 1 is sending a message! Chile 2 is sending a ...实验目的
1.熟悉管道的概念
2.掌握利用管道实现进程间通信的方法
实验内容
编写一段程序,实现进程的管道通信:
1.使用系统调用 pipe()建立一条管道线。两个子进程p1和p2分别向管道各写一句话:
Child 1 is sending a message!
Chile 2 is sending a message!
2.父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
部分代码(仅供参考)
父进程
wait(0); //等待管道中有信息
read(fd[0],inpipe,50); // 从管道里读信息
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);exit(0);
子进程
sprintf(outpipe,"message from child one!!!!");
write(fd[1],outpipe,50); //往管道里写消息
sleep(2);exit(0);
注意
1.父子进程之间的同步关系如何实现?
2.当两个子进程往管道里面写信息的时候是否需要对管道实现互斥访问?#include<unistd.h> #include<stdio.h> #include<signal.h> #include<stdlib.h> int pid1,pid2; void main() { int fd[3]; char outpipe[100],inpipe[100]; pipe(fd); while((pid1=fork())==-1); if(pid1==0) { printf("p1\n"); lockf(fd[1],1,0); sprintf(outpipe,"message from child one!!!!"); sleep(5); write(fd[1],outpipe,50); sleep(2); lockf(fd[1],0,0); exit(0); } else { while((pid2=fork())==-1); if(pid2==0) { printf("p2\n"); lockf(fd[1],1,0); sprintf(outpipe,"message from child two!!!!"); write(fd[1],outpipe,50); lockf(fd[1],0,0); exit(0); } else { printf("parent\n"); wait(0); read(fd[0],inpipe,50); printf("%s\n",inpipe); wait(0); read(fd[0],inpipe,50); printf("%s\n",inpipe); exit(0); } } }
代码运行截图:
-
进程间通信之管道通信实验内容分析
2021-05-17 14:29:02标签:操作系统(344)Linux(1563)8.7.1 管道通信实验1.实验目的通过编写有名管道多路通信实验,读者可进一步掌握管道的创建、读写等操作,同时,也复习使用select()函数实现管道的通信。2.实验内容读者还记得在... -
操作系统进程间通信实验报告(源码)
2009-11-18 14:37:07该实验报告详细描述了进程间通信的实现原理,使用管道以及信号实现进出那个间通信,附有源代码实现。 -
2.4 实验四:使用命名管道实现进程通信.doc
2021-09-19 04:27:242.4 实验四:使用命名管道实现进程通信.doc -
Linux进程间通信(二)---管道通信之无名管道及其基础实验
2021-05-17 18:38:23管道简介管道是Linux中进程间通信的一种方式,它把一个程序的...)1、无名管道无名管道是Linux中管道通信的一种原始方法,如图一(左)所示,它具有以下特点:① 它只能用于具有亲缘关系的进程之间的通信(也就是父子进... -
linux实现软中断通信和管道通信报告(含代码)
2019-01-26 01:50:302) 编制实现进程的管道通信的程序 使用系统调用pipe()建立一条管道线,两个子进程分别向管道写一句话: Child process 1 is sending a message! Child process 2 is sending a message! 而父进程则从管道中读出... -
操作系统实验1--进程管理实验报告.docx
2020-07-15 13:42:04程的创建实验、进程的控制实验、进程互斥实验、守护进程实验、 信号机制实验、进程的管道通信实验、消息的发送与接收实验、 共享存储区通信 -
[转载]Linux进程间通信示例——管道
2021-05-17 17:46:16使用管道进行通信编写程序建立一个无名管道,然后生成3个子进程,使这4个进程利用同一个管道进行通信。分别试验3写1读、2写2读情况,多次执行,看结果是否一致,并对记录的执行结果进行解释。【注】没有关于管道的... -
《操作系统》实验报告——进程通信
2020-12-14 19:48:51(1)阅读以上父子进程利用管道进行通信的例子(例1),写出程序的运行结果并分析。 (2)编写程序:父进程利用管道将一字符串交给子进程处理。子进程读字符串,将里面的字符反向后再交给父进程,父进程最后读取并... -
操作系统实验报告(进程的管道及消息通信).doc
2020-06-06 01:11:18实验题 目 进程的管道及消息通 信 小组 合作 否 姓名 班级 学 号 一实验目的 1了解什么是管道 2熟悉LINUX支持的管道通信方式 3了解Linux系统中的进程间通信机制包括共 享内存和信号量 二实验环境 安装了Linux虚拟机... -
实验二进程通信-Linux实验报告
2021-05-17 11:18:22《实验二进程通信-Linux实验报告》由会员分享,可在线阅读,更多相关《实验二进程通信-Linux实验报告(9页珍藏版)》请在人人文库网上搜索。1、实验报告学号_____ 姓名____ ___ 成绩 实验二 进程通信 【实验目的和要求... -
浙江理工大学操作系统实验2:进程通信报告
2020-01-28 15:20:47(1) 阅读以上父子进程利用管道进行通信的例子(例1),写出程序的运行结果并分析。 (2) 编写程序:父进程利用管道将一字符串交给子进程处理。子进程读字符串,将里面的字符反向后再交给父进程,父进程最后读取并打印... -
实验二进程通信 Linux实验报告
2021-05-10 07:13:59《实验二进程通信 Linux实验报告》由会员分享,可在线阅读,更多相关《实验二进程通信 Linux实验报告(9页珍藏版)》请在人人文库网上搜索。1、实验报告学号_____ 姓名____ ___ 成绩 实验二 进程通信 【实验目的和要求... -
linux信号处理编程实验报告,实验二进程通信-Linux实验报告
2021-05-17 11:18:22《实验二进程通信-Linux实验报告》由会员分享,可在线阅读,更多相关《实验二进程通信-Linux实验报告(9页珍藏版)》请在人人文库网上搜索。1、实验报告学号_____ 姓名____ ___ 成绩 实验二 进程通信 【实验目的和要求... -
2020年新版操作系统实验报告进程的管道及消息通信.docx
2020-10-28 23:00:11实验题目 进程的管道及消息通信 小组合作 否 姓名 班级 学 号 一实验目的 1 为了掌握linux进程通信...能力 2 理解进程通信的原理掌握管道通信机制熟悉无名管道和有名 管道通信的实现 二.实验环境 Windows xpVMwareLinux