WiFi探针的开发过程在我之前的那个项目大致分了四段吧,就是用到了四种方法。

第一个是用 PCAP 库进行抓包,获取到数据,再进行处理,可惜我用的时候运行起来了,但是没得到数据,可能是芯片不支持吧。

第二个是在固件中做功夫,修改 802.11 的无线驱动,再通过 netlink 从内核中把数据传递到应用层进行处理,这个方法比前一个简单,但是很可惜,我没有得到数据,甚至再驱动里打印 log 也不生效,也是芯片问题吧。

第三个是根据 rt2860v2 驱动改的WiFi探针功能,我试了下,最后也没成功,感觉驱动没使用上,不知道是为什么。

第四个就是用钱喽,直接买了别人的WiFi探针板,还带蓝牙 mac 地址采集,小巧玲珑,就是我得从串口读取它得 json 数据,为此还专门写了一段代码来解析,这里算是成功拿到数据了。。。

下面讲讲我的过程吧!

使用PCAP库抓包

这里使用的是一个老哥的代码,博客写的很详细,可以参考下,我就不用在说了

https://blog.csdn.net/sunhaobo1996/category_7690613.html

ps. 我还在知网上下载了一些资料,有一篇论文写的很详细,不知道是不是这个老哥的。

修改无线固件

这个方法估计是我当时的救命草吧,因为我想无论你怎么使用 WiFi 功能,你的帧肯定得经过无线驱动吧,我是觉得这跟芯片关系不大,可是最后就是没成,我也很郁闷,下面详细讲。

修改驱动接受源码

修改无线驱动mac80211文件夹中的rx.c文件,获取 probe帧的mac。

vim build_dir/target-mips_34kc_uClibc-0.9.33.2/linux-ar71xx_generic/compat-wireless-2016-01-10/net/mac80211/rx.c

找到下面函数进行修改

static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, struct sk_buff *skb)

具体修改地方看下面代码,改了三个地方

