(转)I/O多路复用详解(三)
(转)并发网络编程学习之路(二):多进程与进程池(续)

(整理)并发编程之一:多进程

孔令春 posted @ 2009年10月18日 21:37 in 网络安全 with tags 多进程编程 , 3198 阅读

        多进程编程的主要内容包括进程控制和进程间通信:

一、Linux下进程控制

        在传统的Unix环境下,有两个基本的操作用于创建和修改进程:

         fork():用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;

         exec函数族:用来启动外部程序以取代当前的进程(由于此类函数并不是只有一个,而是六个,所以统称exec函数族)。

下面分别介绍一下:

1、 fork函数

        fork在英文中是“分叉”的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这个名字取得很形象。下面来看一下它的格式:

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
    正确返回:在父进程中返回子进程的进程号,在子进程中返回0
    错误返回:-1

      使用fork函数后,系统就会在此时新建一个进程(称为子进程)并且拷贝原有进程(称为父进程)的上下文和数据(子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。),并且利用fork函数的不同返回值(>0表示父进程,0表示子进程,-1表示出错)来标识。这样一来,子进程就会和父进程彼此独立运行,互不干扰。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(void)
{
        pid_t pid=fork();
        if(pid==0)
        {
                int j ;
                for(j=0;j<10;j++)
                {
                        printf("child: %d\n",j);
                        sleep(1);
                }
        }
        else if (pid>0)
        {
                int i;
                for(i=0;i<10;i++)
                {
                        printf("parent: %d\n",i);
                        sleep(1);
                }
        }
        else
        {
                fprintf(stderr,"can't fork ,error %d\n",errno);
                exit(1);
        }
        printf("This is the end !\n");
        return 0;
}

     运行了这段代码,我想应该可以明白fork了吧。运行的时候可以查看进程(ps -aux),会发现有两个一样的进程,运行结束后最后一句printf会运行两次,因为每个进程都会运行一次.中间的交替就是进程的调度了。

 

 2、exec函数族

       如果想运行外部程序就要用到exec函数族,这种函数一共有六个,它们分别是:

/*
 * 版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
 * http://tuhao.blogbus.com/logs/22833492.html
*/

extern char **environ;

int execl(const char* fullpath, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* fullpath, const char* arg , ..., char* const envp[]);
int execv(const char* fullpath, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* fullpath, const char* arg[] , char* const envp[]);

int execl(const char* fullpath, const char* arg, ....)
使用范例:execl(“/bin/ls”, ”ls”, ”-al”, NULL)

int execlp(const char* file, const char* arg, ....)
使用范例:execlp(“ls”, ”ls”, ”-al”, NULL)

int execle(const char* fullpath, const char* arg, ...., char* const envp[])
使用范例:execle(“/bin/ls”, ”ls”, ”-al”, NULL, environ)

int execv(const char * fullpath, char* const argv[])
使用范例:execle(“/bin/mkdir”, argv) // int main(int argc, char* argv[])
或
char* const p[] = {"a.out", "testDir", NULL};
execv("/bin/mkdir", p);

int execvp(const char* file, const char* arg, ....)
使用范例:execlp(“ls”, argv) // int main(int argc, char* argv[])
或
char* const p[] = {"a.out", "testDir", NULL};
execvp("mkdir", p);

int execve(const char* fullpath, const char* arg, ...., char* const envp[])
使用范例:execve(“/bin/ls”, argv, environ)
或
char* const p[] = {"a.out", "testDir", NULL};
execve("/bin/mkdir", p);

 下面一段代码显示如何配合fork函数启动运行其它程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

char command[256];
int main()
{
   int rtn; /*子进程的返回数值*/
   while(1) 
   {
        /* 从终端读取要执行的命令 */
		printf( ">" );
		fgets( command, 256, stdin );
		command[strlen(command)-1] = 0;
		if ( fork() == 0 ) {
			/* 子进程执行此命令 */
			execlp( command, command, NULL);
			/* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
			perror( command );
			exit(errno);
		}
		else {
			/* 父进程, 等待子进程结束,并打印子进程的返回值 */
			wait ( &rtn );
			printf( " child process return %d\n",rtn );
		}
	}
} 

 

 二、Linux下的进程间通信

      创建一个新进程后,两个进程就会彼此独立运行,互不干扰,那么如果需要两个进程之间通信,该怎么办呢?在Unix/Linux下常用的进程间通信的方法有很多种,如管道、消息队列、共享内存、信号量、套接口等。下面我们以管道为例来介绍:

     管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。

1、(无名)管道

无名管道由pipe函数创建:

       #include <unistd.h>

       int pipe(int filedis[2]);

      参数filedis返回两个文件描述符:filedes[0]为读而打开;filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define INPUT 0
#define OUTPUT 1

