一,前言(问题引出)
我们知道,在Linux下可以使用命令ifconfig eth0来获取网卡的IP地址,但如果我们想在C程序代码里获取IP地址又该如何实现 呢?
其实ifconfig命令本身是一个程序,这样我们可以在程序里创建一个子进程来执行这个程序即可。另外一个问题是该命令执 行的结果会打印到标注输出(默认是屏幕)上,那我们C程不可能像人眼一样在屏幕上获取IP地址。对于这个问题我们可以在子 进程里将标准输出重定向到文件里,这样命令的打印信息会输出到该文件中。之后父进程就可以从该文件中读出相应的内容并作 相应的字符串解析,就可以获取到IP地址了。今天我给大家带来的代码是利用文件I/O系统函数和fork,execl等相关函数来实现这个功能,希望能用这段代码,让大家
深层次的理解APUE编程中父进程和子进程的概念和关系!
二,代码实现(含注释)
以博主的虚拟机为例,IP信息在ens33中,若IP信息在eth0中则需要进行相应的改变哦!
如下图:
代码部分如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
// 标准输出重定向的文件, /tmp路径是在Linux系统在内存里做的一个文件系统,放在这里不用写硬盘程序运行会快些。
#define TMP_FILE "/tmp/.ifconfig.log"
int main(int argc, char **argv)
{
pid_t pid;
int fd;
char buf[1024];
int rv;
char * ptr;
FILE * fp;
char * ip_start;
char * ip_end;
char ipaddr[16];
// 父进程打开这个文件,子进程将会继承父进程打开的这个文件描述符,这样父子进程都可以通过各自的文件描述符访问同一个文件了
if( (fd=open(TMP_FILE, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0 )
{
printf("Redirect standard output to file failure: %s\n", strerror(errno));
return -1;
}
// 父进程开始创建进程
pid = fork();
if(pid < 0)
{
printf("fork() create child process failure: %s\n", strerror(errno));
return -1;
}
else if( pid == 0 ) //子进程开始运行
{
printf("Child process start excute ifconfig program\n");
// 子进程会继承父进程打开的文件描述符,此时子进程重定向标准输出到父进程所打开的文件里
dup2(fd, STDOUT_FILENO);
/*
下面这句execl(...)函数是让子进程开始执行带参数的ifconfig命令: ifconfig ens33 execl()会导致子进程彻底丢掉父进程的文本段、数据段,
并加载/sbin/ifconfig这个程序的文本段、数据段重新建立进程内存空间。execl()函数的第一个参数是所要执行程序的路径,ifconfig命令(程序)的路径是/sbin/ifconfig;
接下来的参数是命令及其相关选项、参数,每个命令、选项、参数都用双引号("")扩起来,并以NULL结束。
*/
/*
ifconfig ens33命令在执行时会将命令的执行的结果输出到标准输出上,而这时子进程已经重定向标准输出到文件中去了,所以ifconfig
命令的打印结果会输出到文件中去,这样父进程就会从该文件里读到子进程执行该命令的结果;
*/
execl("/sbin/ifconfig", "ifconfig", "ens33", NULL);
/* execl()函数并不会返回,因为他去执行另外一个程序了。如果execl()返回了,说明该系统调用出错了。 */
printf("Child process excute another program, will not return here. Return here means execl() error\n");
return -1;
}
else
{
// 父进程等待3s,让子进程先执行
sleep(3);
}
// 子进程因为调用了execl(), 它会丢掉父进程的文本段,所以子进程不会执行到这里了。只有父进程会继续执行这后面的代码
memset(buf, 0, sizeof(buf));
// 父进程这时候读是读不到内容的,这时因为子进程往文件里写内容时已经将文件偏移量修改到文件尾了
rv=read(fd, buf, sizeof(buf));
printf("Read %d bytes data dierectly read after child process write\n", rv);
// 父进程如果要读则需要将文件偏移量设置到文件头才能读到内容
memset(buf, 0, sizeof(buf));
lseek(fd, 0, SEEK_SET);
rv=read(fd, buf, sizeof(buf));
printf("Read %d bytes data after lseek:\n %s", rv, buf);
// 如果使用read()读的话,一下子就读 N 多个字节进buffer,但有时我们希望一行一行地读取文件的内容,这时可以使用fdopen()函数将文件描述符fd转成文件流fp
fp = fdopen(fd, "r");
fseek(fp, 0, SEEK_SET); // 重新设置文件偏移量到文件头
while( fgets(buf, sizeof(buf), fp) ) // fgets()从文件里一下子读一行,如果读到文件尾则返回NULL
{
/*
包含IP地址的那一行包含有netmask关键字,如果在该行中找到该关键字就可以从这里面解析出IP地址了。
inet 192.168.163.128 netmask 255.255.255.0 broadcast 192.168.163.255
inet6 fe80::6f70:8a72:de23:95e4 prefixlen 64 scopeid 0x20<link>
*/
if( strstr(buf, "netmask") )
{
// 查找"inet"关键字,inet关键字后面跟的就是IP地址;
ptr=strstr(buf, "inet");
if( !ptr )
{
break;
}
ptr += strlen("inet");
// inet 关键字后面是空白符,我们不确定是空格还是TAB,所以这里使用isblank()函数判断如果字符还是空白符就往后跳过;
while( isblank(*ptr) )
ptr++;
// 跳过空白符后跟着的就是IP地址的起始字符;
ip_start = ptr;
// IP地址后面又是跟着空白字符,跳过所有的非空白字符,即IP地址部分:xxx.xxx.xxx.xxx
while( !isblank(*ptr) )
ptr++;
// 第一个空白字符的地址也就是IP地址终止的字符位置
ip_end = ptr;
// 使用memcpy()函数将IP地址拷贝到存放IP地址的buffer中,其中ip_end-ip_start就是IP地址的长度,ip_start就是IP地址的起始位置;
memset(ipaddr, 0, sizeof(ipaddr));
memcpy(ipaddr, ip_start, ip_end-ip_start);
break;
}
}
printf("Parser and get IP address: %s\n", ipaddr);
fclose(fp);
unlink(TMP_FILE);
return 0;
}
三,运行结果