@@ -31,6 +31,7 @@#include "tkip.h"#include "wme.h"#include "rate.h"
+#include "probe_nl.h"static inline void ieee80211_rx_stats(struct net_device *dev, u32 len){@@ -3429,6 +3430,36 @@ieee80211_invoke_rx_handlers(rx);return true;}
+void collect_probe_info(u8 *addr,s8 signal)
+{
+  if(g_probed_unsend_num >= MAX_PROBED_NUM)
+  {
+    g_probed_unsend_num = 0;
+    return;
+  }
+  else
+  {
+    int i = 0;
+    bool flag =1;
+    for(i=0; i<g_probed_unsend_num; i++)
+      {
+      if(memcmp(g_probed_info[i],addr,6)==0)
+          {
+        flag = 0;
+              //update signal
+              //memcpy(g_probed_info[i], addr, 6);
+              memcpy(g_probed_info[i]+6, &signal, 1);
+        break;
+      }
+    }
+    if(flag)
+      {
+      memcpy(g_probed_info[g_probed_unsend_num], addr, 6);
+      memcpy(g_probed_info[g_probed_unsend_num]+6, &signal, 1);
+      g_probed_unsend_num++;
+    }
+  }
+}/** This is the actual Rx frames handler. as it belongs to Rx path it must@@ -3512,6 +3543,11 @@prev = NULL;+ if (ieee80211_is_probe_req(fc)/* && g_is_probe*/){
+      struct ieee80211_rx_status *sta = IEEE80211_SKB_RXCB(skb);
+      collect_probe_info(hdr->addr2,sta->signal);
+  }
+list_for_each_entry_rcu(sdata, &local->interfaces, list) {if (!ieee80211_sdata_running(sdata))
编写netlink源码

接下来编写从内核传递到应用层的源码,在 mac80211文件夹下编写probe_nl.c、probe_nl.h两个文件

  • probe_nl.h
#ifndef _PROBE_NL_H_
#define _PROBE_NL_H_#define MAX_PROBED_NUM      200 //max saved sta device's mac number
#define PROBED_INFO             7   //mac addr(6Byte)+ signal(1Byte)extern unsigned char g_is_probe;
extern unsigned char g_probed_unsend_num;
extern unsigned char g_probed_info[MAX_PROBED_NUM][PROBED_INFO];extern int probe_genetlink_init(void);
extern void probe_genetlink_exit(void);
#endif
  • probe_nl.c
#include <net/netlink.h>
#include <net/genetlink.h>
#include "probe_nl.h"unsigned char g_is_probe;
unsigned char g_probed_unsend_num;
unsigned char g_probed_info[MAX_PROBED_NUM][PROBED_INFO];//1. Registering a family
//1.1 Define the family, just create an instance of genl_family struct.
/* attributes */
enum {DOC_EXMPL_A_UNSPEC,DOC_EXMPL_A_MSG,__DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {[DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};
/* genl_family definition */
static struct genl_family doc_exmpl_genl_family = {.id = GENL_ID_GENERATE,.hdrsize = 0,.name = "ProbeMacList",.version = 1,.maxattr = DOC_EXMPL_A_MAX,
};
//1.2 Define the operations for the family, which we do by creating at least one instance of the genl_ops structure.
/* commands: enumeration of all commands (functions) */
enum {DOC_EXMPL_C_UNSPEC,DOC_EXMPL_C_ECHO,__DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)/* handler */
int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info)
{/* message handling code goes here, return 0 on success, negative value on failure */if (info == NULL)goto out;struct nlmsghdr *nlhdr;struct genlmsghdr *genlhdr;struct nlattr *nlh;char *str;struct sk_buff *skb_send;void *msg_head;int rc;unsigned char snd_buff[1024];nlhdr = nlmsg_hdr(skb);genlhdr = nlmsg_data(nlhdr);nlh = genlmsg_data(genlhdr);str = nla_data(nlh);if(*str == 's'){g_is_probe = 1;//printk("doc_exmpl_echo get: %s, start probe\n", str);}else{g_is_probe = 0;//printk("doc_exmpl_echo get: %s, stop probe\n", str);}skb_send = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);if (skb_send == NULL)goto out;msg_head = genlmsg_put(skb_send, 0, info->snd_seq + 1, &doc_exmpl_genl_family, 0, DOC_EXMPL_C_ECHO);if (msg_head == NULL) {goto out;}printk("g_probed_unsend_num = %u\n", g_probed_unsend_num);memcpy(snd_buff, &g_probed_unsend_num, sizeof(g_probed_unsend_num));if(g_probed_info > 0){memcpy(snd_buff + sizeof(g_probed_unsend_num), g_probed_info, g_probed_unsend_num*PROBED_INFO);}rc = nla_put(skb_send, DOC_EXMPL_A_MSG, sizeof(g_probed_unsend_num) + g_probed_unsend_num*PROBED_INFO, snd_buff);if (rc != 0)goto out;genlmsg_end(skb, msg_head);rc = genlmsg_unicast(genl_info_net(info), skb_send, info->snd_portid);g_probed_unsend_num = 0;return 0;out:printk("An error occured in doc_exmpl_echo function.\n");return -1;
}/* operation definition */
#if 1 //this need array
static struct genl_ops doc_exmpl_genl_ops_echo[] =
{{.cmd = DOC_EXMPL_C_ECHO,.flags = 0,.policy = doc_exmpl_genl_policy,.doit = doc_exmpl_echo,.dumpit  = NULL,}
};
#else
static struct genl_ops doc_exmpl_genl_ops_echo = {.cmd = DOC_EXMPL_C_ECHO,.flags = 0,.policy = doc_exmpl_genl_policy,.doit = doc_exmpl_echo,.dumpit = NULL,
};
#endif//1.3 Register ProbeMacList family with the Generic Netlink operation, and Register the operations for the family.
int probe_genetlink_init(void)
{int rc;g_is_probe = 0; g_probed_unsend_num = 0;rc = genl_register_family_with_ops(&doc_exmpl_genl_family, doc_exmpl_genl_ops_echo);if (rc != 0)goto error;printk("Init probe_genetlink_init modules OK!\n");return 0;
error:printk("Init probe_genetlink_init modules error!\n");return -1;
}
void probe_genetlink_exit(void)
{printk("Init probe_genetlink_exit modules OK!\n");genl_unregister_family(&doc_exmpl_genl_family);
}
初始化netlink

修改mac80211文件夹中的 main.c,初始化时加入对 probe_nl 的初始化,具体修改看下面代码。

@@ -32,6 +32,7 @@#include "wep.h"#include "led.h"#include "debugfs.h"
+#include "probe_nl.h"void ieee80211_configure_filter(struct ieee80211_local *local){
@@ -1235,12 +1236,18 @@ret = ieee80211_iface_init();if (ret)goto err_netdev;
+
+  ret = probe_genetlink_init();
+  if (ret)
+      goto err_netlink;return 0;err_netdev:rc80211_minstrel_ht_exit();err_minstrel:rc80211_minstrel_exit();
+ err_netlink:
+  probe_genetlink_exit(); return ret;}
@@ -1253,6 +1260,7 @@ieee80211s_stop();ieee80211_iface_exit();
+  probe_genetlink_exit();rcu_barrier();}
增加对netlink源码的编译

修改 mac80211 文件夹中的 Makefile,编译 probe_nl 源码

@@ -21,6 +21,7 @@ethtool.o \rx.o \spectmgmt.o \
+  probe_nl.o \tx.o \key.o \util.o \
应用层代码

应用层代码比较简单,主要就是和内核创建连接,拿到内核传递出的数据,进行处理并显示。

  • wifi-probe.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <json-c/json.h>
#include <string.h>
#include <sys/wait.h>
#include <net/if.h>
#include <stdarg.h>
#include "genl-lib.h"struct {time_t cur_time;unsigned char addr[6];char signal;
} kernel_sended_mac_list[MAX_PROBED_NUM];int format_mac_info(void)
{int i;time_t now;time(&now);for(i=0;i<kernel_sended_pre_time;i++){memcpy(kernel_sended_mac_list[i].addr,kernel_sended_info[i],6);memcpy(&(kernel_sended_mac_list[i].signal),kernel_sended_info[i]+6,sizeof(char));kernel_sended_mac_list[i].cur_time = now;}return 0;
}void send_json_by_mosquitto(void)
{printf("kernel_sended_pre_time = %d\n\n",kernel_sended_pre_time);if(kernel_sended_pre_time == 0)return;int i;json_object *pData = json_object_new_array();json_object *pRespObj = json_object_new_object();for(i=0;i<kernel_sended_pre_time;i++){json_object *jvalue = json_object_new_object();struct tm tm_fr;char fr_t[64]={0};char pmac[32]={0};char signal[16]={0};localtime_r(&kernel_sended_mac_list[i].cur_time, &tm_fr);strftime(fr_t,sizeof(fr_t),"%Y-%m-%d %H:%M:%S",&tm_fr);sprintf(pmac,"%02X:%02X:%02X:%02X:%02X:%02X",kernel_sended_mac_list[i].addr[0],kernel_sended_mac_list[i].addr[1],kernel_sended_mac_list[i].addr[2],kernel_sended_mac_list[i].addr[3],kernel_sended_mac_list[i].addr[4],kernel_sended_mac_list[i].addr[5]);sprintf(signal,"%d",kernel_sended_mac_list[i].signal);json_object_object_add(jvalue,"signal",json_object_new_string(signal));json_object_object_add(jvalue,"pt",json_object_new_string(fr_t));json_object_object_add(jvalue,"mac",json_object_new_string(pmac));json_object_array_add(pData,jvalue);}json_object_object_add(pRespObj,"probes",pData);const char *pJsonStr = json_object_get_string(pRespObj);printf("------------\n%s\n------------\n\n",pJsonStr);char cmdstring[200*64] = {0};sprintf(cmdstring, "echo \'%s\' > /tmp/tb-report", pJsonStr);system(cmdstring);system("/bin/sh /usr/sbin/wifi-report /tmp/tb-report && rm -rf /tmp/tb-report");return;
}
void init_golbal_params(void)
{kernel_sended_pre_time = 0;memset(kernel_sended_mac_list,0,sizeof(kernel_sended_mac_list));memset(kernel_sended_info,0,sizeof(kernel_sended_info));
}
int main()
{int sock_fd;sock_fd = genl_socket_init();if(sock_fd < 0){printf("create_nl_socket create failure\n");return 0;}int family_id = genl_get_family_id(sock_fd, "ProbeMacList");for(;;){init_golbal_params();genl_send_msg(sock_fd, family_id, DOC_EXMPL_C_ECHO, DOC_EXMPL_A_MSG, "s");genl_rcv_msg(family_id,sock_fd);format_mac_info();send_json_by_mosquitto();sleep(5);}
}
  • genl-lib.h
#ifndef _GENL_LIB_
#define _GENL_LIB_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <linux/genetlink.h>enum {DOC_EXMPL_A_UNSPEC,DOC_EXMPL_A_MSG,__DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
enum {DOC_EXMPL_C_UNSPEC,DOC_EXMPL_C_ECHO,__DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)#define MAX_MSG_SIZE 1024*2#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))typedef struct msgtemplate {struct nlmsghdr nlh;struct genlmsghdr gnlh;char data[MAX_MSG_SIZE];
} msgtemplate_t;int genl_socket_init(void);
int genl_get_family_id(int sock_fd, char *family_name);
int genl_send_msg(int sock_fd, u_int16_t family_id, u_int8_t genl_cmd, u_int16_t nla_type,void *nla_data);
void genl_rcv_msg(int family_id, int sock_fd);//From Kernel define
#define MAX_PROBED_NUM      200 //max saved sta device's mac number
#define PROBED_INFO             7   //mac addr(6Byte)+ signal(1Byte)extern unsigned char kernel_sended_pre_time;
extern unsigned char kernel_sended_info[MAX_PROBED_NUM][PROBED_INFO];#endif
  • genl-lib.c
#include "genl-lib.h"unsigned char kernel_sended_pre_time; // 0-200
unsigned char kernel_sended_info[MAX_PROBED_NUM][PROBED_INFO];int genl_get_family_id(int sock_fd, char *family_name)
{msgtemplate_t ans;int id, rc;struct nlattr *na;int rep_len;rc = genl_send_msg(sock_fd, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, CTRL_ATTR_FAMILY_NAME, (void *)family_name);rep_len = recv(sock_fd, &ans, sizeof(ans), 0);if (rep_len < 0) {return 1;}if (ans.nlh.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&ans.nlh), rep_len)){return 1;}na = (struct nlattr *) GENLMSG_DATA(&ans);na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len));if (na->nla_type == CTRL_ATTR_FAMILY_ID) {id = *(__u16 *) NLA_DATA(na);} else {id = 0;}return id;
}int genl_send_msg(int sock_fd, u_int16_t family_id, u_int8_t genl_cmd, u_int16_t nla_type,void *nla_data)
{struct nlattr *na;struct sockaddr_nl dst_addr;int r, buflen;char *buf;int nla_len = strlen(nla_data)+1;msgtemplate_t msg;if (family_id == 0) {return 0;}msg.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);msg.nlh.nlmsg_type = family_id;msg.nlh.nlmsg_flags = NLM_F_REQUEST;msg.nlh.nlmsg_seq = 0;msg.nlh.nlmsg_pid = getpid();msg.gnlh.cmd = genl_cmd;msg.gnlh.version = 0x1;na = (struct nlattr *) GENLMSG_DATA(&msg);na->nla_type = nla_type;na->nla_len = nla_len + 1 + NLA_HDRLEN;memcpy(NLA_DATA(na), nla_data, nla_len);msg.nlh.nlmsg_len += NLMSG_ALIGN(na->nla_len);buf = (char *) &msg;buflen = msg.nlh.nlmsg_len ;memset(&dst_addr, 0, sizeof(dst_addr));dst_addr.nl_family = AF_NETLINK;dst_addr.nl_pid = 0;dst_addr.nl_groups = 0;while ((r = sendto(sock_fd, buf, buflen, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr))) < buflen) {if (r > 0) {buf += r;buflen -= r;} else if (errno != EAGAIN) {return -1;}}return 0;
}
void genl_rcv_msg(int family_id, int sock_fd)
{int ret;struct msgtemplate msg;struct nlattr *na;ret = recv(sock_fd, &msg, sizeof(msg), 0);if (ret < 0) {printf("error receiving reply message via Netlink length < 0\n");return;}/* Validate response message */if (msg.nlh.nlmsg_type == NLMSG_ERROR) { /* error */printf("NLMSG_ERROR error received NACK - leaving \n");return;}if (msg.nlh.nlmsg_type == family_id && family_id != 0) {na = (struct nlattr *) GENLMSG_DATA(&msg);unsigned char * result = (unsigned char *)NLA_DATA(na);int i;memcpy(&kernel_sended_pre_time, result,sizeof(kernel_sended_pre_time));if(kernel_sended_pre_time > 0 && kernel_sended_pre_time <= MAX_PROBED_NUM ){memcpy(kernel_sended_info,result + sizeof(kernel_sended_pre_time),kernel_sended_pre_time*PROBED_INFO);}}
}int genl_socket_init(void)
{struct sockaddr_nl saddr;int fd;fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);if (fd < 0){perror("genl_init: socket create error\n");return -1;}memset(&saddr, 0, sizeof(saddr));saddr.nl_family = AF_NETLINK;saddr.nl_groups = 0;saddr.nl_pid = getpid();if (bind(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0){perror("bind failed\n");close(fd);return -1;}return fd;
}
  • 源码 Makefile