int main() 
{
	int file_descriptors[2];
	/*定义子进程号 */
	pid_t pid;
	char buf[256];
	int returned_count;

	/*创建无名管道*/
	pipe(file_descriptors);
	
	/*创建子进程*/
	if((pid = fork()) == -1) 
	{
		printf("Error in fork\n");
		exit(1);
	}

	/*执行子进程*/
	if(pid == 0) 
	{
		printf("in the spawned (child) process...\n");

		/*关闭管道的读端,并且将数据"test data"从写端写入*/
		close(file_descriptors[INPUT]);
		write(file_descriptors[OUTPUT], "test data", strlen("test data"));
		exit(0);
	}
	else 
	{
		/*执行父进程*/
		printf("in the spawning (parent) process...\n");

		/*关闭管道的写端,父进程从管道读取子进程传来的数据*/
		close(file_descriptors[OUTPUT]);
		returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));

		printf("%d bytes of data received from spawned process: %s\n",
			returned_count, buf);
	}
}

2、有名管道     

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod和函数mkfifo。下面的两种途径者在当前目录下生成了一个名为myfifo的有名管道:

              方式一:mknod myfifo p          //p-表示创建FIFO特殊文件

              方式二:mkfifo("myfifo", "rw")  //rw表示读、写两种权限

      生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面是一个简单的例子:

/*
 * fifo_read.c
 * 管道通信:有名管道
 * 无名管道只能用于具有亲缘关系的进程之间,而有名管道可以在互不相关的两个进程间
 * 实现彼此通信。要注意,FIFO严格按照先进先出的规则,对管道及FIFO的读总是从开始
 * 处返回数据,对它们的写则把数据添加到末尾,不支持lseek等文件定位操作。
 *
 * 有名管道的创建使用mkfifo()。创建成功后就可以使用open、read、write这些函数了。
 * 读管道部分
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*在这里设置打开管道文件的mode为只读形式*/
#define FIFOMODE (O_CREAT | O_RDWR | O_NONBLOCK)
#define OPENMODE (O_RDONLY | O_NONBLOCK)
#define FIFO_SERVER "myfifo"

int main(void)
{
        char buf[100];
        int fd;
        int readnum;

        /*创建有名管道,设置为可读写,无阻塞,如果不存在则按照指定权限创建*/
        if ((mkfifo(FIFO_SERVER, FIFOMODE) < 0) && (errno != EEXIST)) {
                printf("cannot create fifoserver\n");
                exit(1);
        }

        printf("Preparing for reading bytes... ...\n");

        /*打开有名管道,并设置非阻塞标志*/
        if ((fd = open(FIFO_SERVER, OPENMODE)) < 0) {
                perror("open");
                exit(1);
        }

        while (1) {
                /*初始化缓冲区*/
                bzero(buf, sizeof(buf));
                /*读取管道数据*/
                if ((readnum = read(fd, buf, sizeof(buf))) < 0) {
                        if (errno == EAGAIN) {
                                printf("no data yet\n");
                        }
                }
                /*如果读到数据则打印出来,如果没有数据,则忽略*/
                if (readnum != 0) {
                        buf[readnum] = '\0';
                        printf("read %s from FIFO_SERVER\n", buf);
                }
                sleep(1);
        }

        return 0;
}
/*
 * fifo_wirte.c
 * 管道通信:有名管道
 * 无名管道只能用于具有亲缘关系的进程之间,而有名管道可以在互不相关的两个进程间
 * 实现彼此通信。要注意,FIFO严格按照先进先出的规则,对管道及FIFO的读总是从开始
 * 处返回数据,对它们的写则把数据添加到末尾,不支持lseek等文件定位操作。
 *
 * 有名管道的创建使用mkfifo()。创建成功后就可以使用open、read、write这些函数了。
 * 写管道部分
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/*特别注意写管道时,设置打开管道文件的格式必须为可写*/
#define FIFO_SERVER "myfifo"
#define OPENMODE (O_WRONLY | O_NONBLOCK)

int main(int argc, char **argv)
{
        int fd;
        int nwrite;

        /*打开管道文件,可写非阻塞*/
        if ((fd = open(FIFO_SERVER, OPENMODE)) < 0) {
                perror("open");
                exit(1);
        }

        /*如果没有在命令行中写入参数,那么要重新运行程序*/
        if (argc == 1) {
                printf("Please send something\n");
                exit(1);
        }

        /*向管道文件中写入数据,在这里要用strlen,如果用sizeof,则只是4个字节的指针长度*/
        if ((nwrite = write(fd, argv[1], strlen(argv[1]))) < 0) {
                if (errno == EAGAIN) {
                        printf("The FIFO has not been read yet.Please try later\n");
                }
        }
        else {
                printf("write %s to FIFO\n", argv[1]);
        }

        return 0;
}

       测试过程,开两个窗口,先打开fifo_read,然后打开fifo_write写入数据,观察情况。

参考网址:http://www.linuxdiyf.com/viewarticle.php?id=6195


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter