存档在 2011年3月

基于Qt的多窗口设计-窗体切换的实现

2011年3月26日

窗体类的关系

在布局管理部分,我们已经说过将主界面和两个子系统分别封装成三个不同的类。首先我们讨论每个类中应该封装什么,其次再讨论这三个类之间的具体关系。

通过Qt Dsigner设计好界面后会在工程文件中对应一个.ui的文件;编译器会将该文件转换成能被C++所识别的.h文件。比如configUI.ui文件就对应一个ui_configUI.h文件,该头文件中包含了类Ui::configUI的定义。这个类完全与我们所设计的用户界面等价,也就是说它包含对主界面中各个部件的定义、该界面的布局以及初始化窗体的setupUi()等。

但是应该注意的是,该类仅仅是对我们设计的界面的一种等价转化。我们对该界面的实际操作(比如对各种槽以及信号的定义)并不包含在该类中。为了完成配置系统的具体功能,我们会创建一个新类configUI,并让这个新类继承Ui::configUI。通常在configUI类中会有一个Ui::configUI类的成员变量ui,因此可以通过ui直接获取界面中的各个部件。

注意,由于界面所对应的类configUI被定义在UI这个名字空间中,因此两个同名的类并不会发生名字冲突。这两个类的关系可以通过下面的代码进一步理解:

//将界面对应的configUI类定义在Ui名字空间中;
namespace Ui {
    class configUI;
}

class configUI : public QWidget
{
    Q_OBJECT

public:
    explicit configUI(QWidget *parent = 0);
    ~configUI();
//将 Ui::configUI的对象作为configUI的成员变量;
private:
    Ui::configUI *ui;

private slots:
//在此定义槽函数;
};

在configUI类中,还可以根据需要包含一些成员函数、信号和槽函数等。其他两个界面对应的类也有类似的封装关系,不再赘述。

在布局管理中,通过点击主界面上的按钮就可以切换到相应的窗体。由此引发出我们对这几个窗体类之间关系的思考。具体的做法是,我们可以将两个子系统对应类的对象作为主界面类的成员变量,比如:

namespace Ui {
    class frontPage;
}

class frontPage : public QWidget
{
    Q_OBJECT

public:
    explicit frontPage(QWidget *parent = 0);
    ~frontPage();

private:
    Ui::frontPage *ui;
    configUI *configWidget;
    logUI *logWidget;

signals:
    //在此定义信号;

private slots:
   //在此定义槽函数;
};

那么在切换各个窗体时,就可以方便的通过show()和hide()近来完成。至此,我们已经完成了界面的设计和类的定义,下面要做的就是实现具体窗口的跳转工作。

信号和槽函数的设计

由上文可得知,我们要实现的功能即通过点击每个按钮就可以跳转到相应的窗口。所以三个窗体对应的按钮就对应三个槽函数,触发这几个槽函数的信号即为clicked()。在类frontPage中对上述三个槽函数的声明如下:

signals:
    void goToWidget(int);
private slots:
    void on_logSysBtn_clicked();
    void on_frontPageBtn_clicked();
    void on_configSysBtn_clicked();
    void on_quitBtn_clicked();
    void runningWidget(int);
    void on_quitBtn_clicked();

除了三个窗口按钮对应的槽函数外,还包含其他函数的声明,我们在稍候会解释。按照以往的做法,我们声明了按钮对应的槽函数后,就应该依次去实现这些函数以便实现窗体间的跳转。不过,由于本程序中窗体跳转之间有一定的规律,所以将采用下面的方法:

void frontPage::on_frontPageBtn_clicked()
{
    emit goToWidget(0);
}

void frontPage::on_configSysBtn_clicked()
{
    emit goToWidget(1);
}

void frontPage::on_logSysBtn_clicked()
{
    emit goToWidget(2);
}

也就是说,在每个按钮对应的槽函数中再次发送goToWidget(int)信号,不同按钮将传递不同的整数。在这里我们使用emit关键字显示的发送信号,这和平时我们在Qt Designer中所使用的关联方发不同,但是本质是相同的。由于这个信号是我们自己定义的,并且信号本身就是一个函数,因此需要在frontPage类中对这个信号进行声明,具体可参见上面的示例代码。

我们将此信号和槽函数runningWidget(int)关联。也就是说点击不同窗体对应的按钮都会执行runningWidget(int)槽函数。只不过在该函数内部则会根据所传递的整形参数执行不同的程序段。runningWidget函数的示例代码如下:

void frontPage::runningWidget(int widgetNum)
{
    switch (widgetNum) {
    case 0:
        ui->introTextBrowser->show();
        configWidget->hide();
        logWidget->hide();
        break;
    case 1:
        ui->introTextBrowser->hide();
        configWidget->show();
        logWidget->hide();
        break;
    case 2:
        ui->introTextBrowser->hide();
        configWidget->hide();
        logWidget->show();
        break;
    }
}

从上面的代码中可以看出,通过传递不同的整形参数就可以显示三个窗体中的某一个,并同时隐藏另外两个窗体。通过上面的方法,这样就可以实现多窗体之间的切换了。另外,退出按钮的槽函数的实现基本方法即为直接调用exit函数。

至此,一个基本的多窗体程序就设计完成了。

基于Qt的多窗口编程-界面的设计

2011年3月25日

对于应用程序中的多窗体切换,我们已经习以为常。通常一个应用程序中,不同的窗口代表不同功能的工作区。本文将详细描述基于Qt的多窗体程序的设计方法。在阅读本文之前,你最好了解面向对象的基本思想以及Qt的基本使用方法。

接下来我们以编写一个客户端为例来具体说明多窗体程序的编程方法。该客户端包括多个子系统,每个子系统对应一个窗口;在客户端的主界面,通过点击相应的按钮实现多个窗体之间的切换。主界面图如下:

 

如图所示,该客户端主界面包含顶部的标签、右方的按钮区、中间的主窗口区以及底部的状态栏。通过点击不同的按钮则进入不同的子系统。配置信息子系统和日志信息管理子系统界面设计如下:

布局管理

上述三个界面都可以通过Qt Designer轻松设计完成,这里只对窗体部件的布局管理作说明。通常我们可以显示指定窗体中各个部件的大小以及位置,但是这样麻烦而且缺少灵活性。因此在实际的应用中,使用布局管理器对窗体中的各个子部件进行布局管理。布局管理器会为每个窗体部件提供合理的默认值,并随着个别部件大小的变化而调整整体的窗体布局。

常用的布局管理器有QHBoxLayout、QVBoxLayout和QGridLayout,即依次为水平布局管理、垂直布局管理器和网格布局管理器。结合上面的主界面图示,其对应的布局管理图如下:

使用布局管理器的一般方法为:先创建相应布局管理器的对象;再将要进行布局管理窗体部件加入到布局管理器对象中;为当前窗体设置该布局管理器。除了可以将窗体部件加入到当前的布局管理器中,还可以将另外一个布局管理器对象加入到当前的布局管理器中;此外,设置布局管理器只需在所有布局管理器都添加好之后进行一次的设置即可。具体使用方法可参考随后给出的示例代码。

在topLayout这个顶部的布局管理器中,我们仅加入一个标签。正如上面所述,我们首先创建一个水平布局管理器对象,再将这个mainlabel标签加入其内。

    QHBoxLayout *topLayout = new QHBoxLayout;
    topLayout->addWidget(ui->mainLabel);

右边的按钮区的布局管理rightBtnLayout和上述类似,只不过我们此时使用的是垂直管理器。接下来重点说一下主窗口区布局管理器widgetLayout的设置。两个子系统和主界面的功能不同,因此将他们分别封装成类configUI、logUI和frontPage,我们依次创建三个类的对象。接下来将这三个部件加入到widgetLayout中。由于客户端初始显示主界面,则我们将其他两个窗体暂时隐藏。

    QVBoxLayout *widgetLayout = new QVBoxLayout;
    configWidget = new configUI(this);
    logWidget = new logUI(this);
    mainMenu = new frontPage(this);
    widgetLayout->addWidget(ui->introTextBrowser);
    widgetLayout->addWidget(configWidget);
    widgetLayout->addWidget(logWidget);
    configWidget->hide();
    logWidget->hide();

我们现在已经设置好了rightBtnLayout和widgetLayout,接下来将这两个布局管理器加入到mainWindowLayout中。

    QHBoxLayout *mainWindowLayout = new QHBoxLayout;
    mainWindowLayout->addLayout(widgetLayout);
    mainWindowLayout->addLayout(rightBtnLayout);

这样mainWindowLayout布局管理器就设置好了。最后我们还需要设置bottomLayout布局管理器,只是简单的添加一个标签和一个水平分割线。

至此,我们拥有三个二级布局管理器:topLayout、mainWindowLayout和bottomLayout,我们将这三个布局管理器全部添加到顶级布局管理器mainLayout中:

   QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(topLayout);
    mainLayout->addLayout(mainWindowLayout);
    mainLayout->addLayout(bottomLayout);
    this->adjustSize();

    setLayout(mainLayout);

此时,我们就设置好了主界面的布局。

Linux下的socket编程-基于Qt的客户端

2011年3月20日

上文中对面向连接的C/S模型中的服务器进行了简单描述,本文将说明如何编写一个客户端应用程序。与服务器程序不同,客户端程序要求有友好的交互界面,因此本文所示的客户端程序采用Qt和linux C共同完成。客户端的界面部分采用Qt完成,而与服务器间具体的通信部分则通过linux C语言完成。

1.界面设计

通过Qt Designer可快速设计好客户端界面,并对四个按钮所对应的信号和槽函数进行连接。客户端所对应的类为ClientUI。

2.与服务器间通信的编程

由于Qt是对C++的一种扩展,因此我们必须将使用Linux C编写的客户端通信程序封装成类,这里我们将其定义为ClientConnection。

我们已经对客户端的界面和通信部分分别定义了两个类,但是如何将两者结合在一起?方法很简单。将ClientConnection类的对象cli作为ClientUI类的成员,然后在具体的槽函数中对应相应的通信函数。和上文分析服务器编程的方法不同,本文将以分析客户端对应的槽函数的方式来说明如何编写客户端程序。

2.1连接按钮的槽函数

在连接按钮对应的槽函数中,首先创建客户端套接字描述符;然后从输入框中获得服务器的IP地址;再通过connect函数创建一个连接。

connect函数的原型如下:

     #include < sys/socket.h >
     int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数用于在sockfd套接字上创建一个连接,sockfd即客户端套接字;addr即为服务器端的套接字地址;addrlen为addr的长度。我们将connect函数封装成ClientConnection类中的connectingSocket函数,该成员函数在连接按钮所对应的槽函数中被调用。:

int ClientConnection::connectingSocket()
{
    if (::connect(sockfd, (struct sockaddr *)&serv_addr,
                          sizeof(struct sockaddr)) == -1) {
        return 0;
    }

    return 1;
}

在该槽函数中,可以这样使用连接函数:

    if (cli.connectingSocket() == 1) {
        ui->statusLabel->setText("connecting to server success!");
    } else {
        ui->statusLabel->setText("ERROR:connecting to server fail!");
        //exit(1);
    }

当客户端连接至服务器成功后,客户端就可以跟服务器端进行数据的传送。

2.2 修改按钮的槽函数

该槽函数的工作比较简单,使得文本编辑区可被编辑。

2.3 应用按钮的槽函数

在该槽函数中,将文本编辑区的数据发送至服务器端;或从服务器接受数据显示在这个文本编辑区中。发送和接受数据的API仍然是send和recv函数,不过这里我们将这两个函数封装成ClientConnection类中的sendData成员和recvData成员:

int ClientConnection::recvData(char *buf)
{
    if ((recvbytes = recv(sockfd, buf, MAX_DATA_NUM, 0)) == -1) {
        return 0;
    }

    buf[recvbytes] = '\0';
    return 1;
}

int ClientConnection::sendData(char *msg, int len)
{
    if (send(sockfd, msg, len, 0) == -1) {
        printf("send error!\n");
        return 0;
    }

    return 1;
}

这两个成员函数在该槽函数的适当位置被调用。

2.4退出按钮的槽函数

退出按钮的槽函数中只需断开客户端和服务器之间的连接即可。

参考:

1.Qt Assistant

2.Linux C编程实战;童永清 著;人民邮电出版社;

Linux下的socket编程-服务器

2011年3月15日

我们都知道,同一台计算机上的进程可以通过IPC(进程间通信)机制进行通信;而不同机算计上运行的进程则通过网络IPC,即套接字(socket)进行通信。Linux下的socket API是基于BSD套接口而是实现的,通过这些统一的API就可以轻松实现进程间的网络通信。此外,socket API即可用于面向连接(TCP)的数据传输,又可用于无连接(UDP)的数据传输。一般使用Client/Server交互模型进行通信。

本文以及下文将实现一个面向连接的C/S通信模型。本文首先介绍服务器端的实现。

1.创建套接字

#include < sys/socket.h >
int socket(int domain, int type, int protocol);

通过socket函数可以创建一个套接字描述符,这个描述符类似文件描述符。通过这个套接字描述符就可以对服务器进行各种相关操作。

该函数包含三个参数,domain参数用于指定所创建套接字的协议类型。通常选用AF_INET,表示使用IPv4的TCP/IP协议;如果只在本机内进行进程间通信,则可以使用AF_UNIX。参数type用来指定套接字的类型,SOCK_STREAM用于创建一个TCP流的套接字,SOCK_DGRAM用于创建UDP数据报套接字。参数protocol通常取0。对于本文所描述的服务器,创建套接字的示例代码如下:

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

		printf("socket error!\n");
		exit(1);
	}

2.绑定套接字

对于服务器而言,它的IP地址和端口号一般是固定的。服务器的IP即为本地IP,而服务器的端口号则需要显示的指定。通过bind函数可将服务器套接字和一个指定的端口号进行绑定。

在具体介绍绑定函数之前,先说明一下socket中的套接字地址结构。由于套接字是通过IP地址和端口号来唯一确定的,因此socket提供了一种通用的套接字地址结构:

   struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
           }

sa_family指定了套接字对应的协议类型,如果使用TCP/IP协议则改制为AF_INET;sa_data则用来存储具体的套接字地址。不过在实际应用中,每个具体的协议族都有自己的协议地址格式。比如TCP/IP协议组对应的套接字地址结构体为:

struct sockaddr_in {
	short int sin_family; /* Address family */
	unsigned short int sin_port; /* Port number */
	struct in_addr sin_addr; /* Internet address */
	unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};

struct in_addr {
	unsigned long s_addr;
};

该地址结构和sockaddr结构均为16字节,因此通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置具体地址,然后再通过强制类型转换为sockaddr类型。

绑定函数的函数原型如下:

       #include < sys/socket.h >
       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数sockfd即服务器的套接字描述符;addr参数指定了将socket绑定到的本地地址;addrlen则为所使用的地址结构的长度。示例代码如下:

	memset(&my_addr, 0, sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(SERV_PORT);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(sockfd, (struct sockaddr *)&my_addr,
				sizeof(struct sockaddr_in)) == -1) {

		printf("bind error!\n");
		exit(1);
	}

注意在上述代码中,将IP地址设置为INADDR_ANY,这样就既适合单网卡的计算机又适合多网卡的计算机。

3.在套接字上监听

对于C/S模型来说,通常是客户端主动的对服务器端发送连接请求,服务器接收到请求后再具体进行处理。服务器只有调用了listen函数才能宣告自己可以接受客户端的连接请求,也就是说,服务器此时处于被动监听状态。listen函数的原型如下:

       #include < sys/socket.h >
       int listen(int sockfd, int backlog);

sockfd为服务器端的套接字描述符,backlog指定了该服务器所能连接客户端的最大数目。超过这个连接书目后,服务器将拒绝接受客户端的连接请求。示例代码如下:

        #define BACKLOG 10
	if(listen(sockfd, BACKLOG) == -1) {

		printf("listen error!\n");
		exit(1);
	}