LDFLAGS = -ljson-c wifi-probe:wifi-probe.o genl-lib.o$(CC) $(LDFLAGS) wifi-probe.o genl-lib.o -o wifi-probe
genl-lib.o:genl-lib.c$(CC) $(CFLAGS) -c genl-lib.c
wifi-probe.o:wifi-probe.c$(CC) $(CFLAGS) -c wifi-probe.cclean:rm *.o wifi-probe
  • 外层 Makefile
#
# Top level makefile for example application
#include $(TOPDIR)/rules.mkPKG_NAME:=wifi-probe
PKG_VERSION:=1.0.0
PKG_RELEASE:=1include $(INCLUDE_DIR)/package.mkdefine Package/wifi-probeSECTION:=utilsDEPENDS:= +libjson-cCATEGORY:=UtilitiesTITLE:= wifi-probe userspace applications
endefdefine Build/Preparemkdir -p $(PKG_BUILD_DIR)$(CP) ./src/* $(PKG_BUILD_DIR)
endefdefine Build/Configure
endefTARGET_CFLAGS += $(FPIC)define Package/wifi-probe/install$(INSTALL_DIR) $(1)/bin$(INSTALL_DIR) $(1)/usr/sbin$(INSTALL_BIN) $(PKG_BUILD_DIR)/wifi-probe $(1)/bin/$(INSTALL_BIN) $(PKG_BUILD_DIR)/wifi-report $(1)/usr/sbin/
endef$(eval $(call BuildPackage,wifi-probe))
备注

感觉自己有点小偷的感觉啦,这里都是复制别人的代码,感觉不太好还是把参考的文章贴一下吧。

这个写的很详细,贴了GitHub源码

https://blog.csdn.net/bingdele/article/details/96442385

https://hub.fastgit.org/wesley-fly/wifi-probe

这个应该是最早的版本吧,可惜没开源,图都绿了

http://blog.sina.com.cn/s/blog_636a55070102wpfx.html

基于rt2860v2的wifi探针

rt2860v2 是一个开源的驱动,我觉得大致原理应该是和上面的方法差不多,就是有人做成开源的驱动了,别人博客也写的很清楚了,我就不多说了。

https://blog.csdn.net/lixuande19871015/article/details/71601363

结语

虽然我最后的WiFi探针功能是通过买别人的小板子做成的,但是上面这些方法应该是网上常见的方法了,在重新编写博客的时候,我都觉得可能是我当时太年轻,没仔细按博客做,如果读者有需要,可以试试上面方法,我是不想再做这样的工作了,但是说不定你的板子就可以了!

end

完美撒花

openwrt 中 WiFi探针的三种实现办法相关推荐

  1. openwrt上wifi探针的实现

    openwrt上wifi探针的实现 探针是通过wifi搜集经过这个AP范围的手机的mac地址,没有什么深刻的东西,知乎上关于这个东西讨论的很多,有人觉得很有用,可以做很多增值的应用,有人觉得没啥用,不 ...

  2. ZigBee网络数据传递流程_蓝牙、Wifi与ZigBee三种,这三种无线传输技术,谁能一统天下...

    智能产品种类越来越多,运用在智能家居上的技术也越来越成熟.然而在无线通信协议上却一直无法做到统一,从目前的情况来看,短期内是无法实现这一愿望的了.既然如此,我们何不另辟蹊径,在这些标准中,选择优势最大 ...

  3. 在JavaScript中重复字符串的三种方法

    In this article, I'll explain how to solve freeCodeCamp's "Repeat a string repeat a string" ...

  4. oracle if=,oracle中if/else的三种实现方式详解

    1.标准sql规范 1.单个IF IF v=... THEN END IF; 2.IF ... ELSE IF v=... THEN ELSE t....; END IF; 3.多个IF IF v=. ...

  5. Django中Model继承的三种方式

    Django中Model继承的三种方式 Django中Model的继承有三种: 1.抽象继承 2.多表继承 3.proxy model(代理model) 1.抽象继承 第一种抽象继承,创建一个通用父类 ...

  6. android获取自定义属性,android 自定义控件中获取属性的三种方式(转)

    第一种方法,直接设置属性值,通过attrs.getAttributeResourceValue拿到这个属性值. (1)在xml文件中设置属性值 android:layout_width="f ...

  7. UE4学习-在虚幻编辑器中打开VS的三种方式

    文章目录 方式一 方式二 方式三 在虚幻编辑器中打开VS的三种方式 方式一 在文件浏览器这里,选择C++类,然后在文件夹内,找到一个和截图中类似的图标,双击,即打开vs,并在vs中打开这个类的代码. ...

  8. 在JavaScript中反转字符串的三种方法

    This article is based on Free Code Camp Basic Algorithm Scripting "Reverse a String" 本文基于F ...

  9. mysql添加临时索引_mysql 中添加索引的三种方法

    在mysql中有多种索引,有普通索引,全文索引,唯一索引,多列索引,小伙伴们可以通过不同的应用场景来进行索引的新建,在此列出三种新建索引的方法 mysql 中添加索引的三种方法 1.1 新建表中添加索 ...

  10. centos 卸载软件_一篇看懂!详解-Linux系统中安装软件的三种方法

    Linux系统中安装软件的三种方法 注:本文主要以CentOS为例介绍常用的安装方式,其他版本linux在文章底部 Linux系统中怎么安装软件,首先说一下应用程序与系统命令的区别: 1.文件位置 系 ...

最新文章

  1. php ob_flush无效,php ob_flush,flush在ie中缓冲无效的解决方法
  2. 五款软件快速解决网络故障问题
  3. 创维oled工厂模式abd_创维OLED游戏电视创维专业电竞显示器 为SN战队保驾护航
  4. Oracle11.2.0.4 windows32+64bit opatch工具 11.2.0.0 百度云盘下载
  5. SpringAOP xml 方式和注解简单实现日志处理
  6. 【优先队列】HDU 1873——看病找医生
  7. 2021年赚钱的建议
  8. golang slice分割和append copy还是引用
  9. http抓包实践--(三)--HTTP协议中的缓存
  10. IDEA 更换主题样式
  11. Java多线程编程核心技术 (pdf完整版)
  12. 人工智能改变我们生活的7种方式
  13. Unicode(全世界每个国家字符的唯一编码0x000000 到 0x10FFFF)与UTF-8的区别
  14. 通过实例学Python爬虫(一)——认识HTML网页与爬虫基础框架
  15. jQuery ajax 文件下载
  16. python点图为什么显示不出来怎么办_Python底图不显示打印的点
  17. Unity中Switch的用法
  18. 递归算法的时间复杂度的分析方法
  19. http协议中get和post的区别(转)
  20. HDFS的DN退役以及如何加快DN退役速度

热门文章

  1. 2020年9月电子学会Python等级考试试卷(二级)考题解析
  2. mysql explain ref const_MYSQL explain详解
  3. Android广播机制Broadcast详解
  4. Empathy Map:让团队移情到用户的奥秘
  5. 忘记电脑密码的解决方法——使用pe工具重置电脑密码
  6. springboot 整合 security(四) 方法级别权限控制 @resource,@secured,@preAuthorize
  7. .bat、python与C++程序进行批量处理的学习与实践
  8. 人工智能——产生式规则
  9. VC++开发RTX拨打电话插件
  10. 读取图片java_用java读取图片的三种方式