通过获取IP地址熟悉popen、str*系列、snprintf()、fgets

本文介绍如何利用popen()函数结合其他字符串处理函数获取网络接口的IP地址及子网掩码,涵盖popen(), snprintf(), fgets(), strstr(), strchr() 和 strrchr()等函数的使用方法。

通过获取IP和网关的代码来学习popen().str*系列等函数的使用


/*********************************************************************************
 *      Copyright:  (C) 2017 LingYun Lab.
 *                  All rights reserved.
 *
 *       Filename:  ifcfg.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(05/16/2017)
 *         Author:  lingyun <lingyun@email.com>
 *      ChangeLog:  1, Release initial version on "05/16/2017 06:59:54 PM"
 *                 
 ********************************************************************************/

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


#define DEBUG

#define IPADDR_LEN          16//定义这个宏的长度为16
#define NIC                 "eth0"//定义NIC为“eth0”

int get_nic_ipaddr(char *nic, char *ipaddr, char *netmask);//声明函数

int main (int argc, char **argv)
{
    char         ipaddr[IPADDR_LEN];
    char         netmask[IPADDR_LEN];


    if( get_nic_ipaddr(NIC, ipaddr, netmask) < 0 )//如果函数的返回值小于0,就表示获取失败
    {
        printf("get IP address for network card %s failure\n", NIC);
        return -1;
    }

    printf("Network card [%s] IPaddr: <%s> Netmask: <%s>\n", NIC, ipaddr, netmask);//打印ip和netmask信息
    return 0;
} /* ----- End of main() ----- */


/* Description:  This function used to get NIC $nic IP address and netmask
 * Input args:  nic: get which Network interface card ipaddress or netmask
 * Output args: ipaddr: the NIC IP address  netmask: the NIC netmask
 * Return Value:  0: Success   <0: Failure
 */
int get_nic_ipaddr(char *nic, char *ipaddr, char *netmask)
{
    FILE               *fp;
    char               command[32];
    char               buf[256];
    char               *p1;
    char               *p2;
    int                rv = -1;

#ifdef DEBUG
    printf("time: %s %s %s %d %s()\n", __DATE__, __TIME__, __FILE__, __LINE__, __FUNCTION__ );
#endif


    if( !nic || !ipaddr )//判断这两个参数是否定义
    {
        printf("invalid input arguments\n");//输入的参数无效
        return -1;
    }

    snprintf(command, sizeof(command), "ifconfig %s", nic);//把"ifconfig eth0"命令存放到command数组里
    printf("command: %s\n", command);//打印command中的字符串


    fp=popen(command, "r");//打开一个进程执行命令command,r表示可以读取该进程下的标准输出内容,fp指向该进程
    if( NULL == fp)
    {
        printf("popen() for command %s failure: %s\n", command, strerror(errno));//popen失败
        return -2;
    }

    while( fgets(buf, sizeof(buf), fp) )//把fp指向的进程中的内容存储到buf中
    {
        if( NULL!= (p1=strstr(buf, "inet addr:")) )//用strstr函数在buf中查找inet addr的位置,记录在p1中
        {
            p2 = strchr(p1, ':');//如果找到inet addr ,就继续找":",把位置记录在p2中
            if(p2 == NULL)//如果p2为空,则没有ip信息
                break;

            p2++;//找到":"之后,p2指向":"后的字符串的地址

            p1 = strchr(p2, ' ');//在p2中查找"空格",把地址记录在p1中
            if(p1 == NULL)
                break;

            strncpy(ipaddr, p2, p1-p2);//把从p2地址开始到p1-p2大小的内容拷贝到ipaddr中,就是把找到的ip信息保存到ipaddr中
            rv = 0;

            if( netmask )//继续查找子网掩码信息
            {
 testip.c                                                                                                                           
                rv = -1;
                p2 = strrchr(p1, ':');//我们知道netmask的信息在ip之后,所以从p1记录的地址继续找":",把地址记录在p2中
                if(p2 == NULL)
                    break;

                p2++;//p2指向":"之后的字符

                p1 = strchr(p2, '\n');//在p2中找换行符"\n",把地址记录在p1中
                if(p1 == NULL)
                    break;

                strncpy(netmask, p2, p1-p2);//把从p2开始,p1-p2大小的内容拷贝到netmask中,就是保存子网掩码
                rv = 0;
            }
        }
    }

    pclose(fp);//关闭打开的进程

    return rv;
}




在写代码时,功能复杂的代码,在主函数main()内只负责调用相关函数,比较简洁,对应功能的函数要在开头进行声明,在编写时做一些说明,有助于整理程序。


一、popen()函数


popen()计算机科学中的进程I/O函数,与pclose函数一起使用,必须由 pclose 来终止


头文件

#include <stdio.h>

函数说明

FILE * popen ( const char * command , const char * type );
int pclose ( FILE * stream );

type 参数只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 "r" 则文件指针连接到 command 的标准输出;如果 type 是 "w" 则文件指针连接到 command 的标准输入。

command 参数是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用-c 标志,shell 将执行这个命令。


二、snprintf()函数

int snprintf(char *restrict buf, size_t n, const char * restrict format, ...);

函数说明: 最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。

函数返回值: 若成功则返回欲写入的字符串长度,若出错则返回负值。

将可变个参数(...)按照format格式化成字符串,然后将其复制到str中。


三、fgets()

函数原型:

char *fgets(char *buf, int bufsize, FILE *stream);

参数:
*buf: 字符型指针,指向用来存储所得数据的地址。
bufsize: 整型数据,指明存储数据的大小。
*stream: 文件结构体指针,将要读取的文件流。


从文件结构体指针stream中读取数据,每次读取一行。读取的数据保存在buf指向的字符数组中,每次最多读取bufsize-1个字符(第bufsize个字符赋'\0'),如果文件中的该行,不足bufsize个字符,则读完该行就结束。如若该行(包括最后一个换行符)的字符数超过bufsize-1,则fgets只返回一个不完整的行,但是,缓冲区总是以NULL字符结尾,对fgets的下一次调用会继续读该行。函数成功将返回buf,失败或读到文件结尾返回NULL。



四、str*系列

头文件

#include<string.h>

strstr()

strstr(string,search [, bool $before_needle = false  ])

strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。


string 规定被搜索的字符串。

search 规定所搜索的字符串


strchr()

strchr函数原型:extern char *strchr(const char *s,char c);

查找字符串s中首次出现字符c的位置。


strrchr()

strrchr(string,char)

查找字符在指定字符串中从左面开始的最后一次出现的位置,如果成功,返回该字符以及其后面的字符,如果失败,则返回 NULL。

string 规定被搜索的字符串。
char 规定要查找的字符。如果该参数是数字,则搜索匹配数字 ASCII 值的字符。如果该参数多于一个字符,则以第一个字符为准。


关于rv要做几点说明,在写程序时,很多时候为了能够及时排除bug,要明确知道程序的每一步执行的情况就要用到rv,返回值来判断,尤其是涉及到子函数,因为很多时候主函数只是调用子函数,它不能具体掌握子函数的执行情况,这个时候返回值就会派上用场。

还有就是csdn博客格式有些问题,代码排版不太好。有时间我再来改吧。


