目的
(1)在理解进程的概念及其生命周期和状态转换过程的基础上,进行进程相关编程;
(2)在理解进程间通信的基本原理的基础上,编程实现进程间通信。
内容
(1)在Linux中利用fork()在父进程A中创建一个子进程B,再在子进程B中创建一个子进程C,在3个进程中都要编写适当的语句模拟进程的工作。
(2)创建子进程时将复制父进程的所有内容,此时的复制对象也包含套接字文件描述符。编写程序验证复制的文件描述符整数值是否与原文件描述符整数值相同。
(3)如果在未注册SIGINT信号的情况下输入Ctrl+C,将由操作系统默认的事件处理器终止程序。但如果直接注册Ctrl+C信号的处理器,则程序不会终止,而是调用程序员指定的事件处理器。编写注册处理函数的程序,完成如下功能:
“输入Ctrl+C时,询问是否确定退出程序,输入Y则终止程序。” 另外,编写程序使其每隔1秒输出简单字符串,并适用于上述时间处理器注册代码。
(4)利用fork()实现并发服务器,且让客户端的读写操作由两个进程分别处理。
(5)编写程序实现进程间通信,使2个进程相互交换3次字符串。当然,这2个进程应具有父子关系,各位可指定任意字符串。
实验代码及结果
(1)在Linux中利用fork()在父进程A中创建一个子进程B,再在子进程B中创建一个子进程C,在3个进程中都要编写适当的语句模拟进程的工作。
//fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gval=10;
int main(int argc, char *argv[])
{
//父进程为进程A
pid_t pid,pid1;
int lval=20;
gval++, lval+=5;
//创建子进程B
pid=fork();
if(pid==0)//如果是子进程B
{
gval+=2, lval+=2;
pid1=fork();//用子进程创建子进程C
if(pid1==0)//如果是进程C
{
gval+=2, lval+=2;
}
else//如果是进程B
gval-=2, lval-=2;
}
else //如果是父进程A
gval-=2, lval-=2;
if(pid==0&&pid1!=0)
printf("Child Proc: [%d, %d] \n", gval, lval);
else if(pid1==0)
printf("Child child Proc: [%d, %d] \n", gval, lval);
else
printf("Parent Proc: [%d, %d] \n", gval, lval);
return 0;
}
测试结果:
(2)创建子进程时将复制父进程的所有内容,此时的复制对象也包含套接字文件描述符。编写程序验证复制的文件描述符整数值是否与原文件描述符整数值相同。
//fockfdcheck.c
#include<stdio.h>
#include <unistd.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
pid_t pid;
int sock=socket(PF_INET, SOCK_STREAM, 0);
pid=fork();
if(pid==0)
{
puts("Hi,I am a child process");
//输出子进程中的sock值
printf("Child sock fd: [%d] \n\n", sock);
}
else
{
printf("Child Process ID:%d\n",pid);
//输出父进程中的sock值
printf("Parent sock fd: [%d] \n\n", sock);
}
//查看父进程与子进程的sock值是否一致
close(sock);
return 0;
}
测试结果:
(3)如果在未注册SIGINT信号的情况下输入Ctrl+C,将由操作系统默认的事件处理器终止程序。但如果直接注册Ctrl+C信号的处理器,则程序不会终止,而是调用程序员指定的事件处理器。编写注册处理函数的程序,完成如下功能: “输入Ctrl+C时,询问是否确定退出程序,输入Y则终止程序。” 另外,编写程序使其每隔1秒输出简单字符串,并适用于上述时间处理器注册代码。
//sigaction.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//处理函数
void keycontrol(int sig)
{
char c;
if(sig==SIGINT)
{
fputs("Do you want to exit?[if so,put Y]:",stdout);
scanf("%c",&c);
if(c=='Y'||c=='y')
exit(0);
}
}
int main(int argc, char *argv[])
{
int i;
struct sigaction act;
//注册信号
act.sa_handler=keycontrol;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
//注册SIGINT信号处理器
sigaction(SIGINT, &act, 0);
for(i=0; i<100; i++)
{
puts("wait...");
sleep(1);
}
return 0;
}
测试结果:
(4)利用fork()实现并发服务器,且让客户端的读写操作由两个进程分别处理。
//服务端
//mpserv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
pid_t pid;
struct sigaction act;
socklen_t adr_sz;
int str_len, state;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
//注册信号
act.sa_handler=read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
state=sigaction(SIGCHLD, &act, 0);
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
while(1)
{
adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
if(clnt_sock==-1)
continue;
else
puts("new client connected...");
pid=fork();
if(pid==-1)
{
close(clnt_sock);
continue;
}
if(pid==0)//如果是子进程,负责接收一个客户端的信息,如果是父进程,继续监听
{
close(serv_sock);
while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0)
write(clnt_sock, buf, str_len);
close(clnt_sock);
puts("client disconnected...");
return 0;
}
else
close(clnt_sock);
}
close(serv_sock);
return 0;
}
//信号产生时的处理事件
void read_childproc(int sig)
{
pid_t pid;
int status;
pid=waitpid(-1, &status, WNOHANG);
printf("removed proc id: %d \n", pid);
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
//客户端
//mpclient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
int main(int argc, char *argv[])
{
int sock;
pid_t pid;
char buf[BUF_SIZE];
struct sockaddr_in serv_adr;
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("connect() error!");
pid=fork();
if(pid==0)
//如果是子进程,负责写
write_routine(sock, buf);
else
//否则是父进程,负责读
read_routine(sock, buf);
close(sock);
return 0;
}
//读函数
void read_routine(int sock, char *buf)
{
while(1)
{
int str_len=read(sock, buf, BUF_SIZE);
if(str_len==0)
return;
buf[str_len]=0;
printf("Message from server: %s", buf);
}
}
//写函数
void write_routine(int sock, char *buf)
{
while(1)
{
fgets(buf, BUF_SIZE, stdin);
if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
{
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
测试结果:
(5)编写程序实现进程间通信,使2个进程相互交换3次字符串。当然,这2个进程应具有父子关系,各位可指定任意字符串。
//pidmessage.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
int fds1[2], fds2[2];
char *str1[]={"hello","nice to meet you","good bye"};//子进程需要发送的字符串
char *str2[]={"hello","nice to meet you too","bye"};//父进程需要发送的字符串
char buf[BUF_SIZE];
pid_t pid;
int i;
pipe(fds1), pipe(fds2);//建立管道
pid=fork();
//利用管道进行进程间的通信
if(pid==0)
{
for(i=0; i<3; i++)
{
write(fds1[1], str1[i], strlen(str1[i])+1);
read(fds2[0], buf, BUF_SIZE);
printf("Child proc output: %s \n", buf);
}
}
else
{
for(i=0; i<3; i++)
{
read(fds1[0], buf, BUF_SIZE);
printf("Parent proc output: %s \n", buf);
write(fds2[1], str2[i], strlen(str2[i])+1);
sleep(2);
}
}
return 0;
}
测试结果:
对于网络编程中,创建多个进程能够让服务端实现与多个客户端通信,从而实现了服务端的并发性,提高了通信效率。
一开始对于fork函数的理解有点误区,fork函数是通过复制正在运行、调用fork函数的进程来工作的,复制相同的内存空间,通过返回值加以区分。同时需要特别注意,父子进程拥有完全独立的内存结构。