4.接受连接

listen函数只是将服务器套接字设置为等待客户端连接请求的状态,真正接受客户端连接请求的是accept函数:

      #include < sys/socket.h >
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数中所使用的参数都在上面的API中有所描述。accept函数执行成功后将返回一个代表客户端的套接字描述符,服务器进程通过该套接字描述符就可以与客户端进行数据交换。

5.数据传输

由于socket适用多个数据传输协议,则不同的协议就对应不同的数据传输函数。与TCP协议对应的发送数据和接受数据的函数如下:

       #include < sys/socket.h >
       #include < sys/types.h >
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

从这两个函数的原型可以看书,socket中的数据传输函数与普通文件的读写函数类似,只不过第一个参数需要传递套接字描述符;buf指定数据缓冲区;len为所传输数据的长度;flag一般取0。示例代码如下:

	while (1) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
			printf("accept error!\n");
			continue;
		}
		/*
                 *进行相应的数据处理;
                 */
	}

如示例代码所示,通过while循环使得服务器对客户端进行持续监听。如果客户端有连接请求则新建一个代表客户端的套接字描述符,进而进行对客户端数据的接受和发送。

上述的几个函数属于网络编程中最基本的也是最关键的几个API,依次通过上述的方法就可以完成服务器端的程序的编写,具体的过程还可以参考下图:

正如上图所示,在处理完客户端的数据传输请求后,必须通过close函数关闭客户端的连接。

参考:

1.Linux C编程实战;童永清 著;人民邮电出版社;

exec族函数

2011年3月14日

通常我们使用了fork函数创建了一个子进程后,子进程会调用exec族函数执行另外一个程序。由于fork函数新建的子进程和父进程几乎一模一样(不过,后来引入了写时复制技术避免了这一点),因此需要使用exec函数使得子进程执行一个新的可执行程序。

我们之所以称exec为族函数是因为它有6种不同的形式,主要区别体现在参数上:

#include <unistd.h>;
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
             ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[],
              char *const envp[]);

总体来说,exec族函数的参数分为三类:文件路径,命令行参数,环境变量。不论那个exec函数,最终都是将上述三类参数传递给可执行程序中的main函数。这里有一个新的概念,即环境变量,它定义了用户的工作环境,包括用户的主目录,终端的类型,当前用户,当前目录等信息。事实上,main函数的完整形式应该是:

int main(int argc, char *argv[], char **envp);

通常我们并没有显示指定环境变量,这是因为main函数每次都默认使用系统自定义的全局变量environ。另外,通过在终端输入env命令,也可查看当前的所有环境变量信息。可以看到,main函数中的argv[]和envp与exec函数的参数有对应关系。

初次接触exec族函数,易被这6种不同的调用形式搞得混乱。下面我们将依次从上述三个参数的角度来区分这些函数的细微区别。

1.可执行文件的路径

以p结尾的exec函数可以直接传递可执行程序的文件名,此时这类函数会自动在PATH环境变量所指定的目录下查找这个可执行文件;非p结尾的exec函数则必须显示指定可执行程序的完整路径;

2.命令行参数

以execl开始的exec函数必须以列表的形式传递所有的命令行参数,并且最终以NULL作为命令行参数的结束;而以execv开始的exec函数则以字符串指针数组的形式传递所有命令行参数;

3.环境变量

以e结尾的exec函数必须显示指定环境变量;而非p结尾的exec函数则使用默认的环境变量,也就是main函数中的环境变量;

虽然exec函数有6种不同的调用形式,但是每个函数实现的功能都是一样的,即完成新的可执行程序的装入。下面的这个程序很好的演示了不同exec函数的使用方法:

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