//检查实际的连接状态 bool is_wifi_actually_connected(const char *interface_name) { FILE *fp; char command[128]; char result[16]; // 使用iwgetid检查实际连接状态 snprintf(command, sizeof(command), "iwgetid %s -r 2>/dev/null", interface_name); fp = popen(command, "r"); if (fp == NULL) { return false; } if (fgets(result, sizeof(result), fp) != NULL) { pclose(fp); // 如果有返回结果,表示已连接到某个SSID return strlen(result) > 0; } pclose(fp); return false; } static int wifi_retry_count = 0; #define MAX_WIFI_RETRY 3 //最大重试次数 lv_timer_t *Wifi_conn_timer; void Wifi_conn_timer_callback(lv_timer_t *timer) { if (load_network_info(&netInfo) == 0) { // 检查是否真正连接到WiFi if (is_wifi_actually_connected("wlan0")) { EventBuf e; e.eventType = EVENT_CONNECTWIFI; strncpy((char *)e.databuf, netInfo.wifi_name, 31); e.databuf[31] = '\0'; strncpy((char *)e.databuf + 32, netInfo.wifi_pass, 31); e.databuf[63] = '\0'; sendImageMsg(e); SW_Network = true; wifi_retry_count = 0; // 重置重试计数 printf("Connected to WiFi: %s\n", netInfo.wifi_name); lv_timer_del(Wifi_conn_timer); Wifi_conn_timer = NULL; return; } else { // 未真正连接,增加重试计数 wifi_retry_count++; printf("WiFi not actually connected, retry %d/%d\n", wifi_retry_count, MAX_WIFI_RETRY); if (wifi_retry_count >= MAX_WIFI_RETRY) { // 超过最大重试次数,显示WiFi列表 printf("Max retries reached, showing WiFi list\n"); SW_Network = false; wifi_retry_count = 0; // 触发显示WiFi列表的事件 EventBuf e; e.eventType = EVENT_SCANWIFI; sendImageMsg(e); lv_timer_del(Wifi_conn_timer); Wifi_conn_timer = NULL; return; } // 继续重试,不删除定时器 return; } } else { SW_Network = false; printf("No saved WiFi network info\n"); lv_timer_del(Wifi_conn_timer); Wifi_conn_timer = NULL; } } // 函数:检查指定网络接口是否为无线接口 bool is_truly_wireless(const char *interface_name) { char wireless_dir[256]; snprintf(wireless_dir, sizeof(wireless_dir), "/sys/class/net/%s/wireless", interface_name); struct stat st; if (stat(wireless_dir, &st) == 0 && S_ISDIR(st.st_mode)) { // "/sys/class/net/<interface_name>/wireless" 目录存在,表明它是一个无线接口 return true; } return false; } // 函数:检查指定网络接口是否处于UP状态 // 返回 true 如果接口是UP,否则 false bool is_interface_up(const char *interface_name) { int sock_fd; struct ifreq ifr; // 创建一个UDP socket,用于ioctl操作 sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror("socket error"); return false; } // 设置接口名称 strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; // 确保字符串以null结尾 // 获取接口标志 if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) { // perror("ioctl SIOCGIFFLAGS error"); // 接口不存在或其他错误 close(sock_fd); return false; } close(sock_fd); // 检查IFF_UP标志是否设置 return (ifr.ifr_flags & IFF_UP) != 0; } // 函数:设置指定网络接口的状态 (UP/DOWN) // 参数: interface_name - 接口名称, should_be_up - true为UP,false为DOWN // 返回 true 表示成功,否则 false bool set_interface_state(const char *interface_name, bool should_be_up) { int sock_fd; struct ifreq ifr; sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror("socket error in set_interface_state"); return false; } strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; // 首先获取当前标志 if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) { perror("ioctl SIOCGIFFLAGS error in set_interface_state"); close(sock_fd); return false; } if (should_be_up) { ifr.ifr_flags |= IFF_UP; // 设置IFF_UP标志 } else { ifr.ifr_flags &= ~IFF_UP; // 清除IFF_UP标志 } // 设置新的标志 if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) { // 常见的错误是 EPERM (Operation not permitted),表示没有权限 perror("ioctl SIOCSIFFLAGS error in set_interface_state"); close(sock_fd); return false; } close(sock_fd); return true; } // 函数:检查指定无线接口是否被RF-killed (射频阻断) // 注意:rfkill通常是针对整个无线设备,而不是单个接口 // 返回 true 如果未被RF-killed,否则 false bool is_wireless_rf_unblocked() { DIR *d; struct dirent *dir_entry; char path[256]; char buf[16]; FILE *fp; d = opendir("/sys/class/rfkill/"); if (d == NULL) { // 如果rfkill子系统不存在或无法访问,假定没有rfkill阻断 // 在某些嵌入式系统中可能没有rfkill // perror("Error opening /sys/class/rfkill/"); return true; } while ((dir_entry = readdir(d)) != NULL) { if (strncmp(dir_entry->d_name, "rfkill", 6) == 0) // 查找类似 rfkill0, rfkill1 的目录 { // 检查rfkill设备的类型 snprintf(path, sizeof(path), "/sys/class/rfkill/%s/type", dir_entry->d_name); fp = fopen(path, "r"); if (fp) { if (fgets(buf, sizeof(buf), fp) != NULL) { // 去除换行符 buf[strcspn(buf, "\n")] = 0; if (strcmp(buf, "wlan") == 0) // 如果是无线LAN设备 { fclose(fp); // 关闭type文件 // 检查其状态 snprintf(path, sizeof(path), "/sys/class/rfkill/%s/state", dir_entry->d_name); fp = fopen(path, "r"); if (fp) { if (fgets(buf, sizeof(buf), fp) != NULL) { if (strcmp(buf, "1\n") == 0) // '1' 表示被阻断 (blocked) { fclose(fp); closedir(d); return false; // 发现无线被RF-killed } } fclose(fp); } } else { fclose(fp); // 如果不是wlan类型,也要关闭文件 } } else { fclose(fp); // fgets failed } } } } closedir(d); return true; // 没有发现无线被RF-killed } // 函数:检查系统中是否存在任何活动的无线网络接口 bool check_for_active_wlan() { DIR *d; struct dirent *dir_entry; bool wlan_found = false; d = opendir("/sys/class/net/"); if (d == NULL) { perror("Error opening /sys/class/net/"); return false; } while ((dir_entry = readdir(d)) != NULL) { if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) { continue; } if (strncmp(dir_entry->d_name, "wlan", 4) == 0 || strncmp(dir_entry->d_name, "wlp", 3) == 0 || strncmp(dir_entry->d_name, "wifi", 4) == 0) { if (is_truly_wireless(dir_entry->d_name)) { // printf("Found active wireless interface: %s\n", dir_entry->d_name); wlan_found = true; break; } } } closedir(d); return wlan_found; } // 函数:检查系统中是否存在任何活动的、未被阻断的无线网络接口 // 如果找到一个可用的无线接口,则返回 true,否则 false bool check_for_act_wlan_interf() { DIR *d; struct dirent *dir_entry; bool wlan_fully_ready = false; // 修改变量名以反映更全面的检查 char found_interface_name[IFNAMSIZ]; // 存储找到的接口名 // 首先检查是否存在全局的RF-kill阻断 //if (!is_wireless_rf_unblocked()) //{ // printf("Wireless is RF-killed (soft/hard blocked).\n"); // return false; // 如果被rf-killed,直接返回false //} d = opendir("/sys/class/net/"); if (d == NULL) { perror("Error opening /sys/class/net/"); return false; } while ((dir_entry = readdir(d)) != NULL) { if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) { continue; } // 过滤可能的无线接口名称 if (strncmp(dir_entry->d_name, "wlan", 4) == 0 || strncmp(dir_entry->d_name, "wlp", 3) == 0 || strncmp(dir_entry->d_name, "wifi", 4) == 0) { // 1. 检查是否真的是无线接口 if (is_truly_wireless(dir_entry->d_name)) { // 2. 检查无线接口是否处于UP状态 if (is_interface_up(dir_entry->d_name)) { // 如果代码执行到这里,说明: // - 接口是无线接口 // - 接口处于UP状态 // - 且全局没有RF-kill阻断(已在函数开头检查) // 可以选择在这里存储接口名称,如果只需要一个可用的接口 strncpy(found_interface_name, dir_entry->d_name, IFNAMSIZ); found_interface_name[IFNAMSIZ - 1] = '\0'; printf("Found a fully ready wireless interface: %s\n", found_interface_name); wlan_fully_ready = true; break; // 找到一个就足够了,如果需要列出所有,则移除break } else { printf("Found wireless interface %s, but it is DOWN.\n", dir_entry->d_name); } } } } closedir(d); return wlan_fully_ready; } void FindWlanProc(EventBuf evt) { //是否有无线接口 bool is_wlan_up = check_for_active_wlan(); //是否真正的连接 bool is_actually_connected = is_wifi_actually_connected("wlan0"); if (is_wlan_up && is_actually_connected) { // WLAN 接口实际存在且真正连接 if (!FindWlanFlag) { FindWlanFlag = true; printf("///////////////////////////Wlan connected and verified!!!\n"); // 不需要创建连接定时器,因为已经连接 } } else if (is_wlan_up && !is_actually_connected) { // WLAN 接口存在但未连接 printf("/////////////////////////////////////Wlan interface up but not connected to any network\n"); if (!Wifi_conn_timer) { Wifi_conn_timer = lv_timer_create(Wifi_conn_timer_callback, 10000, NULL); if (Wifi_conn_timer == NULL) { printf("////////////////////////////Error: Failed to create Wifi_conn_timer!\n"); } } } else { // WLAN 接口不存在或未被识别 if (FindWlanFlag) { // 如果之前的状态是“已加载” FindWlanFlag = false; // 更新状态:wifi没有加载 printf("Wlan out (verified by direct system check)!!!\n"); } // 1. 获取输入框内容 const char *ip = "127.0.0.1"; const char *port_str = "9003"; // 3. 复制IP(字符串形式) strncpy(socketconnInfo.ip, ip, sizeof(socketconnInfo.ip) - 1); socketconnInfo.ip[sizeof(socketconnInfo.ip) - 1] = '\0'; // 2. 输入验证 if (!ip || !port_str) { printf("ERROR: IP或端口不能为空\n"); return; } // IP格式验证 struct in_addr addr; if (inet_pton(AF_INET, ip, &addr) != 1) { printf("ERROR: 无效的IP地址格式\n"); lv_label_set_text(guider_ui.SettingScreen_label_server_conn_status, "Wrong IP!"); return; } // 端口号转换和验证 char *endptr; long port = strtol(port_str, &endptr, 10); if (*endptr != '\0' || port < 0 || port > 65535) { printf("ERROR: 端口号必须为0-65535的整数\n"); lv_label_set_text(guider_ui.SettingScreen_label_server_conn_status, "Wrong PORT!"); return; } socketconnInfo.port = (uint16_t)port; // 或htons((uint16_t)port) // 3. 构造数据缓冲区 uint8_t buffer[21] = {0}; // 16+4+1=21字节 // 格式化IP部分(16字节) size_t ip_len = strlen(ip); if (ip_len > 15) { printf("WARNING: IP地址超过15字符将被截断\n"); ip_len = 15; } memcpy(buffer, ip, ip_len); // 填充空格 if (ip_len < 15) { memset(buffer + ip_len, ' ', 15 - ip_len); } // 格式化端口部分(4字节网络序) uint32_t net_port = htonl(port); memcpy(buffer + 16, &net_port, 4); // 添加状态码(1字节) buffer[20] = 0x01; // 连接成功状态 // 4. 发送网络消息 int ret = sendNetMsg(EVENT_SOCKET_CONNECT, (const char *)buffer); // 5. 处理发送结果 if (ret == 0) { printf("INFO: 连接信息发送成功\n"); printf("IP: %-15s Port: %-5d\n", ip, (int)port); } else { printf("ERROR: 发送失败,错误码: %d\n", ret); } } } 这个is_wifi_actually_connected函数好像没什么用处,因为在判断的时候,即使我的路由器断电,此时按理说应该是无法连接网络的,然后就显示全部的列表,但是由于这个函数没用,所以每次路由器断电后,没有网络了进入的分支也是if (is_wlan_up && is_actually_connected),这就导致了不管上一次连接的网络是否是存在的,每次打开机器都需要重新去输入密码连接WiFi,但是我想要的是在路由器断电没有网以后会重连但是一直连不上,就显示全部的列表,如果是有网络可以连接那就直接连接网络,
09-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值