网络层以上的协议用IP地址来标识网络接口,但以太数据帧传输时,以物理地址来标识网络接口。因此我们需要进行IP地址与物理地址之间的转化
对于IPv4来说,我们使用ARP地址解析协议来完成IP地址与物理地址的转化(IPv6使用邻居发现协议进行IP地址与物理地址的转化,它包含在ICMPv6中)
工作流程
每个主机都会在自己的 ARP 缓冲区中维护一个 ARP 列表,表示 IP 地址和 MAC 地址之间的对应关系
主机(网络接口)新加入网络时(也可能只是mac地址发生变化,接口重启等), 会发送免费ARP报文把自己IP地址与Mac地址的映射关系广播给其他主机
网络上的主机接收到免费ARP报文时,会更新自己的ARP缓冲区。将新的映射关系更新到自己的ARP表中
某个主机需要发送报文时,首先检查 ARP 列表中是否有对应 IP 地址的目的主机的 MAC 地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送 ARP 数据包,该数据包包括的内容有:源主机 IP 地址,源主机 MAC 地址,目的主机的 IP 地址等
当本网段的所有主机收到该 ARP 数据包时
首先检查数据包中的 IP 地址是否是自己的 IP 地址,如果不是,则忽略该数据包
如果是,则首先从数据包中取出源主机的 IP 和 MAC 地址写入到 ARP 列表中,如果已经存在,则覆盖
然后将自己的 MAC 地址写入 ARP 响应包中,告诉源主机自己是它想要找的 MAC 地址
源主机收到 ARP 响应包后。将目的主机的 IP 和 MAC 地址写入 ARP 列表,并利用此信息发送数据。如果源主机一直没有收到 ARP 响应数据包,表示 ARP 查询失败
存在问题:只要能模拟报文格式,无需身份验证
ARP攻击(断网):通过发送虚假的ARP广播或ARP单播报文,其中虚假的MAC地址是不存在的
ARP欺骗(窃取):通过发送虚假的ARP广播或ARP单播报文,其中虚假的MAC地址是攻击者的MAC地址,ARP欺骗(又称 中间人攻击)可以正常上网通信,会造成信息泄露
数据结构与算法 常用排序算法实现比较丑陋,勿喷啊
冒泡排序:从前向后比较相邻的元素。如果前一个比后一个大,就交换他们两个,每一轮把一个最大的数运到数组最后面。
public static int[] sort(int[] arr) { int len = arr.length; // 冒泡总次数 for (int i = 1; i < len; i++) { boolean flag = true; // 每次冒泡过程 for (int j = 0; j < len - i; j++) { if (arr[j] > arr[j + 1]) { MyUtils.swap(arr, j, j + 1); flag = false; } } if (flag) { // 如果一个冒泡过程没改变,退出返回已经有序 break; } } return arr; }
选择排序:每次从未排序数组中找一个最小的元素,放到以有序数组后面
public static int[] sort(int[] arr) { int len = arr.length; // 选择次数 for (int i = 0; i < len - 1; i++) { int min = i; // 每次选择过程 for (int j = i + 1; j < len; j++) { if (arr[j] < arr[min]) { min = j; } } if (min != i) { MyUtils.swap(arr, i, min); } } return arr; }
插入排序:每次把未排序的第一个数,插入到已排序数组的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)
public static int[] sort(int[] arr) { int len = arr.length; // 插入次数,left为未有序的左边 for (int left = 1; left < len; left++) { int temp = arr[left]; int right = left - 1; // right为有序部分的右边 while (right >= 0 && temp < arr[right]) { arr[right + 1] = arr[right]; right--; } // 判断是否需要插入 if (right != left - 1) { arr[right + 1] = temp; } } return arr; }
归并排序:将数组分成很多小份,然后依次合并
public static int[] sort(int[] arr) { sort(arr, 0, arr.length - 1); return arr; } private static void sort(int[] arr, int left, int right) { if (left == right) { return; } // 等同于(right + left)/2 int mid = left + ((right - left) >> 1); sort(arr, left, mid); sort(arr, mid + 1, right); // 已经分成了许多小份,开始合并 merge(arr, left, mid, right); } private static void merge(int[] arr, int left, int mid, int right) { int[] help = new int[right - left + 1]; int i = 0; int p1 = left; int p2 = mid + 1; // 左边右边通过辅助数组合并 while (p1 <= mid && p2 <= right) { help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } // 左边没空加到后面 while (p1 <= mid) { help[i++] = arr[p1++]; } // 右边没空加到后面 while (p2 <= right) { help[i++] = arr[p2++]; } for (int j = 0; j < help.length; j++) { arr[left + j] = help[j]; } }