int main()
{
	char *envp[]={
		"PATH = /tmp",
	        "USER = edsionte",
		NULL
	};

	char *argv_execv[] = {"echo", "excuted by execv", NULL};
	char *argv_execvp[] = {"echo", "executed by execvp", NULL};
	char *argv_execve[] = {"env", NULL};

	if (fork() == 0)
		if(execl("/bin/echo", "echo", "executed by execl", NULL) < 0)
			perror("Err on execl");
	if(fork() == 0)
		if(execlp("echo", "echo", "executed by execlp", NULL) < 0)
			perror("Err on execlp");
	if(fork() == 0)
		if(execle("/usr/bin/env", "env", NULL, envp) < 0)
			perror("Err on execle");
	if(fork() == 0)
		if(execv("/bin/echo", argv_execv) < 0)
			perror("Err on execv");
	if(fork() == 0)
		if(execvp("echo", argv_execvp) < 0)
			perror("Err on execvp");
	if(fork() == 0)
		if(execve("/usr/bin/env", argv_execve, envp) < 0)
			perror("Err on execve");

	printf("goodbye!\n");
	return 0;
}
//结果:
edsionte@edsionte-desktop:~/edsionte$ ./sys_process
goodbye!
edsionte@edsionte-desktop:~/edsionte$ executed by execl
excuted by execv
executed by execvp
executed by execlp
PATH = /tmp
USER = edsionte
PATH = /tmp
USER = edsionte

这个程序分别使用不同的exec函数调用了echo或env命令,并根据所传递的不同参数显示不同结果。该程序可能每次运行后的显示顺序都有所不同,这是进程的并发性所导致的。

值得注意的是,我们通常所说的命令行参数是以argv数组的第一个元素开始的,而第0号元素是可执行程序的名称。以第一个if语句为例,execl函数为/bin目录下的echo程序传递了两个参数:”echo”和”executed by execl”。由于我们通过execl函数已经装入了/bin/echo程序,因此在调用execl函数时,第一个参数”echo”可以替换成任意字符串,但是这并不代表就不需要传递该参数。

我们也可以让exec函数去执行我们自己编写的可执行程序。下面这个例子很好的演示了父进程和子进程的关系:

/*
 *Author: edsionte
 *Email:  edsionte@gmail.com
 *Time:   2011/03/14
*/

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

int main()
{
	int pid;
	int val = 0;

	pid = fork();
	if (pid == 0) {
		//child proces;
		printf("[Child %d] I will be a new process!\n", getpid());
		if (execl("./sleeping","first arg", "20", NULL) == -1) {
			printf("execl error!\n");
			exit(1);
		}
		printf("[Child %d] I never go here!\n", getpid());
	} else if (pid > 0) {
		printf("[Parent %d] I am running!\n", getpid());
		/*
		wait(&val);
		printf("[Parent %d] All my child were leaving..\n", getpid());
		*/
	} else {
		printf("fork error!\n");
		exit(1);
	}

	printf("[process %d] I am leaving..\n", getpid());
	return 0;
}
//sleeping.c程序
#include < stdio.h >
#include < unistd.h >

int main(int argc, char** argv)
{
	int sec;

	sec = atoi(argv[1]);
	while (sec != 0) {
		sleep(1);
		printf("[New Child %d] I am running and my parent is %d\n", getpid(), getppid());
		sec--;
	}

	return 0;
}

该程序使用了与进程有关的四个最基本的系统调用函数:fork(),exec(),wait()和exit()。对于该程序的具体分析如下:

1.父进程通过fork函数创建子进程,然后父进程打印自己的pid;

2.子进程首先打印自己的pid,再通过execve函数装入可执行程序sleeping,并通过execve函数向sleeping传递了一个参数“20”;

3.在sleeping程序中,将主函数中传递的参数作为计数器,并依次循环睡眠20s。并且循环打印当前的sleeping进程的pid以及其父进程pid;

4.由于父进程先于子进程退出,则sleeping进程成为孤儿进程,因此在sleeping打印的父进程pid为1;如果去掉程序中的注释部分,则父进程等待子进程退出后再退出,那么sleeping所打印的父进程pid即为父进程的pid;

5.如果子进程执行exec函数成功,则主程序中最后一条语句就不会被子进程执行。说明子进程在执行了exec函数后就与父进程完全脱离。另外,在子进程执行过程中也可以通过ps命令查看当前的进程存在情况;

参考:

1.http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part3/

2.Linux C编程实战;童永清 著;人民邮电出版社;

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key