-
手把手教你制作原版win10系统安装盘
这两天电脑突然坏了,鼠标不能正常移动了,起初以为是系统出了问题,就在网上搜索了一波教程,下载了一个一键装机工机具,没想到重装系统之后连鼠标都没有了!!!毫无疑问,装机工具给我安装的系统不对劲,作为计算机专业出身而且是程序汪的我怎么能把电脑带去电脑维修店呢!!!于是就又上网寻找了一波教程,终于找到了,并成功将系统还原而且是win10官方纯净版本的,没有Ghost安装带来的各种附带软件,弹窗广告等问题,下面对这次系统安装过程做个记录并分享给大家。一、下载win10镜像按照原文中的教程,首先到微软官网下载win10镜像:微软官网win10下载打开官网之后会要求选择要下载的win10版本,选好会后点击确认按钮,随后会让我们选择语言,点击下拉框,这里我选择的是简体中文,选择完成之后点击下方的确认按钮,随后,会跳转到如下选择下载系统位数的界面,根据目标主机的硬件情况选择就好了,这里我选择的是64bit的,选择之后静静等待下载吧。二、执行镜像文件制作启动盘下载完成之后会得到一个iso后缀可执行文件,这就是win10最新的1909版本的系统盘制作安装工具。//执行文件.png在当前电脑插上准备好的U盘,然后直接双击执行上面的iso镜像文件,稍后会出现一个“Windows10安装程序”对话框,里面显示的是一些声明和条款,不用管它,点击“接受“”,出现另一个对话框:“你想执行什么操作?”选第二个“为另一台电脑创建安装介质(u盘、DVD或ISO文件)”,点击下一步,开始“选择语言、体系结构和版本”,选择以前先把“对这台电脑使用推荐的选项”前面的对勾去掉,否则系统会根据你现在使用的这台电脑的配置自动选择win10的版本。然后,语言就选默认的“中文(简体)”,版本选“Windows10”,体系结构选“64位(X64)”点下一步,选择要使用的介质,选“U盘”,点下一步选择你的U盘盘符继续点击下一步开始win10系统的下载,这个过程根据网速快慢大概得15-40分钟。下载完成后,程序会自动检查系统文件的完整性、创建Windows10介质,最后点击“完成”,一个干净、稳定的win10系统安装盘就制作完成了。三、使用启动盘安装系统开机按快捷键进入启动菜单(不同品牌的电脑或主板按键不同,不知道就看说明书或者找度娘),选择从U盘启动,进入下图的安装界面开始系统的安装,就和平时安装应用软件一样,按安装程序的提醒一步步来就OK了。原文链接https://baijiahao.baidu.com/s?id=1659068917431404756&wfr=spider&for=pc
LoveIT 2021-10-28Windows -
centOS 7下无法启动网络(service network start)错误解决办法
今天在centOS7下更改完静态ip后发现network服务重启不了,翻遍了网络,尝试了各种方法,终于解决了。现把各种解决方法归纳整理,希望能让后面的同学少走点歪路。。。首先看问题:执行servicenetworkrestart命令后出现下面的错误:根据提示输入systemctlstatusnetwork.service命令后出现如下错误信息:network服务启动不了首先保证/etc/sysconfig/network-scripts目录下的ifcfg-xxx(每台机器不一定相同)没有错误(如果你改动过的话),这里的错误指的在更改过程中因为手误敲错字母之类的。网上最常见的几种做法如下:1、和NetworkManager服务有冲突,这个好解决,直接关闭NetworkManger服务就好了,serviceNetworkManagerstop,并且禁止开机启动chkconfigNetworkManageroff。之后重启就好了。2、和配置文件的MAC地址不匹配,这个也好解决,使用ipaddr(或ifconfig)查看mac地址1:lo:<LOOPBACK,UP,LOWER_UP>mtu65536qdiscnoqueuestateUNKNOWNqlen1link/loopback00:00:00:00:00:00brd00:00:00:00:00:00inet127.0.0.1/8scopehostlovalid_lftforeverpreferred_lftforeverinet6::1/128scopehostvalid_lftforeverpreferred_lftforever2:ens33:<BROADCAST,MULTICAST,UP,LOWER_UP>mtu1500qdiscpfifo_faststateUPqlen1000link/ether00:0c:29:b8:e7:21brdff:ff:ff:ff:ff:ffinet192.168.65.141/24brd192.168.65.255scopeglobaldynamicens33valid_lft1193secpreferred_lft1193secinet6fe80::cca2:d835:f93:e637/64scopelinkvalid_lftforeverpreferred_lftforever00:0c:29:b8:e7:21这个就是MAC地址了,将/etc/sysconfig/network-scripts/ifcfg-xxx中的HWADDR(如果没有就添加上)改成这个MAC地址3、设定开机启动一个名为NetworkManager-wait-online服务,命令为:systemctlenableNetworkManager-wait-online.service=========================================================================================================上面两个是我看到最多的解决方法,但是很遗憾,我的并没有解决。有查看资料,发现了以下一些方法:4、查看/etc/sysconfig/network-scripts下,将其余无关的网卡位置文件全删掉,避免不必要的影响,即只留一个以ifcfg开头的文件,留的那一个应和使用ipaddr命令查看ip第二条开头的名称一致(我的是ens33,参见上面我贴出的ipaddr命令执行结果),所以我只留了一个ifcfg-ens33。(我的其中两台机器就是这么弄好的,因为我在修改前留了备份,把备份删掉就好了。。。)5、将ifcfg-xxx文件中的DEVICE一行注释掉。6、将ifcfg-xxx中的NAME改为和文件名一致。7、在VMWare的编辑-虚拟网络编辑器中将网络模式改为桥接。8、看VMWare右下角的网络适配器是否连接,如果没有连接则连接上。(补充:点击网络适配器-设置,将NAT模式改为桥接试试,我的改为桥接后可以重启network服务了但是上不了网,可以重启后再将桥接模式改为NAT模式,发现既可以上网又可以重启network服务了)9、查看下你电脑有没有禁用了VMwareDHCPservice和VMwareNATservice这几个vm服务,如果禁用则开启。10、如果你改成了静态ip别忘了将BOOTPROTO改为static。11、如果以上都没有解决,还有最后一招--重启看一看有没有奇迹发生!!(我的另外一台重启后莫名就好了)参考【1】一路前行1.centOS7下无法启动网络(servicenetworkstart)错误解决办法.CSDN
LoveIT 2020-12-13Linux -
海量数据处理:如何从10亿个数中,找出最大的10000个数?(top K问题)
问题:有10亿个不重复的数字,内存中只能放进1万个数,怎么找到最大的1万个数字?此题多数互联网公司在面试的时候都会问到,这里简单总结一下。我将在下面介绍一些基本的海量数据处理的方法,供大家参考。需要明确的一点是,现实情况复杂多变,所以对于海量数据处理这样大的主题,是不可能用一篇博客就说清楚的。但方法论可以一通百通,我们通过一些已经被无数次实验证明有效的方法,就能大致理解对此类问题的解决思路,之后在面临新的问题时,至少能找到一个大致的方向。首先,被问到这题应该先询问数据规模与数据分布。如果数据规模比较小,在千数量级,采用O(nlgn)的快排或大顶堆取前K个即可。如果数据为整形,且分布范围不大,可以考虑计数排序,在线性时间中求解。其次,如果不是上面讨论的情况,就是大规模一般情况。数据集可能在亿数量级,此时有可能数据都没有办法全部加载到内存中,此时通常比较好的方案是:分治、Trie树/hash、小顶堆,即先将数据集按照Hash方法分解成多个小数据集,然后使用Trie树或Hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出现频率最高的前K个数,最后在所有topK中求出最终的topK。方法一:将数据全部排序将数据全部排序,然后在排序后的集合中进行查找,最快的排序算法的时间复杂度一般为O(nlogn),如快速排序。但是在32位的机器上,每个int类型占4个字节,10亿个数就要占用3.7G的存储空间,对于一些可用内存小于3.7G的计算机而言,很显然是不能一次将全部数据读入内存进行排序的。其实即使内存能够满足要求寻找出最大的10000个数即可,而排序却是将所有的元素都排序了,做了很多的无用功。只能锁这是一个方法,但是实际可行性不高。方法二:局部淘汰法维护一个10000长度的数组a[],先读取源数据中的前10000个放入数组,对该数组进行升序排序,再依次读取源数据第10000个以后的数据,和数组中最小的元素(a[0])比较,如果小于a[0]直接pass,大于的话,就丢弃最小的元素a[0],利用二分法找到其位置,然后该位置前的数组元素整体向前移位,直到源数据读取结束。最后遍历完这10亿个数,得到的结果容器中保存的数即为最终结果了。此时的时间复杂度为O(n+m^2),其中m为容器的大小,即10000。这比方法一效率会有很大的提高,但是当K的值较大的时候,长度为K的数据整体移位,也是非常耗时的。方法三:常规解法,利用最小堆的原理(时间复杂度为O(N*logK))(掌握)首先读入前10000个数来创建大小为10000的最小堆,建堆的时间复杂度为O(mlogm)(m为数组的大小即为10000),最小堆的堆顶元素就是最大K个数中的最小的一个。之后读取后续数字,每次扫描一个数据X,如果X比堆顶元素小,则不需要改变原来的堆。如果X比堆顶元素大,那么用X替换堆顶元素,在替换之后,X可能破坏了最小堆的结构,需要调整堆来维持堆的性质。调整过程时间复杂度为O(logK)。全部的时间复杂度为O(N*logK)。这种方法当数据量比较大的时候,比较方便。因为对所有的数据只会遍历一次。甚至还可以运用多线程技术,把所有10亿个数据分组存放,比如分别放在1000个文件中。这样处理就可以分别在每个文件的10^6个数据中找出最大的10000个数,合并到一起在再找出最终的结果。下面我使用堆排序的思想写一个寻找数组中最大的K个数的模板,仅供参考:方法四:分治法+快速排序原理(时间复杂度O(N*logK)(掌握)假设N个数存储在数组S中,从数组中随机找最大的K个数,将数组分成两部分SMax和SMin。SMax中的元素大于等于X,SMin中的元素小于X。出现如下两种情况:(1)若SMax组的个数大于或等于K,则继续在SMax分组中找取最大的K个数字。(2)若SMin组中的数字小于K,其个数为T,则继续在SMin中找取K-T个数字。一直这样递归下去,不断把问题分解成小问题,平均时间复杂度为O(N*logK)。上面这个思路具体可以参考剑指Offer40.最小的k个数这道题,这道题是寻找最小的K个数,但是思想都是一样的。这里我给出个模板,仅供参考:上面的思路在数据规模比较小的时候完全可以和方法一相互替换使用,但是当数据量比较大的时候,这个方法不能直接使用,但是方法一可以直接使用,而此方法不能直接使用的原因就是数据没有办法一次性加载到内存中。因此我们可以考虑配合分治法,即:将10亿个数据分成100份,每份1000万个数据,找到每份数据中最大的10000个,最后在剩下的100*10000(100万)个数据里面找出最大的10000个。如果100万数据选择足够理想,那么可以过滤掉10亿数据里面99%的数据。一些经常被提及的该类问题这里再列举一些常见的海量数据处理的问题以及给出对应的解决方案,仅供参考:(1)有10个文件,每个文件1GB,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。按照query的频度排序。这个还是典型的Topk问题,解决方案如下:方案1:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件中。这样新生成的文件每个的大小大约也1G(假设hash函数是足够离散的)。找一台内存在2G左右的机器,依次用hash_map(query,query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件(记为)。最后对这10个文件进行归并排序即可。方案2:一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。这样,我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。方案3:与方案1类似,但在做完hash分成多个文件后,可以采用大数据分布式的架构来处理(比如MapReduce),最后再进行合并。(2)有一个1GB大小的文件,里面的每一行是一个词,词的大小不超过16个字节,内存限制大小是1MB。返回频数最高的100个词。(3)给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?方案1:分治法。粗略计算得知,每个文件的大小为50*10^8×64=320G,单单一个文件就远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。具体策略如下:遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,...,a999)中。这样每个小文件的大约为300M(假设hash函数足够离散)。遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,...,b999)。这样处理后,所有可能相同的url都在对应的小文件(a0-b0,a1-b1,...,a999-b999)中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。方案2:使用布隆过滤器。如果允许有一定的错误率,可以使用Bloomfilter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloomfilter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloomfilter,如果是,那么该url应该是共同的url。(4)现有两个各有20亿行的文件,每一行都只有一个数字,求这两个文件中数字的交集?这个题的如果换成给定两个整型数组让求两个数组(nums1,nums2)的交集该如何做呢?这是LeetCode第349题,解决方法有很多,这里我提供两种解法,仅供参考:解法1:由于给定的数据有限,所以可以不用考虑内存装不装的下的问题,因此这里可以直接先把nums1中的所有元素全部添加到集合set1中,然后遍历nums2,判断在nums2中的元素在集合set1中是否存在,如果存在,说明有交集,就把他添加到结果中。解法2:对两个数组先升序排序,排序之后设置两个指针分别从nums1和nums2数组首地址开始遍历数组,比那里过程中会有以下3种情况:1.nums1[i]<nums2[j],此时应该将i继续向后移动,j不动,因为nums1[i]已经比nums2[j]小了,在他们之间不可能找到相等的数2.nums1[i]>nums2[j],情况正好相反,此时移动j就好了,i不动,道理类似3.nums1[i]=nums2[j],出现了交集,将此值加入结果,并且i,j同时向后移动下面分别给出代码实现,仅供参考!有了这两种解题思路之后,尤其是方法2的思路之后,现在我们遇到最大的困难就是大文件无法全部加载到内存中,这个问题的解决办法还是分治法,即先使用外排序分别对两个文件进行分隔排序,比如分隔成1000个小文件,总之排序完成会得到两个有序的文件,之后同时遍历这两个文件就可以了,每次遍历加载文件一部分数据,遍历时采取的方法就如方法2所示。(5)海量日志数据,提取出某日访问网站次数最多的那个IP。粗略估算一下便知,就拿32位的IPV4来说,最多可以有2^32(42亿)种取值情况,所以不能完全加载到内存中处理;可以考虑采用分而治之的思想:按照IP地址的Hash(IP)%1024值,把海量IP日志分别存储到1024个小文件中,每个小文件最多包含420万个IP地址(hash函数理想的情况下)对于每一个小文件,可以构建一个IP为key,出现的次数为value的HashMap,同时记录当前出现次数最多的那个IP地址,通过HashMap可以很容的得到该文件中次数最个IP每个文件一个最大次数Ip,汇总起来可以得到1024个IP,再依据常规的排序算法得出总体上出现次数最多的IP。(6)10亿个整数(有重复),找出重复次数最多的100个整数。解决思路:通过hash取模的方式将大文件分成电脑可以直接完全加载计算的小份文件,比如10亿个数分成1000份;在每一小份文件中排序,统计出出现次数最多的100个数;将每个文件中出现次数最多的100个数拿出来放到一起使用上文所说的快排思路或小顶堆取出其前100个即可。(7)海量数据排序问题:一个文件中有9亿条不重复的9位整数,对这个文件中数字进行排序方法一、数据库排序法。将文本文件导入到数据库中,让文本文件导入到数据库中,让数据库进行索引排序操作后提取数据到文件.该种方法虽然操作简单,方便,但是运算速度较慢,而且对数据库设备要求比较高.方法二、分治法。通过Hash法将9亿条数据分为20段,每一段大约5000万条,大约需要占用500万*4B=200MB空间,在文件中依次搜索0~5000万,50000001~1亿,,,,,,,将排序的结果存入文件,该方法要装满9位整数,一共需要20次,所以一共要进行20次排序,需要对文件进行20次读操作.该方法虽然缩小了每次使用的内存空间大小,但是编码复杂,速度也慢.方法三、位图法。考虑到最大的9位整数为999999999,由于9亿条数据是不重复的,如果这些数据都还是正整数的话,那么可以申请一个10亿个元素的位数组bitset表示0~999999999,结点中用0表示没有这个数,1表示存在这个数,判断0或1只用一个bit存储就够了,而声明一个可以包含9位整数的bit数组,一共需要10亿bit/8/1024/1024≈120MB内存,把内存中的数组全部初始化为0,读取文件中的数据,并将数据放入内存.比如读到一个数据为314332897这个数据,那就先在内存中找到314332897这个bit,并将bit值置为1,遍历整个bit数组,将bit为1的数组下标存入文件,最终得到排序的内容.参考【1】xiaoding133.【编程之美】读书笔记:寻找最大的K个数.CSDN【2】寒泉Hq.海量数据处理:如何从10亿个数中,找出最大的10000个数?(topK问题).CSDN【3】guoziqing506.海量数据处理技巧.CSDN【4】July、youwang、yanxionglu.十道海量数据处理面试题与十个方法大总结.CSDN
LoveIT 2020-12-05数据结构与算法 -
深度优先搜索解决矩阵搜索问题
最近在刷LeetCode的时候发现一类和二维矩阵(二维数组)搜索有关的题目,一般使用深度优先或广度优先搜索就可以很好的解决这类题目,这里特意总结一下。1、剑指Offer12.矩阵中的路径这是一个典型的矩阵搜索问题,可使用深度优先搜索(DFS)+剪枝解决。首选,在矩阵中任意选择一个格子作为路径的起点。假设举证中某个格子的字符为ch,并且这个格子将对应于路径上的第i个字符。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的第i个位置。如果路径上第i个字符正好是ch,那么到相邻的格子寻找路径上的第i+1个字符。出矩阵边界上的格子以外,其他格子都有上、下、左、右4个方向可以选择,重复这个过程,知道路径上所有字符都在矩阵中找到相应的位置。并且为了便于控制一个格子只会被访问一遍,我们可以使用一个和矩阵一样大小的boolean矩阵来标识路径是否已经进入了某个格子。相信上面代码一看就会,值得说明的一点使用了isVisited来判断是否再次访问,用来标记已经访问过的点。2、岛屿问题岛屿问题是一类经典的网格搜索类问题。关于岛屿的题目有:200.NumberofIslands岛屿数量695.MaxAreaofIsland岛屿最大面积463.岛屿的周长694.不同岛屿的个数711.不同岛屿的个数II要求解这道题,我们首先来看如何在网格上做DFS如何在网格上做DFS网格问题是这样一类搜索问题:有m\timesnm×n个小方格,组成一个网格,每个小方格与其上下左右四个方格认为是相邻的,要在这样的网格上进行某种搜索。这种题目乍一看可能有点麻烦,实际上非常简单,尤其是用DFS的方法。题目没有限制的话,我们尽量用DFS来写代码。下面我们一步步地构造出方格类DFS的代码。首先,每个方格与其上下左右的四个方格相邻,则DFS每次要分出四个岔:但是,对于网格边缘的方格,上下左右并不都有邻居。一种做法是在递归调用之前判断方格的位置,例如位于左边缘,则不访问其左邻居。但这样一个一个判断写起来比较麻烦,我们可以用“先污染后治理”的方法,先做递归调用,再在每个DFS函数的开头判断坐标是否合法,不合法的直接返回。同样地,我们还需要判断该方格是否有岛屿(值是否为1),否则也需要返回。之后为了能够让递归停止下来,我们需要在遍历的时候把已经遍历过的网格做一下标记,可以单独开辟一个和矩阵同样大小的二维boolean矩阵来记录,或者也可以直接修改遍历过的网格的值使其在下一次判断时条件不成立。通常考虑到内存友好,我们直接在原数组上标记就好了。标记遍历过的方格并不需要使用额外的空间,只需要改变方格中存储的值就可以。在这道题中,值为0表示非岛屿(不可遍历),值为1表示岛屿(可遍历),我们用2表示已遍历过的岛屿。这样我们就可以得到网格DFS遍历的框架代码:岛屿有关的二维数组搜索问题,基本都可以在此框架上修改得到结果。我们来看下面几道经典的题。2.1LeetCode200.岛屿数量示例:对于这个二维矩阵进行遍历,如果来到一个是1的位置,开始一个“感染”过程,就是从当前位置出发,把连成一片的1全部变成2。“感染”过程结束之后,继续遍历二维矩阵,直到结束。有多少次“感染”过程,就有多少个岛。实现“感染”过程。假设从i行j列位置出发,向上下左右四个位置依次去“感染”。写成递归函数即可。2.2LeetCode695.岛屿的最大面积示例:基本思路和上面相同,只是需要在遍历每个岛屿的时候,使用一个计数器将这个岛屿的陆地数目记录下来返回,然后比较每个岛屿的大小,返回最大的岛屿的面积即可。代码实现上只需做略微改动即可。2.3LeetCode463.岛屿的周长示例:求岛屿的周长其实有很多种方法,如果用DFS遍历来求的话,有一种很简单的思路:岛屿的周长就是岛屿方格和非岛屿方格相邻的边的数量。注意,这里的非岛屿方格,既包括水域方格,也包括网格的边界。我们可以画一张图,看得更清晰:将这个“相邻关系”对应到DFS遍历中,就是:每当在DFS遍历中,从一个岛屿方格走向一个非岛屿方格,就将周长加1。完整代码如下:那么每一次进行“深度优先遍历”或者“广度优先遍历”的条件就是:1、这个格子是陆地1,如果是水域0就无从谈论“岛屿”;2、这个格子不能是之前发现“岛屿”的过程中执行了“深度优先遍历”或者“广度优先遍历”操作,而被标记的格子(这句话说得太拗口了,大家意会即可,意会不了不是您的问题,是我表达的问题,直接看代码会清楚很多)。3、剑指Offer13.机器人的运动范围示例:输入:m=2,n=3,k=1输出:3这个题目和前面对题目类似,也可以看做是一个m×n的矩阵。同样,在这个矩阵中,除了边界上的格子,其他位置的格子都有4个相邻的格子。机器人从(0,0)开始。当他准备移动到(i,j)的时候,首先判断他能否进入,如果他可以进入,则在判断它能否进入4个相邻的格子。同时这道题还有一个隐藏的优化:我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索。如下图,我们展示了19*19的地图随着限制条件k的放大,可行方格的变化趋势,每个格子里的值为行坐标和列坐标的数位之和,绿色方格代表非障碍方格,即其值小于等于当前的限制条件k。我们可以发现随着限制条件k的增大,(0,0)所在的绿色方格区域内新加入的非障碍方格都可以由上方或左方的格子移动一步得到。而其他不连通的黄色方格区域会随着k的增大而连通,且连通的时候也是由上方或左方的格子移动一步得到,因此我们可以将我们的搜索方向缩减为向右或向下。可以套用岛屿问题中的模板,完整代码如下:参考【1】liweiwei1419.DFS+BFS+并查集(Python代码、Java代码).力扣(LeetCode)【2】nettee.图解:在DFS遍历过程中求周长(Java).力扣(LeetCode)
LoveIT 2020-10-24数据结构与算法 -
进程间通信的几种方式
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。**进程间通信(IPC,InterProcessCommunication)**是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、信号、共享内存、Socket这6种情况。其中Socket支持不同主机上的两个进程IPC。1、管道1.1无名管道管道,通常指无名管道,是UNIX系统IPC最古老的形式。如果你学过Linux命令,那你肯定很熟悉「|」这个竖线上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令ps-ef的输出,作为后一个命令grepmysql的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。同时,我们得知上面这种管道是没有名字,所以「|」表示的管道称为匿名管道,用完了就销毁。无名管道具有以下特点:它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。1.2FIFO管道还有另外一个类型是命名管道,也被叫做FIFO,因为数据是先进先出的传输方式。FIFO具有以下特点:FIFO可以在无关的进程之间交换数据,与无名管道不同。FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。在使用命名管道前,先需要通过mkfifo命令来创建,并且指定管道名字:fifo1就是这个管道的名称,基于Linux一切皆文件的理念,所以管道也是以文件的方式存在,我们可以用ls看一下,这个文件的类型是p,也就是pipe(管道)的意思:接下来,我们往fifo1这个管道写入数据:你操作了后,你会发现命令执行后就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。于是,我们执行另外一个命令来读取这个管道里的数据:可以看到,管道里的内容被读取出来了,并打印在了终端上,另外一方面,echo那个命令也正常退出了。我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处,自然就是简单,同时也我们很容易得知管道里的数据已经被另一个进程读取了。1.3、管道的原理匿名管道的创建,需要通过下面这个系统调用:当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:其实,所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。2、消息队列前面说到管道的通信方式是效率低的,因此管道不适合进程间频繁地交换数据。对于这个问题,消息队列的通信模式就可以解决。比如,A进程要给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。同理,B进程要给A进程发送消息也是如此。再来,消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。但邮件的通信方式存在不足的地方有两点,一是通信不及时,二是附件也有大小限制,这同样也是消息队列通信不足的点。消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在Linux内核中,会有两个宏定义MSGMAX和MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。消息队列特点消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。3、共享内存消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程A和进程B的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。共享内存特点共享内存是最快的一种IPC,因为进程是直接对内存进行存取。因为多个进程可以同时操作,所以需要进行同步。信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。4、信号量用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。信号量表示资源的数量,控制信号量的方式有两种原子操作:一个是P操作,这个操作会把信号量减去-1,相减后如果信号量<0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量>=0,则表明还有资源可使用,进程可正常继续执行。另一个是V操作,这个操作会把信号量加上1,相加后如果信号量<=0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量>0,则表明当前没有阻塞中的进程;P操作是用在进入共享资源之前,V操作是用在离开共享资源之后,这两个操作是必须成对出现的。接下来,举个例子,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为1。具体的过程如下:进程A在访问共享内存前,先执行了P操作,由于信号量的初始值为1,故在进程A执行P操作后信号量变为0,表示共享资源可用,于是进程A就可以访问共享内存。若此时,进程B也想访问共享内存,执行了P操作,结果信号量变为了-1,这就意味着临界资源已被占用,因此进程B被阻塞。直到进程A访问完共享内存,才会执行V操作,使得信号量恢复为0,接着就会唤醒阻塞中的线程B,使得进程B可以访问共享内存,最后完成共享内存的访问后,执行V操作,使信号量恢复到初始值1。可以发现,信号初始化为1,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。例如,进程A是负责生产数据,而进程B是负责读取数据,这两个进程是相互合作、相互依赖的,进程A必须先生产了数据,进程B才能读取到数据,所以执行是有前后顺序的。那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为0。具体过程:如果进程B比进程A先执行了,那么执行到P操作时,由于信号量初始值为0,故信号量会变为-1,表示进程A还没生产数据,于是进程B就阻塞等待;接着,当进程A生产完数据后,执行了V操作,就会使得信号量变为0,于是就会唤醒阻塞在P操作的进程B;最后,进程B被唤醒后,意味着进程A已经生产了数据,于是进程B就可以正常读取数据了。可以发现,信号初始化为0,就代表着是同步信号量,它可以保证进程A应在进程B之前执行。5、信号上面说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。信号跟信号量虽然名字相似度66.66%,但两者用途完全不一样,就好像Java和JavaScript的区别。在Linux操作系统中,为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过kill-l命令,查看所有的信号:运行在shell终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如Ctrl+C产生SIGINT信号,表示终止该进程;Ctrl+Z产生SIGTSTP信号,表示停止该进程,但还未结束;如果进程在后台运行,可以通过kill命令的方式给进程发送信号,但前提需要知道运行中的进程PID号,例如:kill-91050,表示给PID为1050的进程发送SIGKILL信号,用来立即结束该进程;所以,信号事件的来源主要有硬件来源(如键盘Cltr+C)和软件来源(如kill命令)。信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。1.执行默认操作。Linux对每种信号都规定了默认操作,例如,上面列表中的SIGTERM信号,就是终止进程的意思。Core的意思是CoreDump,也即终止进程后,通过CoreDump将当前进程的运行状态保存在文件里面,方便程序员事后进行分析问题在哪里。2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即SIGKILL和SEGSTOP,它们用于在任何时候中断或结束某一进程。6、socket前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要Socket通信了。实际上,Socket通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。关于Linux的Socket可以参考我的另一篇博文Linux之Socket编程详解
LoveIT 2020-09-16操作系统 -
操作系统-缓存算法(页面置换算法)
缓存算法是指令的一个明细表,用于提示计算设备的缓存信息中哪些条目应该被删去。常见缓存算法包括FIFO、LFU、LRU、ARC、MRU。1、FIFOFIFO(FirstinFirstout),先进先出。其实在操作系统的设计理念中很多地方都利用到了先进先出的思想,比如作业调度(先来先服务),为什么这个原则在很多地方都会用到呢?因为这个原则简单、且符合人们的惯性思维,具备公平性,并且实现起来简单,直接使用数据结构中的队列即可实现。在FIFOCache设计中,核心原则就是:如果一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。在FIFOCache中应该支持以下操作:那么利用什么数据结构来实现呢?下面提供一种实现思路:利用一个双向链表保存数据,当来了新的数据之后便添加到链表末尾,如果Cache存满数据,则把链表头部数据删除,然后把新的数据添加到链表末尾。在访问数据的时候,如果在Cache中存在该数据的话,则返回对应的value值;否则返回-1。如果想提高访问效率,可以利用hashmap来保存每个key在链表中对应的位置。2、LRULRU全称是LeastRecentlyUsed,即最近最久未使用的意思。LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。用什么数据结构来实现LRU算法呢?可能大多数人都会想到:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。这种实现思路很简单,但是有什么缺陷呢?需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。那么有没有更好的实现办法呢?有一种流行的思路是利用链表和哈希表。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。详细的实现可以参考我的另一篇博文:LRU缓存算法到底是怎么一回事?3、LFULFU(LeastFrequentlyUsed)最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。用什么数据结构来实现LFU算法呢?有一种实现方式是使用三个HashMap,一个Map维护Key-Value的关系,一个Map维护Key-Freq的关系,前两个Map可以保证查找和插入是O(1)的时间复杂度,不能保证删除也是O(1)的时间复杂度,因此还需要使用一个类似LinkList的数据结构维护插入的时间顺序,并且还需要肯定是需要freq到key的映射,用来找到freq最小的key,因此可以维护一个Map<Integer,LinkedHashSet>类型的Map,这是个一对多的关系,一方就是频率,多方就是key,因为具有某个频率的key可能有多个,并且在使用一个变量minFreq维护系统当前访问频次最低的次数,当表满了的时候直接删除minFreq对应的LinkedHashSet中的第一个key就好了。详细的代码实现可以参考我的另一篇博文:LFU缓存算法到底是怎么一回事?4、自适应缓存替换算法(ARC)由IBMAlmaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。5、最近最常使用算法(MRU)这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。参考【1】Matrix海子.缓存算法(页面置换算法)-FIFO、LFU、LRU.博客园【2】JCjunior.操作系统之缓存算法.CSDN
LoveIT 2020-09-01操作系统 -
LFU缓存算法到底是怎么一回事?
1、LFU算法是什么?LFU(LeastFrequentlyUsed)最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。从实现难度上来说,LFU算法的难度大于LRU算法,因为LRU算法相当于把数据按照时间排序,这个需求借助链表很自然就能实现,你一直从链表头部加入元素的话,越靠近头部的元素就是新的数据,越靠近尾部的元素就是旧的数据,我们进行缓存淘汰的时候只要简单地将尾部的元素淘汰掉就行了。而LFU算法相当于是淘汰访问频次最低的数据,如果访问频次最低的数据有多条,需要淘汰最旧的数据。把数据按照访问频次进行排序,而且频次还会不断变化,这可不容易实现。2、实现LFU算法LFU常见的实现方式有以下三种:(1)利用一个数组存储数据项,用hashmap存储每个数据项在数组中对应的位置,然后为每个数据项设计一个访问频次,当数据项被命中时,访问频次自增,在淘汰的时候淘汰访问频次最少的数据。这样一来的话,在插入数据和访问数据的时候都能达到O(1)的时间复杂度,在淘汰数据的时候,通过选择算法得到应该淘汰的数据项在数组中的索引,并将该索引位置的内容替换为新来的数据内容即可,这样的话,淘汰数据的操作时间复杂度为O(n)。(2)利用小顶堆+哈希表,小顶堆插入、删除操作都能达到O(logn)时间复杂度,因此效率相比第一种实现方法更加高效。(3)使用三个HashMap,一个Map维护Key-Value的关系,一个Map维护Key-Freq的关系,前两个Map可以保证查找和插入是O(1)的时间复杂度,不能保证删除也是O(1)的时间复杂度,因此还需要使用一个类似LinkList的数据结构维护插入的时间顺序,并且还需要肯定是需要freq到key的映射,用来找到freq最小的key,因此可以维护一个Map<Integer,LinkedHashSet>类型的Map,这是个一对多的关系,一方就是频率,多方就是key,因为具有某个频率的key可能有多个,并且在使用一个变量minFreq维护系统当前访问频次最低的次数,当表满了的时候直接删除minFreq对应的LinkedHashSet中的第一个key就好了,这样就可以实现O(1)的删除时间复杂度接下来我们针对第三个方案,来实现一下LFU,首先我们分析一下算法的实现思路思路分析先从最简单的开始,根据LFU算法的逻辑,我们先列举出算法执行过程中的几个显而易见的事实:1、调用get(key)方法时,要返回该key对应的val。对于这个需求,我们可以使用一个Map来保存Key-Value的映射关系,可以使get()方法是O(1)的时间复杂度。2、只要用get或者put方法访问某个key一次,该key的freq就要加1。对于这个需求,还是可以使用一个Map来保存Key-Freq的映射关系3、如果在容量满了的时候进行插入,则需要将freq最小的key删除,如果最小的freq对应多个key,则删除其中最旧(加入时间最长)的那一个数据。这种情况比较复杂,也是实现LFU的核心,我们分开说:3.1为了通过freq找到需要删除的key,我们首先需要一个Map来维护freq到key的映射关系;3.2将freq最小的key删除,那你就得快速得到当前所有key最小的freq是多少。想要时间复杂度O(1)的话,肯定不能遍历一遍去找,那就用一个变量minFreq来记录当前最小的freq吧。3.3可能有多个key拥有相同的freq,所以freq对key是一对多的关系,即一个freq对应一个key的列表。3.4希望freq对应的key的列表是存在时序的,便于快速查找并删除最旧的key。3.5希望能够快速删除key列表中的任何一个key,因为如果频次为freq的某个key被访问,那么它的频次就会变成freq+1,就应该从freq对应的key列表中删除,加到freq+1对应的key的列表中。综合各种情况,我们可以使用Map<Integer,LinkedHashSet>来解决问题,LinkedHashSet顾名思义,是链表和哈希集合的结合体。链表不能快速访问链表节点,但是插入元素具有时序;哈希集合中的元素无序,但是可以对元素进行快速的访问和删除。那么,它俩结合起来就兼具了哈希集合和链表的特性,既可以在O(1)时间内访问或删除其中的元素,又可以保持插入的时序。综上,我们可以大致写一下LRU算法大致算法框架:接下来就是实现get和put两个核心功能,get方法的执行逻辑非常简单,就是直接从K-V哈希表中获取键位key的元素即可,如果有就返回Value并同时更新对应key的访问频次即可;否则返回null,结束。updateFreq方法更新某个key的freq肯定会涉及FK表和KF表,我们分别更新这两个表就行了。和之前类似,当FK表中freq对应的列表被删空后,需要删除FK表中freq这个映射。如果这个freq恰好是minFreq,说明minFreq变量需要更新。能不能快速找到当前的minFreq呢?这里是可以的,因为我们刚才把key的freq加了1嘛,所以minFreq也加1就行了。下面来实现put(key,val)方法,逻辑略微复杂,我们直接画个图来看:removeMinFreqKey方法中删除某个键key肯定是要同时修改三个映射表的,借助minFreq参数可以从FK表中找到freq最小的keyList,根据时序,其中第一个元素就是要被淘汰的deletedKey,操作三个映射表删除这个key即可。但是有个细节问题,如果keyList中只有一个元素,那么删除之后minFreq对应的key列表就为空了,也就是minFreq变量需要被更新。如何计算当前的minFreq是多少呢?实际上没办法快速计算minFreq,只能线性遍历FK表或者KF表来计算,这样肯定不能保证O(1)的时间复杂度。但是,其实这里没必要更新minFreq变量,因为你想想removeMinFreqKey这个函数是在什么时候调用?在put方法中插入新key时可能调用。而你回头看put的代码,插入新key时一定会把minFreq更新成1,所以说即便这里minFreq变了,我们也不需要管它。3、总结终于总结完了,其实,感觉思想搞明白了,代码实现起来就相对容易一些。但是,还是需要多写,多实践。过段时间再来回顾一下下面将源码贴出来:github:https://github.com/LoverITer/leetcode/blob/master/lru/src/main/java/cache/lfu/LFUCache.java完整代码:参考【1】labuladon.算法题就像搭乐高:手把手带你拆解LFU算法.微信公众号
LoveIT 2020-09-01操作系统 -
从输入URL到页面加载完成期间经历了什么?
作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等。本文将更深入的研究当你输入一个网址的时候,后台到底发生了一件件什么样的事~整个过程大致的的流程是:在浏览器地址栏输入某个网站的URL浏览器通过DNS服务,查询域名的IP地址(浏览器缓存-->操作系统hosts缓存--->路由缓存—>本地域名服务器—>根域名服务器—>顶级域名服务器—>一级域名服务器...,注意图中箭头的含义是上一步查询失败后才会继续下一步,如果上一步直接查询到了,那就直接返回了)发起TCP三次握手,建立连接TCP建立成功后浏览器向服务器发送HTTP请求服务器响应HTTP请求,返回HTML页面断开TCP连接(四次挥手)浏览器解析HTML代码,请求js,css等资源,最后进行页面渲染,呈现给用户1、在浏览器地址栏输入某个网站的URL在一个网络中,不同计算机拥有的ip地址都是唯一的。提供网页的服务器也是一台计算机,所以同样拥有唯一的一个ip。比如百度的某一台服务器ip为111.13.101.208,你完全可以通过111.13.101.208:80去访问百度的首页。但如果你还想访问腾讯、淘宝等其他网站呢?显然以ip方式去访问一个网站是很费劲的。就像我们说天安门在哪的时候不会说天安门在经纬多少多少度一样,而是以人话说在北京哪哪哪。所以,我们同样以别名的方式去记住每一个网站。而这就是DNS服务器干的活。我们在浏览器输入的是一个,人能够轻松记住的域名网址。当我们回车的时候DNS服务器就会去找该域名网址在网络中对应的ip地址,称之为解析域名。2、浏览器通过DNS服务,查询域名的IP地址导航的第一步是通过访问的域名找出其IP地址。DNS查找过程如下:浏览器缓存–浏览器会缓存DNS记录一段时间。有趣的是,操作系统没有告诉浏览器储存DNS记录的时间,这样不同浏览器会储存个自固定的一个时间(2分钟到30分钟不等)。系统缓存–如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用(windows里是gethostbyname),主要去查找了系统中的hosts文件。这样便可获得系统缓存中的记录。路由器缓存–接着,前面的查询请求发向路由器,查看路由器映射表,它一般会有自己的DNS缓存。本地DNS服务器(提供本地连接的服务商)–接下来要检查的就是ISP缓存DNS的服务器。在这一般都能找到相应的缓存记录。递归查询或迭代查询–DNS在解析域名的时候有两种方式:递归查询和迭代查询。本地DNS服务器找不到会将域名发送给其他服务器,进行递归过程或迭代过程,首先会发送到根域名服务器去找,返回顶级域名服务器的IP地址,再请求顶级域名服务器IP返回二级域名服务器IP,再请求二级域名服务器IP返回三级域名服务器IP......直到找到对应的IP地址,返回给浏览器。递归查询是以本地DNS服务器为中心的,是DNS客户端和服务器之间的查询活动,递归查询的过程中“查询的递交者”一直在更替,其结果是直接告诉DNS客户端需要查询的网站目标IP地址;迭代查询则是DNS客户端自己为中心的,是各个服务器和服务器之间的查询活动,迭代查询的过程中“查询的递交者”一直没变化,其结果是间接告诉DNS客户端另一个DNS服务器的地址。DNS递归查询或迭代查询如下图所示:扩展阅读:什么是DNS劫持?什么是301重定向?与301重定向设置教程电脑上不了网将DNS改为114.114.114.114或8.8.8.8可以解决或加快网速的原理是什么?局域网IP和公网IP有何差别?根域名服务器的作用是什么?全球13组根域名服务器中有10组在美国,意味着什么?递归和迭代的区别?3、浏览器发起TCP三次握手,建立TCP连接拿到IP地址后的浏览器很开心,终于可以有目的的去联系远方的“朋友”了,此时作用于传输层的TCP协议向远端服务器发起连接请求,此举称为三次握手:(1)客户端->服务器:你好,我想跟你连接可以吗?(SYN=1,seq=x)(2)服务器->客户端:可以,你确定要连接是吧?(SYN=1,ACK=1,ack=x+1,seq=y)(3)客户端->服务器:确定,我们连接吧!(ACK=1,ack=y+1,seq=x+1)4、TCP建立成功后浏览器向服务器发送HTTP请求OK,连接上了,传输吧,这时就需要将用户输入的地址封装成HTTPRequest请求报文,发送到服务器,服务器收到请求后会发出应答,即响应数据。HTTP请求报文格式:请求行+请求头+空行+消息体,请求行包括请求方式(GET/POST/DELETE/PUT)、请求资源路径(URL)、HTTP版本号;使用Wireshark抓包分析:5、服务器响应HTTP请求,返回HTML页面HTTP响应报文格式:状态行+响应头+空行+消息体,状态行包括HTTP版本号、状态码、状态说明。使用Wireshark抓包分析:6、断开TCP连接(四次挥手)传也传完了,那咱们断开连接吧!(1)客户端->服务器:好了,咱们断开吧(FIN=1,seq=u)(2)服务器->客户端:行,等我稍微检查一下还有没有要发你的数据(ACK=1,ack=u+1,seq=v)(3)服务器->客户端:可以了,咱们断开吧,拜拜(FIN=1,ACK=1,ack=u+1,seq=w)(4)客户端->服务器:好的,再会,拜拜(ACK=1,ack=w+1,seq=u+1)7、浏览器解析HTML代码,请求js,css等资源,最后进行页面渲染,呈现给用户浏览器获取文件后开始利用内核解析了,解析过程中也会出现一些HTTP请求请求一些资源,如js,css等文件,将这些文件下载到本地。浏览器解析HTML文件时会自上而下,起初产生一个DOM树,解析CSS之后产生CSS规树,后将两树进行融合,合成为渲染层,最后调用操作系统的NativeGUI的API绘制。浏览器是多进程的,浏览器能够运行是因为系统给它的进程分配了资源,每打开一个Tab页就相当于创建了一个独立的浏览器进程。浏览器主要包含以下进程:(1)浏览器主进程,负责协调主控,只有一个负责浏览器页面显示,用户交互,如前进后退等负责各个页面的管理、创建和销毁进程网络资源的管理下载等(2)第三方插件进程,每种类型插件对应一个进程,使用时才创建(3)GPU进程,最多一个,用于3D绘制等(4)浏览器渲染进程(浏览器内核),内部是多线程的,主要用于页面渲染、脚本执行、事件处理等重点介绍浏览器内核即渲染进程,页面的渲染、JS的执行、事件的循环都在这个进程内进行。浏览器内核是多线程的,主要包含以下线程:1、GUI渲染线程负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结),GUI更新会被保存到一个队列中等JS引擎空闲时立即被执行。2、JS引擎线程(JS内核)负责处理JavaScript脚本程序JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页上只有一个JS线程在运行JS程序JS引擎线程与GUI渲染线程是互斥的,因此如果JS执行时间过长会造成页面渲染不连贯,导致页面渲染加载阻塞。3、事件触发线程用来控制事件循环当JS引擎执行代码块如setTimeOut时(也可能来自浏览器内核的其它线程,如鼠标点击、ajax异步请求等),会将对应任务添加到事件线程中符合条件的事件出发时,该线程会把事件添加到待处理的队列队尾,等待JS引擎处理4、定时触发器线程5、异步http请求线程JS运行机制分析JS分为同步任务和异步任务同步任务都在主线程上执行,形成一个执行栈主线程外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果就在任务队列中放置一个事件执行栈中的所有同步任务执行完毕,系统会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。参考【1】从输入URL到页面加载完成期间经历了什么?(总览篇)【2】浏览器工作原理:从URL输入到页面展现到底发生了什么?
LoveIT 2020-08-29计算机网络 -
十大经典排序算法(动图演示)
0、算法概述0.1算法分类十种常见排序算法可以分为两大类:比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。0.2相关概念稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面。时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。0.3算法复杂度分析1、冒泡排序(BubbleSort)冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。1.1算法描述比较相邻的元素。如果第一个比第二个大,就交换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;针对所有的元素重复以上的步骤,除了最后一个;重复步骤1~3,直到排序完成。1.2动图演示1.3代码实现冒泡排序的优化当我们的数字序列基本有序的时候往往只需要扫描几次就可以排序好了,当数字序列有序了之后在重复扫描那就是在做无用功,因此我们需要优化一下代码的实现。1.4算法分析最好情况下,待排序序列已经有序,只需要进行一次排序,关键字需要n-1次比较,不需要移动记录。最坏情况下,待排序记录刚好是逆序的,此时,每一趟冒泡嘘排序需要n-i次比较,3i次移动。经过n-1趟排序后,总的比较次数为∑(n-i)=n(n-1)/2其中1<=i<=n-1,总的移动次数为3n(n-1)/2次,因此该算法的时间复杂度为O(n^2),空间复杂度是O(1)。另外它是一种稳定的排序算法。2、选择排序(SelectionSort)选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。2.1算法描述n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:初始状态:无序区为R[1..n],有序区为空;第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;n-1趟结束,数组有序化了。2.2动图演示2.3代码实现2.4算法分析表现最稳定的排序算法之一,因为无论什么数据进去都是O(n^2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。3、插入排序(InsertionSort)插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。3.1算法描述一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:从第一个元素开始,该元素可以认为已经被排序;取出下一个元素,在已经排序的元素序列中从后向前扫描;如果该元素(已排序)大于新元素,将该元素移到下一位置;重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;将新元素插入到该位置后;重复步骤2~5。3.2动图演示3.2代码实现优化上面的算法在寻找合适的插入位置的时候使用的是顺序查找,非常的耗费时间,既然前面的数字序列是已经排序好的,那么我们可以思考可以用二分查找优化寻找合适插入位置的逻辑。3.4算法分析插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。插入排序在小数据量或数据基本有序的情况下非常高效,但是数据量一大就基本不那么好使了,基于此Shell排序应运而生。4、希尔排序(ShellSort)1959年Shell发明,第一个突破O(n^2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。4.1算法描述先将整个待排序的记录序列分割成为若干子序列(分割成小数据量)分别进行直接插入排序,具体算法描述:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;按增量序列个数k,对序列进行k趟排序;每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。4.2动图演示4.3代码实现4.4算法分析希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者RobertSedgewick提出的。5、归并排序(MergeSort)归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(DivideandConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。5.1算法描述把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。5.2动图演示5.3代码实现5.4算法分析归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。6、快速排序(QuickSort)快速排序是对冒泡排序的一种改进,快速排序的基本思想是:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。6.1算法描述快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:从数列中挑出一个元素,称为“基准”(pivot);重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。6.2动图演示6.3代码实现7、堆排序(HeapSort)堆排序(Heapsort)是利用堆这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:大顶堆:arr[i]>=arr[2i+1]&&arr[i]>=arr[2i+2]小顶堆:arr[i]<=arr[2i+1]&&arr[i]<=arr[2i+2]一般升序排序使用大顶堆,降序排序使用小顶堆。ok,了解了这些定义。接下来,我们来看看堆排序的基本思想及基本步骤:7.1算法描述以升序排序为例,算法思路如下:将待排序的序列构造成一个初始大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素将堆顶根节点元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。7.2动图演示7.3代码实现7.4算法分析堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。8、计数排序(CountingSort)计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。8.1算法描述找出待排序的数组中最大和最小的元素;统计数组中每个值为i的元素出现的次数,存入数组C的第i项;对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。8.2动图演示8.3代码实现优化8.4算法分析计数排序是一个稳定的排序算法。当输入的元素是n个0到k之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。9、桶排序(BucketSort)桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序(Bucketsort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。9.1算法描述设置一个定量的数组当作空桶;遍历输入数据,并且把数据一个一个放到对应的桶里去;对每个不是空的桶进行排序;从不是空的桶里把排好序的数据拼接起来。9.2图片演示9.3代码实现9.4算法分析平均时间复杂度:O(n+k)最佳时间复杂度:O(n+k)最差时间复杂度:O(n^2)空间复杂度:O(n*k)稳定性:稳定10、基数排序(RadixSort)基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。10.1算法描述取得数组中的最大数,并取得位数;arr为原始数组,从最低位开始取每个位组成radix数组;对radix进行计数排序(利用计数排序适用于小范围数的特点);10.2动图演示10.3代码实现10.4算法分析基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n),当然d要远远小于n,因此基本上还是线性级别的。基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。参考资料【1】十大经典排序算法(动图演示)【2】图解排序算法(三)之堆排序
LoveIT 2020-08-01数据结构与算法 -
面试前你必须知道的HTTP协议知识点(附常见GET、POST面试问题)
1、什么是HTTP协议?HTTP协议是HyperTextTransferProtocol(超文本传输协议)的简称,是用于从万维网(WWW)传输超文本到本地浏览器的传输协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。HTTP是一个应用层协议,由请求和响应组成,是一个标准的客户端服务器架构(C/S)的协议。HTTP还是一个无状态协议。HTTP协议工作于客户端-服务端架构之上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送请求。Web服务器根据接收到的请求后,向客户端发送响应信息。下图所示是HTTP的请求-响应模型示意图。HTTP的请求-响应模型限制了服务器无法在客户端没有请求的时候主动推送消息给客户端。并且HTTP协议是一个无状态协议,即同一个客户端上一次请求和下一次请求之间没有任何关联。HTTP基于TCP通信协议来传递数据(HTML文件,图片文件,查询结果等)。安全版本的HTTPS基于TLS或SSLHTTP默认端口80,HTTPS默认端口443。2、HTTP协议工作过程一次HTTP操作工作过程可分为四步:(1)客户端的浏览器与服务器建立连接。只要单击某个超级链接,HTTP的工作开始。(2)连接建立之后,客户端首先给服务器发送一个请求。请求的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容(3)服务器接收到请求之后给与响应。响应消息的格式为:一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。(4)客户单接收到服务器返回的消息并通过浏览器显示在屏幕上,然后客户机与服务器断开连接。如果在以上过程中的某一步出现错误,那么产生错误的信息将返回到客户端,由显示屏输出。对于用户来说,这些过程是由HTTP自己完成的,用户只要用鼠标点击,等待信息显示就可以了。3、HTTP协议状态响应码一般无论此次请求是否成功响应,服务器都会返回一个状态响应码,HTTP协议中定义了诸多状态码,按照原因可以划分为5大类,如下图所示。常见状态码含义:200(OK):服务器成功处理了请求。204(NOTContent):服务器成功处理了请求,但没有返回任何内容301(MovedPermanently):永久性重定向。表示请求的资源已被分配了新的URI。服务器返回此响应(对GET或HEAD请求的响应)时,会自动将请求者转到新位置302(Found):临时性重定向。表示请求的资源已被分配了新的URI,希望用户(本次)能使用新的URI访问。和301MovedPermanently状态码相似,但302Found状态码代表资源不是被永久移动,只是临时性质的。换句话说,已移动的资源对应的URI将来还有可能发生改变。304(NotModified):表示客户端发送附带条件的请求时,服务器端允许请求访问的资源,但未满足条件的情况。400(BadRequest):表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。401(Unauthorized):请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。403(Forbidden):服务器拒绝请求404(NotFound):表明服务器上无法找到请求的资源。405(MethodNotAllowed):请求的资源不支持请求使用的方法414(Request-URITooLarge):请求参数参数过长500(InternalServerError):服务器内部错误,服务器在处理请求的时候出现错误。501(NotImplemented):服务器不认识或不支持的请求使用的方法502(BadGateway):错误网关503(ServiceUnavailable):服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。504(GatewayTimeout):网关超时,由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。505(HTTPVersionNotSupported):服务器不支持请求中所指明的HTTP版本。4、HTTP协议消息结构(1)客户端请求消息客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(requestline)、请求头(requestheader)、空行(\r\n)和请求数据。使用Wireshark抓包分析:(2)服务器响应消息HTTP响应数据也有4个部分:状态行、消息头部、空行(\r\n)和响应正文。使用Wireshark抓包分析:5、HTTP请求方法HTTP协议中共定义了8种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式,具体介绍如下:HTTP/1.0定义了三种请求方法:GET、POST和HEADHTTP/1.1在新增了五种请求方法:DELETE、PUT、OPTIONS、TRACE和CONNECT八种方法具体含义如下:序号方法描述1GET向特定的资源发出请求。请求参数数据都在url中2HEAD类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头3POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件),请求数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。4PUT向指定资源位置上传其最新内容5DELETE请求服务器删除指定的内容。6CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。7OPTIONS返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性8TRACE回显服务器收到的请求,主要用于测试或诊断。注意:1)方法名称是区分大小写的,当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(MothodNotAllowed);当服务器不认识或者不支持对应的请求方法时,应返回状态码501(NotImplemented)。2)HTTP服务器至少应该实现GET和HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。6、GET、POST的区别?打开浏览器,我们输入“GET、POST的区别”查询出来的答案大致如下:(具体参考W3CSchool:HTTP方法:GET对比POST):GET请求的URL长度是有限制的(不同的浏览器和服务器限制不同),但是POST请求没有限制;GET请求比POST请求更安全,因为GET请求的参数就在URL中,当时POST请求的请求参数在请求体中;对参数的数据类型,GET只接受ASCII字符,而POST没有限制;GET请求参数会被完整保留在浏览器历史记录里;相反,POST请求参数不会被浏览器保留;GET请求只能进行url编码(application/x-www-form-urlencoded),而POST支持多种编码方式;GET请求会被浏览器主动缓存,而POST不会,除非手动设置;GET在浏览器回退时是无害的,而POST会再次提交请求。除了上面在W3CSchool这篇文章中提到的区别,在网上还有一种区别也来总结一下:GET产生一个TCP数据包;POST产生两个TCP数据包。具体说就是:对于GET方式的请求,浏览器会把httpheader和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100continue,浏览器再发送data,服务器响应200ok(返回数据)。7、GET请求有ResuqestBody吗?POST请求可以把请求参数写在URL里吗?先给出答案:GET请求有请求体并且可以将请求参数放到请求体中;POST请求也可以将请求参数写到URL上;其实GET和POST在本质上没有区别,都是HTTP协议中的两种发送请求的方法。而HTTP呢,是基于TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上requestbody,给POST带上url参数,技术上是完全行的通的。HTTP只是个行为准则,而GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。8、URL中传送参数的长度在Get和Post中的限制这一块其实不是HTTP协议本身的限制,而是不同厂家实现浏览器或服务器的限制,原因很简单:数据量太大对浏览器和服务器都是很大负担。业界不成文的规定是,大多数浏览器通常都会限制url长度在2K~8K字节,而大多数服务器最多处理64K大小的url。超过的部分,恕不处理。如果你用GET服务,在requestbody偷偷藏了数据,不同服务器的处理方式也是不同的,有些服务器会帮你卸货,读出数据,有些服务器直接忽略。所以,虽然GET可以带requestbody,却不能保证一定能被接收到。9、POST方法比GET方法更安全?从表面上看,GET请求的参数直接在URL中,在地址栏中可以直接看得见,POST的请求参数在请求体中,似乎更安全。从传输角度看,他们都是不安全的,因为HTTP在网络上是明文传输的,只要在网络节点上抓包,就能完整地获取数据报文。因此想要安全传输,就只有加密,也就是HTTPS。现在,当面试官再问你“GET与POST的区别”的时候,你的内心是不是这样的?参考资料[1]听我讲完GET、POST原理,面试官给我倒了杯卡布奇诺[2]GET请求中URL的最大长度限制总结[3]get和post请求有哪些区别?[4]菜鸟教程:HTTP教程
LoveIT 2020-07-18计算机网络 -
LRU缓存算法到底是怎么一回事?
1、LRU算法是什么?LRU:LeastRecentlyUsed,即最近最久未使用的意思。LRU算法是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。该算法是计算机操作系统中置换页的一种算法,同时在其他领域也有广泛应用,比如Redis的内存淘汰策略,该算法也是面试中面试官常常用来考验面试者代码能力和对LRU算法的正确理解。2、实现LRU算法理论上,LRU有以下三种实现方式:(1)用一个数组来保存数据,并给每一个数据标记一个访问时间戳。每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。(2)利用一个链表来实现。每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。(3)利用双向链表和哈希表。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部并返回值;如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。对于第一种方法,需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。接下来我就使用HashMap+双链表的方式手撕一个时间复杂度为O(1)的LRU算法。Tips:在jdk中,LinkedHashMap其实已经实现了LRU缓存淘汰算法需要在构造方法第三个参数传入true(accessOrder=true;),表示按照时间顺序访问。可以直接继承LinkedHashMap来实现。LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。下图所示是一个基于LinkedHashMap实现LRU的实例:手撕LRU算法思路:使用HashMap保存每个数据项的Key,通过Key就能以O(1)的时间得到节点;访问某个结点的时候就将其从原来位置删除,并重新插入到链表头部。这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就淘汰链表尾部的节点。由于使用HashMap保存了Key到节点的映射,因此通过Key就能以O(1)的时间得到节点,然后双链表可以保证以O(1)的时间将其从链表中删除(不用单链表的原因)。Java实现如下:(1)定义基本的链表操作节点(2)双向链表定义我们定义一个LRUCache类,然后定义它的容量、头节点、尾节点以及用一个HashMap来映射key和各个结点,然后一个基本的构造方法初始化。接下来就是几个操作链表结点的方法:删除:remove()、添加:put()、获取:get()、移动到头部:moveToHead()。(3)添加元素添加元素的时候首先判断是不是新的元素,如果是新元素,判断当前的大小是不是大于总容量了,防止超过总链表大小,如果大于的话直接抛弃最后一个节点,然后再以传入的key\value值创建新的节点。对于已经存在的元素,直接覆盖旧值,再将该元素移动到头部,然后保存在map中(4)访问元素通过key值来访问元素,主要的做法就是先从Map中根据给定的key判断如果是不存在的,直接返回null。如果存在,把数据移动到首部头节点,然后再返回旧值。(5)节点删除操作在根据key删除节点的操作中,我们需要做的是把节点的前一个节点的指针指向当前节点下一个位置,再把当前节点的下一个的节点的上一个指向当前节点的前一个,这么说有点绕,我们来画图来看:(6)移动元素到头节点首先把当前节点移除,类似于删除的效果(但是没有移除该元素),然后再将首节点设为当前节点的下一个,再把当前节点设为头节点的前一个节点。当前几点设为首节点。再把首节点的前一个节点设为null,这样就是间接替换了头节点为当前节点。3、测试代码写完了,我们来测试一下结果:执行结果:可以看到,越是后添加的结点也是靠近链表头部,越是靠近尾部的元素越是优先被淘汰,这符合LRU算法的思想。4、总结本文主要讲述了LRU的算法实现,理解了LRU也能帮助我们理解Java中的LinkedList,因为LinkedList本身就是双向链表。还有就是理解数据结构这种方式,以及LRU的移动节点的过程,如果能在实际的开发中利用它的特性使用到合适的业务场景中。附:LRU算法完整代码
LoveIT 2020-07-07操作系统 -
Linux之Socket编程详解
一、网络中进程之间如何通信进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如:UNIXBSD有:管道(pipe)、命名管道(namedpipe)软中断信号(signal)UNIXsystemV有:消息(message)、共享存储区(sharedmemory)和信号量(semaphore)等.他们都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(processID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。使用TCP/IP协议的应用程序通常采用应用编程接口:UNIXBSD的套接字(socket)和UNIXSystemV的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。二、什么是socket1、socket套接字socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open–>读写write/read–>关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。2、套接字描述符套接字描述符和文件描述符的概念类似,都是用于标识打开的文件的,只不过这里特殊点,是用来标识建立的socket。它是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE*结构的表示就是stdin、stdout、stderr三、Linux内核提供的socket编程系统调用1、socket()函数原型:函数执行后返回sockfd,这是一个文件描述符。socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而**socket()**用于创建一个socket描述符(socketdescriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。创建socket的时候,可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:(1)domain:即协议域,又称为协议族(family)。常用的协议族有:AF_INET:表示使用IPV4AF_INET6:表示使用IPV6AF_LOCAL(或称AF_UNIX,Unix域socket):表示使用绝对路径名作为地址AF_ROUTE:协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。(2)type:指定socket类型。常用的socket类型有:SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET(3)protocol:故名思意,就是指定协议。常用的协议有:IPPROTO_TCP:使用TCP传输协议IPPTOTO_UDP:使用UDP传输协议IPPROTO_SCTP:使用STCP传输协议IPPROTO_TIPC:使用TIPC传输协议当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(addressfamily,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。2、bind()函数原型:bind()函数把一个地址族中的特定地址绑定到一个socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合绑定到socket。函数的三个参数分别为:(1)sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。(2)addr:一个结构体指针,用于指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。这里如果想在Linux下实现socket编程的话,需要对ipv4或ipv6的结构非常了解才可以。ipV4的结构:ipv6的结构:(3)addrLen:对应的是地址的长度。通常服务器在启动的时候都会绑定一个地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。主机字节序和网络字节序的转换主机字节序:就是我们平常说的大端和小端模式,不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。网络字节序:4个字节的32bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。**由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。**字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。所以,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。解决方法使用htons()将短整型的主机字节序转化为网络字节序、htonl()将长整型的主机字节序转化为网络字节序使用ntohs()将短整型的网络字节序转化为主机字节序、ntohl()将长整型的网络字节序转化为主机字节序3、listen()、connect()当服务器在调用socket()创建并使用bind()绑定ip和端口号到一个socket通道之后,接下来就需要监听客户端的连接请求了,Linux为我们提供了listen()系统调用,他的函数原型如下:函数的第一个参数即为要监听的socket的文件描述符(可以通过socket()调用获得),第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。在主句准备好并处于监听之中时,客户端就可以使用connect()来连接服务器,他的函数原型如下:函数的第一个参数即为客户端的socket的文件描述符,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。4、accept()TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。他的函数原型如下:他有3个参数:(1)sockfd:参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。(2)addr:这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。(3)addrlen:用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。如果accept成功返回(返回0),则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。注意!accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。此时我们需要区分两种套接字:监听套接字和连接套接字监听套接字:监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器开始调用socket()函数生成的,称为监听socket描述字(监听套接字)连接套接字:一个套接字会从主动连接的套接字变身为一个监听套接字;而accept函数返回的是已连接socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。5、read()、write()等函数至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!Linux提供的网络I/O操作有下面几组:read()/write()recv()/send()readv()/writev()recvmsg()/sendmsg()recvfrom()/sendto()它们的声明分别如下:(1)read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。(2)write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。6、close()在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。关闭一个TCPsocket的缺省行为是把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。四、Linux下socket编程示例服务器端:启动后监听9000端口,如果收到连接请求,将接收请求并接收客户端发来的消息,并向客户端返回消息。客户端:连接服务器,并向服务器发送消息测试:首先编译源文件,为了方便管理我这里编写一个Makefile执行Makefile编译生成可执行性文件之后首先启动server:之后在另一个窗口启动客户端并向服务器发送消息,服务器也把消息返送回来:转载来自:https://www.cnblogs.com/jiangzhaowei/p/8261174.html
LoveIT 2020-06-06Linux -
深入理解TCP三次握手与四次挥手
一、TCP首部结构详解TCP数据封装在一个IP数据报中,下图是TCP报文数据格式。TCP首部如果不计选项和填充字段,它通常是20个字节。其中比较重要的字段:(0)源端口和目的端口:各自占用2字节,16位,可以表示的最大端口号是2^16=65535,这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。有时一个IP地址和一个端口号也称为socket(插口)(1)序列号seq:占4字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。(2)确认号ack:占4字节,表示期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。(3)标志位,共6个:ACK、SYN、FIN、URG、PSH、RST。重点了解前3个标志ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放连接URG:当URG=1时,注解此报文应尽快传送,而不要按本来的列队次序来传送。与“紧急指针”字段共同应用,紧急指针指出在本报文段中的紧急数据的最后一个字节的序号,使接管方可以知道紧急数据共有多长;PSH:当PSH=1时,接收方应该尽快将本报文段立即传送给其应用层。RST:当RST=1时,表示出现连接错误,必须释放连接,然后再重建传输连接。复位比特还用来拒绝一个不法的报文段或拒绝打开一个连接;(4)窗口:TCP通过滑动窗口的概念来进行流量控制,可以理解成接收端所能提供的缓冲区大小。TCP利用一个滑动的窗口来告诉发送端对它所发送的数据能提供多大的缓冲区。窗口大小为字节数,起始于确认序号字段指明的值(这个值是接收端正期望接收的字节)。窗口大小是一个16bit字段,因而窗口大小最大为65535字节。(5)校验和:检验和覆盖了整个TCP报文段:TCP首部和数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。(6)紧急指针:只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。二、TCP建立socket通道的过程(TCP三次握手的过程)TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-wayhandshake),过程如下图所示。注:握手之前,主动打开的客户端结束CLOSED状态,被动打开的服务器进入到监听(LISTEN)状态第一次握手:建立连接,客户端向服务器发送一个SYN数据包(syn=x,随机值),之后客户端进入SYN_SEND(同步已发送)状态,等待服务器确认;第二次握手:服务器收到客户端的SYN数据包,读取发现SYN=1,表示客户端要求建立连接,于是服务器向客户端发送SYN+ACK数据包:SYN=1,ACK=1,ack=x+1,seq=y(随机值)表示"确认客户端的序列号有效,服务器能正常接收客户端的消息,并同意创建连接"。之后服务器进入SYN_RECVD(同步收到)状态。第三次握手:客户端收到服务器的SYN+ACK数据包之后,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器就进入到EATABLISHED(连接建立)状态,TCP三次握手成功,之后就可以传输数据了。三次握手动态过程演示:三、TCP连接终止(TCP四次挥手,Four-WayWavehand)建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的,如图:由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN标志来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。客户端发送一个FIN,用来关闭和客户端到服务器的数据传输。此过程发送的报文是:FIN=1,seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),发送之后客户端进入FIN-WAIT-1(终止等待1)状态。服务器收到连接释放的报文之后,返回确认报文,ACK=1,ack=u+1,并带上自己的序列号seq=v(随机值),此后服务器就进入到CLOSE-WAIT(关闭等待)状态。(此时客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文FIN,不过在这之前服务器发来的细小客户端还是可以正常接收的。终于,服务器发送完数据之后,它向客户端发送一个连接释放报文:FIN=1,ACK=1,ack=u+1,发送完之后服务器处于LAST-WAIT(最后确认)状态。客户端收到服务器的连接释放报文后,发出确认:ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。一般服务器结束TCP连接的时间要比客户端早一些。四、关于TCP三次握手以及四次挥手过程的一些思考1、为什么建立连接是三次握手,但是关闭连接需要四次挥手?因为在建立连接的时候,当客户端向服务器发送SYN数据包,请求建立连接被服务器接收到之后,服务器可以立即发送SYN+ACK报文同意并证明自己可以正常收发数据了(SYN用于同步,ACK用于确认应答,表示同意建立连接)。但是当关闭连接的时候,当客户端向服务器发送FIN报文之后,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,"你发的FIN报文我收到了"。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。说的通俗点就是:一方发送FIN只表示自己发完了数据,但是对方有可能还需要发送数据,此时另一方就不能立即回应FIN,而是需要等到把数据发完了再回应FIN。建立连接的过程不存在这些问题,只要可以正常收到数据包并作出正确回应,就可以立即建立连接。2、建立连接为什么需要三次握手?两次或四次不行吗?先说明为什么不能两次?TCP有一个超时重传机制,如果客户端发出SYN包之后,由于网络原因,服务器没能立即响应SYN+ACK包,那么client会再次发起syn包,这一点,已经有过多次实验。如果第二次syn包正常达到且与server端建立了tcp连接,server端维护了一个连接,一次貌似OK,但别忘了,第一次那个syn包可能就在此时达到server端了,于是server端又要维护一个连接,而这个连接是无效的,可以认为是死连接。而一个进程打开的socket是有限度的,维护这些死连接非常耗费资源。所以,二次握手,服务端有较大隐患,容易因为资源耗尽而崩溃。而三次握手既可以解决这个问题,经过三次握手客户端和服务器双方都可以确认对方具有收发数据的能力,从而不会存在无效连接。为什么不采用四次、五次或更多次?这个问题就更简单了,三次握手就可以确认客户端和服务器双方都可以确认对方具有收发数据的能力了,再增加握手,并不能显著提高可靠性,而且也没有必要。3、在关闭阶段,为什么客户端要经历一个2MSL的TIME-WAIT阶段?虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是当网络不可靠的时候,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。具体的工作原理就是:客户端会在发送出ACK之后进入到TIME_WAIT状态。客户端会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么客户端会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(MaximumSegmentLifetime)。MSL指一个报文片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。4、如果已经建立了连接,但是客户端突然出现故障了怎么办?TCP设值有一个保活计时器(显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源)。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75s发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
LoveIT 2020-06-06计算机网络 -
透过代理模式探究JAVA的静态代理和动态代理
一、代理代理是英文Proxy翻译过来的。我们在生活中见到过的最常见的代理大概就是顾客和商家的关系了按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。所以,代理就有一种中间人的味道。接下来,我们说说软件中的代理模式。二、代理模式代理模式是面向对象中比较常见的一种设计模式了。这是代理模式常见的UML图,代理模式可以为其他对象提供一种代理以控制对这个对象的访问,比如像对一个类的访问做一些控制或加强的时候都可以用到代理模式(AOP就是基于此发展而来的)。对于代理模式的理解需要注意的有下面几点:(1)用户只关心接口功能,而不在乎谁提供了功能。上图中接口是Subject。(2)接口真正实现者是上图的RealSubject,但是它不与用户直接接触,而是通过代理。(3)代理就是上图中的Proxy,由于它实现了Subject接口,所以它能够直接与用户接触。(4)用户调用Proxy的时候,Proxy内部调用了RealSubject。所以,Proxy是中介者,它可以增强\控制RealSubject操作。光说概念比较难以理解,下面我用示例来说明。值得注意的是,代理可以分为静态代理和动态代理两种。先从静态代理讲起。三、静态代理我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。现在用代码来进行模拟。首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为Movie,代表电影播放的能力。然后,我们要有一个真正的实现这个Movie接口的类,和一个只是实现接口的代理类。这个类实现了Movie接口的play方法,只要调用就可以播放,接下来还需要一个代理类来代理这个”电影“这里的MovieProxy就是一个代理类它有一个play()方法。不过调用play()方法时,它进行了一些相关利益的处理,那就是广告。现在,测试一下执行结果:从执行的结果可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。上面这种代理方式因为它的类型是我们程序员或接助工具事先预定好的,比如上面代码中的MovieProxy这个类。所以他被称为静态代理模式。下面要介绍的内容就是动态代理。四、动态代理在Java开发中,很多技术和框架都用到了动态代理,它与静态代理的区别就是动态与静态的区别:上一节代码中MovieProxy类是代理,需要程序员手动编写代码让MovieProxy实现Movie接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现Movie接口的代理,而不需要去定义MovieProxy这个类。这就是它被称为动态的原因。下面用一个例子来说明:假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。SellWine是一个买酒的就扣,实现它就可以买酒了,类似于许可证。之后比如我们要买茅台酒,那就实现SellWine获取许可之后按照静态代理就要写一个代理对象了,可是动态代理不需要了,在Java中我们可以使用Proxy.newProxyInstance()方法来动态的创建代理对象。之后客户端通过代理对象买酒就可以了执行结果:可以看到,我并没有像静态代理那样为SellWine接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。五、Java对动态代理的支持1、newProxyInstance方法Java中动态代理涉及到一个非常重要的类Proxy。正是通过Proxy的静态方法newProxyInstance才会动态创建代理。说一下它三个参数的含义:loader:类加载器interfaces:需要代理的接口h:代理类的具体实现,InvocationHandler是一个接口,需要有一个具体的实现类,在这个实现类中可以定义动态生成的代理类可以有哪些行为2、InvocationHandler接口InvocationHandler内部只是一个invoke()方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。proxy:代理对象method:代理对象调用的方法args:调用的方法中的参数在了解了基本的语法之后,我们加大难度,现在我扩大经营了,我还要买五粮液。Wuliangye这个类也实现了SellWine这个接口,说明它也拥有卖酒的许可证,同样把它放到柜台(Counter)上售卖。执行结果:可以看到,结果符合预期。使用动态代理扩展业务十分的快捷,这一切还得归功于Java的反射机制。到这里,脑子灵的同学就会产生好奇了,为什么一个newProxyInstance方法就可以实现动态生成代理对象?它在底层是不是主动帮我们new了一个对象?那么事实是不是这样子呢?直接查看它们的源码好了。(需要说明的是,我当前查看的源码是jdk1.8版本。)查找或生成指定的代理类:有上面的源码可知,在newProxyInstance方法中它确实为我们创建了一个代理类的对象,但是不是简单的new,而是通过反射创建了一个实例。另外,需要我们注意的是:它不会一上来就通过反射创建代理对象的实例,而是先在给定类加载器中检查,如果发现有实现了给定接口的代理类存在时,它就会简单地返回这个代理对象,否者通过ProxyClassFactory类创建代理类的实例。ProxyClassFactory是Proxy类的内部类这个类的注释可以了解到,它通过指定的ClassLoader和接口数组用工厂方法生成proxyclass。然后这个proxyclass的名字是:所以,动态生成的代理类名称是包名+$Proxy+id序号。生成的过程,核心代码如下:到这里,我们J只需要知道Java动态动态代理就是通过反射实现的以及动态代理生成的类名就是包名+$Proxy+id序号即可。下面我们来验证一下动态生成的代理类的名字是不是包名+$Proxy+id序号。执行结果:六、动态代理的应用动态代理的一个显著作用就是可以在不修改被代理对象的源码上,进行功能的增强。熟悉Spring的同学对这个观念应该不陌生,这就是AOP的作用,AOP就可以使用JDK的动态代理来实现(但是Spring采用了另一种方法,详情参考我的另一篇博客Spring从入门到精通—AOP与AspectJ的关系?原生JDK和CGLib手动实现AOP?)。因此,关于动态代理应用,一个比较重要的方向就是实现AOP编程,还有一个就是MyBatis通过动态代理生成Mapper接口的实现类......还有很多,总之凡是想在访问一个类时做一些控制都可以使用代理。总结代理分为静态代理和动态代理两种。静态代理,代理类需要自己编写代码写成。动态代理,代理类通过Proxy.newInstance()方法生成。不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。静态代理和动态代理的区别是在于要不要开发者自己定义Proxy类。动态代理通过Proxy动态生成proxyclass,但是它也指定了一个InvocationHandler的实现类。代理模式本质上的目的是为了增强现有代码的功能。转载来自:https://blog.csdn.net/briblue/article/details/73928350
LoveIT 2020-06-04设计模式 -
Linux sort、uniq、cut、wc命令详解
一、sort命令sort命令对指定的文件中的行排序,并将结果写到标准输出。如果指定多个文件,那么sort命令将这些文件连接起来,并当作一个文件进行排序。1、sort命令语法:常用可选OPTION:选项参数作用-f忽略大小写的差异,例如A与a视为相同-b忽略最前面的空格符部分-M以月份的名字来排序,例如JAN,DEC等等的排序方法-n使用『纯数字』进行排序(默认是以文字型态来排序的)-r反向排序-u就是uniq,相同的数据中,仅出现一行代表-t指定分隔符,默认使用Tab分隔-k以那个区间(field)来进行排序的意思2、sort基本用法示例对/ect/passwd中的账户排序sort默认对字符串排序的,排序安字符串每个字符在字典中出现的先后升序排列,因此在结果中我们结果由字母a开始升序排序。/etc/passwd的第三行都是数字,我想以第三行的数字对账户进行排序默认是升序排列,如果要降序排序,如下:以/etc/passwd的第7个字段进行排序并去重,以统计出有多少种Shell二、uniq命令uniq命令可以去除排序过的文件中的重复行,因此uniq经常和sort合用。也就是说,为了使uniq起作用,所有的重复行必须是相邻的。1、uniq命令语法常用可选OPTION:选项参数作用-i忽略大小写字符的不同-c进行计数-u仅显示出一次的行列-d仅显示重复出现的行列-w指定要比较的字符2、uniq命令基本用法示例uniq处理数据是按行处理的,只有一行都相同的时候才会合并,如果一行中只有一部分相同则会认为是不同的。在根目录下有一个文件uniq.txt,对这个文件排序并去重排序之后删除重复行,同时在行首位置输出该行重复的次排序并去重后仅显示重复的行排序后仅仅显示没有重复的行三、cut命令cut可以从一个文本或文本列中提取文本列cut命令语法:常用可选OPTION:选项参数作用-d后面接分隔字符。与-f一起使用-f依据-d的分隔字符将一段信息分割成为数段,用-f取出第几段的意思-c以字符(characters)的单位取出固定字符区间-b以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了-n标志-n取消分割多字节字符。仅和-b标志一起使用-d和-f参数配合使用类似可以达到类似awk命令的功能,例如下面这个例子,将系统环境变量PATH取出获得其中的任意一个路径将系统环境变量PATH取出获得其中第1~3个路径只显示/etc/passwd中的用户名和Shell类型四、wc命令利用wc指令我们可以计算文件的字节大小、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。wc命令语法:常用可选OPTION:选项参数作用-l只显示行数-w只显示字数-m只显示有多少字符-c只显示Bytes数wc命令的使用比较简单,默认使用wc不指定任何参数的时候会输出行数,单词数和字节数打印出三个数字,从左到右依次表示输入所包含的行数、单词数和字节大小当我们只想获得文件的某个方面的参数的时候可以指定对应的选项参数:
LoveIT 2020-05-22Linux -
Linux文本处理cat、head、tail、grep、sed和awk详解
Linux哲学思想中有一条就是Linux下一切皆文件。因此当我们操作Linux的时候,本质是在操作各种文件。因此掌握文本编辑的命令也是至关重要的,Linux提供了很多的文本编辑命令,以及被称为Linux"三剑客“的grep、sed和awk命令。一、cat命令cat命令(concatenate(连接、连续)的简写)用于连接文件并打印到标准输出设备上,也可以把几个文件内容附加到另一个文件中,即连接合并文件。cat命令的语法格式如下可用的选项:选项含义-A相当于-vET选项的整合,用于列出所有隐藏符号;-E列出每行结尾的回车符$;-n对输出的所有行进行编号;-b和-n参数作用类似,但此选项表示只对非空行进行编号。-T把Tab键^I显示出来;-v列出特殊字符;-s当遇到有连续2行以上的空白行时,就替换为1行的空白行。示例把a.txt中的内容加上行号复制一份到b.txt合并a.txt和b.txt中的内容,并不要空格加上行号之后追加到c.txt二、head命令head命令可以显示指定文件前若干行的文件内容,其基本格式如下:常用可用的选项:选项含义-nK这里的K表示行数,该选项用来显示文件前K行的内容;如果使用"-K"作为参数,则表示除了文件最后K行外,显示剩余的全部内容。-cK这里的K表示字节数,该选项用来显示文件前K个字节的内容;如果使用"-K",则表示除了文件最后K字节的内容,显示剩余全部内容。-v显示文件名;注意!如果没有指定具体显示的行数,默认显示就是前10行。下图是使用head命令查看tomcat中官方README.md的打印:三、tail命令tail命令的作用和head命令的作用正好相反,tail它可以从文件末尾查看文件内容。命令格式如下:常用可用选项:选项含义-nK这里的K指的是行数,该选项表示输出最后K行,在此基础上,如果使用-n+K,则表示从文件的第K行开始输出(这个参数在过滤文本表头的时候非常有用)。--cK这里的K指的是字节数,该选项表示输出文件最后K个字节的内容,在此基础上,使用-c+K则表示从文件第K个字节开始输出。-f输出文件变化后新增加的数据。同样地,tail命令如果在使用的时候没有指定行数,那么默认显示最后10行。使用cat、head、tail组合1、查看日志pro.log后100行数据2、查看日志pro.log100到300行的数据3、打印pro.log文件第1000行开始以后的内容4、打印pro.log文件第1000行开始之前的内容四、grep命令很多时候,我们并不需要列出文件的全部内容,而是从文件中找到包含指定信息的那些行,要实现这个目的,可以使用grep命令。grep是globalregularexpressionsprint的简写,它能够在一个或多个文件中,搜索某一特定的字符模式(也就是正则表达式),此模式可以是单一的字符、字符串、单词或句子。grep家族一共有三个:grep、egrep和fgrep。grep在查找的时候,会有三种类似返回值:找到返回0,没找到返回1,没有找到指定文件返回21、grep命令的格式这里的Patterns支持正则表达式以及普通字符串,grep的可选参数众多,按照men对grep提供的帮助,大致可以划分为以下几种:(1)匹配选择选项含义-E开启扩展(Extend)的正则表达式,也就相当于egrep命令-F解释PATTERN作为固定字符串的列表,由换行符分隔,其中任何一个都要匹配。也就相当于使用fgrep。-G将PATTERN视为普通的表示法来使用。这个是默认值,也就是grep-P将PATTERN解释为Perl正则表达式。(2)匹配控制选项选项含义-ePATTERN使用指定字符串做为查找文件内容的模式。-fFILE指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式。-i忽略大小写(ignorecase)。-v只打印没有匹配的,而匹配的反而不打印-w被匹配的文本只能是单词,而不能是单词中的某一部分-x只显示全列符合的列。-y此参数的效果和指定"-i"参数相同。(3)输出控制选项含义-c,--count显示总共有多少行被匹配到了,而不是显示被匹配到的内容--color将匹配到的内容以颜色高亮显示。-L列出文件内容不符合指定的样式的文件名称。-l列出文件内容符合指定的样式的文件名称。-o只显示匹配PATTERN部分。-q,--quiet,--silent静默。即使有匹配的内容也不显示出来。-s不显示错误信息。-b在显示符合样式的那一行之前,标示出该行第一个字符的编号-H在显示符合样式的那一行之前,表示该行所属的文件名称。-h在显示符合样式的那一行之前,不标示该行所属的文件名称。-Z,--null输出零字节(ASCIINUL字符),而不是通常在文件名后的字符。例如,grep-lZ在每个文件名之后输出一个零字节,而不是通常的换行符。即使存在包含不寻常字符(例如换行符)的文件名,此选项也可以使输出明确。-n显示行号(4)文本行控制选项含义-ANUM,--after-context=NUM显示匹配到的字符串所在的行及其后n行-BNUM,--before-context=NUM显示匹配到的字符串所在的行及其前n行-CNUM,--context=NUM显示匹配到的字符串所在的行及其前后各n行--group-separator=SEP使用SEP作为组分隔符。默认情况下,SEP是双连字符(-)。---no-group-separator使用空字符串作为组分隔符。2、正则表达式grep命令支持使用正则表达式,正则表达式又有基本正则表达式和扩展正则表达式的区别。(1)基本正则表达式匹配字符: .:任意一个字符。 [abc]:表示匹配一个字符,这个字符必须是abc中的一个。 [a-zA-Z]:表示匹配一个字符,这个字符必须是a-z或A-Z这52个字母中的一个。 [\^123]:匹配一个字符,这个字符是除了1、2、3以外的所有字符。对于一些常用的字符集,系统也做了定义: [A-Za-z]等价于[[:alpha:]] [0-9]等价于[[:digit:]] [A-Za-z0-9]等价于[[:alnum:]] tab,space等空白字符[[:space:]] [A-Z]等价于[[:upper:]] [a-z]等价于[[:lower:]] 标点符号[[:punct:]]匹配次数:\\{m,n\\}:匹配其前面出现的字符至少m次,至多n次。\?:匹配其前面出现的内容0次或1次,等价于{0,1}。\*:匹配其前面出现的内容任意次,等价于{0,},所以".*"表述任意字符任意次,即无论什么内容全部匹配。匹配多次,在使用的时候\一定不能丢匹配任意次位置锚定: ^:锚定行首 \$:锚定行尾。技巧:"^$"用于匹配空白行。 \b或\<:锚定单词的词首。如"\blike"不会匹配alike,但是会匹配liker \b或\>:锚定单词的词尾。如"\blike\b"不会匹配alike和liker,只会匹配like \B:与\b作用相反。匹配以某个字符(串)开始的行匹配以某个字符(串)结束的行分组及引用: \\(string\\):将string作为一个整体方便后面引用 \1:引用第1个左括号及其对应的右括号所匹配的内容。 \2:引用第2个左括号及其对应的右括号所匹配的内容。 \n:引用第n个左括号及其对应的右括号所匹配的内容。匹配开头字符和结束字符相同的行(2)扩展正则表达式扩展正则正则表达式需要加-E或者配合egrep使用,基本的东西和基本正则表达式一样,只是扩展了一些内容,具体如下:匹配字符:这部分和基本正则表达式一样匹配次数: \*:和基本正则表达式一样 ?:基本正则表达式是?,二这里没有\。 {m,n}:相比基本正则表达式也是没有了\。比如,{1,3}表示匹配1至3次;{3}表示匹配3次 +:匹配其前面的字符至少一次,相当于{1,}。位置锚定:和基本正则表达式一样。分组及引用: (string):相比基本正则表达式也是没有了\。 \1:引用部分和基本正则表达式一样。 \n:引用部分和基本正则表达式一样。逻辑选择a|b:匹配a或b。比如,/^([0-9]{3}-|([0-9]{3}))/表示匹配xxx-或(xxx)的数字串3、grep基本用法案例其实grep的输入不一定非要是文件,也可以是标准输入或者管道,比如:grep"root",和匹配标准输入使用grep查看指定进程:ps-ef|grep进程名或pa-aux|grep进程名过滤当前目录下的文件夹在网络配置文件/etc/sysconfig/network-scripts/ifcfg-ens33中检索出所有的IPgrep命令的功能非常强大,通过利用它的不同选项以及变化万千的正则表达式,可以获取任何我们所需要的信息。这里所介绍的grep命令,只介绍了它的一部分基础知识,比如说,grep命令可用的选项还有很多,且用法也五花八门,不过对于初学者来说,本节所介绍的内容已经足以应付多数Linux系统的日常工作了。五、sed命令我们知道,Vim采用的是交互式文本编辑模式,你可以用键盘命令来交互性地插入、删除或替换数据中的文本。但这里要讲的sed命令不同,它采用的是流编辑模式,最明显的特点是,sed是一种在线的、非交互式的编辑器,它-次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为模式空间(patternspace),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。sed的工作流程如下图所示:1、sed命令的基本格式2、sed常用的选项参数(sedOPTION)选项含义-e脚本命令该选项会将其后跟的脚本命令添加到已有的命令中。-f脚本命令文件该选项会将其后文件中的脚本命令添加到已有的命令中。一般不推荐使用-n默认情况下,sed会在所有的脚本指定执行完毕后,会自动输出处理后的内容,而该选项会屏蔽启动输出,需使用print命令来完成输出。-i此选项会直接修改源文件,要慎用。-r支持扩展正则表达式特别需要注意的是,sed与grep不同,它的返回值0并不能表示它有没有修改成功,只有当发生语法错误的时候才会返回1。3、sed命令参数(sedCOMMAND)命令参数功能a在定位行号后附加新文本信息i在定位行号后插入新文本信息d删除匹配的行c用新文本替换匹配的文本s使用替换模式替换匹配的文本g对数据中所有匹配到的内容进行全局替换,如果没有g,则只会在第一次匹配成功时做替换操作p打印与替换命令中指定的模式匹配的行。此标记通常与-n选项一起使用。q结束或退出sedn1~512之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有3个A,但用户只想替换第二个A,这是就用到这个标记;r读取文件到缓冲区**w**将缓冲区中的内容写到指定的file文件中h把模式空间中的内容复制到暂存空间H把模式空间中的内容追加到暂存空间x交换模式空间和暂存空间的内容4、sed基本用法sed的命令中支持使用正则表达式,但是用到扩展正则表达式的时候必须结合-r选项,并且正则表达式必须写在双斜线//之间,下面来演示一下sed命令的基本用法:(1)不使用任何COMMANDsed-r""passwd(2)让sed参数静默,不输出内容sed-rn""passwd(3)通过COMMAND让sed输出内容sed-rn"p"passwd(4)使用正则表达式让sed打印root开头的行sed-rn"/^root/p"passwd(5)全局匹配并替换sed-r"s/要替换的串/目标串/g"FILE(6)忽略大小写全局替换sed-r"s/要替换的串/目标串/gi"FILE5、sed扩展(高级)用法5.1sed定址操作地址用于决定对哪些行进行编辑。地址形式可以是数字、正则表达式或二者的结合。如果没有指定地址,sed将处理输入文件中的所有行。删除命令:d查找命令:s文件读入命令:r文件写出命令:w追加、插入、替换命令:a、i、c六、awk命令awk是一个强大的文本分析工具(严格来说是一种编程语言),相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。它之所以叫AWK是因为其取了三位创始人$AlfredAho$,$PeterWeinberger$,和$BrianKernighan$的FamilyName的首字符。1、awk的基本语法格式awk常用选项参数:选项含义-Ffs指定以fs作为输入行的分隔符,awk命令默认分隔符为空格或制表符。-ffile从脚本文件中读取awk脚本指令,以取代直接在命令行中输入指令。-vvar=val在执行处理过程之前,设置一个变量var,并给其设备初始值为val。awk的COMMAND部分由两部分组成,分别为匹配规则和执行命令,如下所示:这里的匹配规则,和sed命令中的COMMAND部分作用相同,用来指定命令可以作用到文本内容中的具体行,可以使用字符串或者正则表达式(比如/root/,表示查看含有root字符串的行)指定。另外需要注意的是,整个COMMAND部分必须是用单引号('')括起,而其中的执行命令部分需要用大括号({})括起来。在awk程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。2、awk使用位置参数变量awk的主要特性之一是其处理文本文件中数据的能力,它会默认自动把一行数据按空白字符(例如空格或制表符)分隔,并给每段数据一个位置参数($1、$2.....)。默认情况下,awk会将如下变量分配给它在文本行中发现的数据字段:$0代表整个文本行;$1代表文本行中的第1个数据字段;$2代表文本行中的第2个数据字段;$n代表文本行中的第n个数据字段。前面说过,在awk中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。在文本行中,每个数据字段都是通过字段分隔符划分的。awk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。这是使用默认的空白字符作为分隔符,我们还可以使用-F选项参数指定分隔符,比如下图以.(点)作为分隔符:3、awkCOMMAND中使用多个命令awk允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号(;)隔开即可,例如:4、awkBEGIN关键字默认情况下,awk会从输入中读取一行文本,然后针对该行的数据执行程序命令,但有时可能需要在处理数据前运行一些程序命令,这就需要使用BEGIN关键字。BEGIN会强制awk在读取文本之前执行BEGIN脚本块的命令,例如:注意!BEGIN命令和普通命令应该写在同一个单引号('')内。5、awkEND关键字END关键字和BEGIN关键字的作用相反,它允许awk在读取完数据执行执行他们,例如:了解BEGIN和END后,我们可以大致总结出awk的工作流程:先执行BEGIN,然后读取文件,读入有/n换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域,随后开始执行模式所对应的动作action。接着开始读入第二条记录······直到所有的记录都读完,最后执行END操作。6、awk的变量awk的变量分为内置变量和自定义变量;内置变量就是awk预定义好的、内置在awk内部的变量,而自定义变量就是用户定义的变量。(1)awk内置变量FS(FieldSeparator):输入字段分隔符,和-F参数作用类似,默认为空白字符OFS(OutofFieldSeparator):输出字段分隔符,默认为空白字符RS(RecordSeparator):输入记录分隔符(输入换行符),指定输入时的换行符,默认是换行符ORS(OutputRecordSeparate):输出记录分隔符(输出换行符),输出时用指定符号代替换行符,默认是换行符NF(NumberforField):当前行的字段的个数(即当前行被分割成了几列)NR(NumberofRecord):行号,输入记录的总的行号。FNR:awk可以同时处理多个文件,因此awk提供此变量用于获取各文件分别计数的行号ARGC:命令行参数的个数ARGV:数组,保存的是命令行所给定的各参数FILENAME:awk浏览的文件名此外,$0变量是指整条记录。$1表示当前行的第一个字段,$2表示当前行的第二个字段,......以此类推。统计/etc/passwd:文件名,每行的行号,每行的列数,对应的完整行内容:在命令中,我们首先使用内置命令FS定义了行分隔符为分号,之后awk开始逐行处理文件,使用内置变量获得文件名(FILENAME)、行号(NR)、每行的列数(NF)以及整行的内容($0),最后完成打印动作。(2)awk自定义变量在awk中自定义变量有两种方式使用-v参数在COMMAND外部定义一般格式:自定义的变量在awk的COMMAND中可以直接使用,不需要向shell脚本那样在变量名前加上$同时使用-v参数也可以接受shell变量的值直接在COMMAND内部定义并使用统计/etc/passwd的账户人数变量默认初始化为0,但是当初始值不为0的时候可以使用=赋值7、awk的格式化输出awk中同时提供了print和printf两种打印输出的函数。其中print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。统计磁盘可用空间大于10000字节的分区(1)使用print函数print函数会自动输出一个换行,一般我们都需要指定输出的格式(2)使用printf函数printf函数的使用方式和C语言的printf函数类似,使用的时候参考C语言的语法格式就可以了8、awk条件语句awk中的条件语句是从C语言中借鉴来的,关键字和使用方法参考C语言就可以了,下面给出一些用法示例:统计某个文件夹下的文件占用的字节数,过滤4096大小的文件(一般都是文件夹):9、awk循环语句awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。用的时候直接用就好了,没啥特别的。(1)for语句(2)while语句(3)do-while语句10、awk的数组因为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。数组使用的语法格式:AWK可以使用关联数组这种数据结构,索引可以是数字或字符串。AWK关联数组也不需要提前声明其大小,因为它在运行时可以自动的增大或减小。显示/etc/passwd的账户11、awk的正则匹配awk还支持正则匹配,正则表达式的语法规则参考上面grep命令的正则语法(通用的),不过需要注意的是:awk的匹配语句要写在//内部才可以被awk识别。示例给定一个包含电话号码列表(一行一个电话号码)的文本文件file.txt,写一个bash脚本输出所有有效的电话号码。你可以假设一个有效的电话号码必须满足以下两种格式:(xxx)xxx-xxxx或 xxx-xxx-xxxx。(x表示一个数字)这个例子可以使用正则表达式配合grep-P、sed和awk,这里使用awk来实现awk编程的还有很多内容,这里只罗列简单常用的用法,更多请参考http://www.gnu.org/software/gawk/manual/gawk.html
LoveIT 2020-05-21Linux -
Shell教程—Linux管道详解
Shell除了有输入/输出重定向的功能,还可以将两个或者多个命令(程序或者进程)连接到一起,把一个命令的输出作为下一个命令的输入,以这种方式连接的两个或者多个命令就形成了一种特殊的文件称为管道(pipe)。Linux中管道分为两种:匿名管道和命名管道一、匿名管道我们常见的管道符|它是一个匿名管道,只能用于具有亲缘关系的进程之间,这是它与命名管道的最大区别。命名管道叫namedpipe或者FIFO(先进先出),可以用命令mkfifo创建。1、匿名管道的命令语法格式Linux匿名管道的具体语法格式如下:当在两个命令之间设置管道时,管道符|左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道。大部分的Linux命令都可以用来形成管道。2、示例:(1)寻找mysql进程(2)获取docker中所有的容器(3)获取当前目录下文件的个数等等,管道还有好多用处,这里仅仅给出一些示例。二、命名管道namedpiped我们常用的|是一个匿名管道,它只能用于具有亲缘关系的进程之间,当需要在爱多个线程之间使用管道的时候就需要命名管道。1、命名管道的创建命名管道的语法格式如下:2、示例:使用mkfifo命令就可以创建一个有名字的管道,前文提到过,管道实际也是一种文件,只不过它具有先进先出以及管道中的数据被读取以后就消失的特性(类似于队列)。(1)使用命令管道实现多终端交互在/dev/pts/0终端上创建了一个名为/tmp/fifo3的管道,并向管道总中输入数据,在/dev/pts/1终端获取数据在/dev/pts/0终端创建管道,并向管道输入数据在/dev/pts/1终端通过管道接收到数据值的注意的是,在/dev/pts/1终端终端接收数据的时候,当/dev/pts/0没有给管道输入数据的时候,/dev/pts/1终端会一直等待,当数据从管道读取之后再次尝试读取发现之前的数据没有了。这也进一步验证了管道的两个特性:FIFO和数据被读取以后就消失。(2)使用命名管道和FD实现对Shell并发的控制创建一个多线程的网络检查脚本,要求最多开启5个线程同时工作执行结果【部分】:三、管道的实现机制管道技术是Linux操作系统中由来已久的一种进程间通信机制。所有的管道技术,无论是半双工的匿名管道,还是命名管道,它们都是利用FIFO排队模型来指挥进程间的通信。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。不过在Linux中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面而实现的。
LoveIT 2020-05-17Linux -
Shell教程—Shell输入/输出重定向
LinuxShell重定向分为两种,一种输入重定向,一种是输出重定向;从字面上理解,输入输出重定向就是改变输入与输出的方向的意思。一般情况下,我们以CPU为参考,CPU读入数据就是输入,CPU写出数据就是输出。在Linux中从键盘输入数据被称为标准输入,向屏幕/显示器上显示数据被称为标准输出。一、硬件设备和文件描述符1、文件描述符定义计算机的硬件设备有很多,常见的输入设备有键盘、鼠标、麦克风、手写板等,输出设备有显示器、投影仪、打印机等。不过,在Linux中,标准输入设备指的是键盘,标准输出设备指的是显示器。Linux中一切皆文件,包括标准输入设备(键盘)和标准输出设备(显示器)在内的所有计算机硬件都是文件。为了表示和区分已经打开的文件,Linux会给每个文件分配一个ID,这个ID就是一个整数,被称为文件描述符(FileDescriptor)。文件描述符文件名类型0stdin标准输入文件1stdout标准输出文件2stderr标准错误输出文件3以上filiename其他文件Linux程序在执行任何形式的I/O操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。stdin、stdout、stderr默认都是打开的,在重定向的过程中,0、1、2这三个文件描述符可以直接使用。2、查看一个进程打开了哪些文件语法:ll/proc/[进程ID]/fd/proc/[进程ID]/fd这个目录专门用于存放文件描述符。0、1、2也就是宏STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。二、LinuxShell输出重定向通过上面的学习我们了解到Linux标准输出是显示器,输出重定向就是将指令命令的结果不在输出到显示器上,而是输出到其它地方,一般是指定文件中。这样做的最大好处就是把命令的结果保存起来,当我们需要的时候可以随时查询。Bash支持的输出重定向符号如下表所示:类型符号作用标准输出重定向command>file以覆盖的方式,把命令command的正确执行结果输出到文件file中command>>file>以追加的方式,把命令command的正确执行结果输出到文件file中标准错误输出重定向command2>file以覆盖的方式,把命令command的错误执行结果输出到文件file中command2>>file以追加的方式,把命令command的错误执行结果输出到文件file中正确输出和错误信息同时保存command>file2>&1以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中command>>file2>&1以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中command>file12>file2以覆盖的方式,把正确的输出结果输出到file1文件中,把错误信息输出到file2文件中。command>>file12>>file2以追加的方式,把正确的输出结果输出到file1文件中,把错误信息输出到file2文件中。command>file2>file【不推荐】这两种写法会导致file被打开两次,引起资源竞争,所以stdout和stderr会互相覆盖command>>file2>>file>注意在文件重定向中我们使用了两个关键点的符号:>和>>,其中前者表示覆盖原来的文本,后者表示在原来的文本后面追加。其实文重定向的标准写法是:fd>file或者fd>>file,其中fd表示文件描述符,不写默认为1,也就表示标准输出。当文件描述符为1时,一般都省略不写,如上表所示;当然,如果你愿意,也可以将command>file写作command1>file,但这样做是多此一举。当文件描述符为大于1的值时,比如2,就必须写上。需要重点说明的是,fd和>之间不能有空格,否则Shell会解析失败;>和file之间的空格可有可无。为了保持一致,我习惯在>两边都不加空格。案例(1)使用echo命令将Shell脚本的运行结果输出到指定的文件中写一个脚本,检测当前系统对CPU的使用率,把脚本加入到crontab定时任务中,每分钟执行一次,并把执行结果输出到/home/cpu_monitor.txt文本中执行结果:向文件中输出命令执行的错误信息同样可以参考上面的方法,不同的地方是需要在>或>>前面加上2,注意不要有空格。(2)/dev/null文件在Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF。在程序员行话,尤其是Unix行话中,/dev/null被称为位桶(bitbucket)或者黑洞(blackhole)。空设备通常被用于丢弃不需要的输出流,或作为用于输入流的空文件。当你读它的时候,它会提供无限的空字符(NULL,ASCIINUL,0x00)。因此利用这个文件,如果你既不想把命令的输出结果保存到文件,也不想把命令的输出结果显示到屏幕上,干扰命令的执行,那么可以把命令的所有结果重定向到/dev/null文件中。如下所示:关于/dev/null的用处还用很多,这里只是举一个例子,在使用的时候大家可以把/dev/null当成Linux系统的垃圾箱,任何放入垃圾箱的数据都会被丢弃,不能恢复。三、LinuxShell输入重定向输入重定向就是改变输入的方向,不再使用键盘作为命令输入的来源,而是使用文件作为命令的输入。符号说明command<file将file文件中的内容作为command的输入。command<<END从标准输入(键盘)中读取数据,直到遇见分界符END才停止(分界符可以是任意的字符串,用户自己定义)。command<file1>file2将file1作为command的输入,并将command的处理结果输出到file2。和输出重定向类似,输入重定向的完整写法是fd<file,其中fd表示文件描述符,如果不写,默认为0,也就是标准输入文件。案例(1)统计给定文本中有多少行统计刚才CPU监控脚本定时计划向/home/cpu_monitor.txt文本中输出的文本的行数(2)逐行读取文件读取/etc/scripts/numbers.txt文件(每个数字占一行),读取到脚本中对文件中的数字排序执行结果:(3)HereDocumentHereDocument是Shell中的一种特殊的重定向方式,用来将输入重定向到一个交互式Shell脚本或程序。它的基本的形式如下:它的作用是将两个delimiter之间的内容(document)作为输入传递给command。
LoveIT 2020-05-17Linux -
Shell教程—Shell函数
和大多数编程语言一样,Shell也可以定义和使用函数(function)。Shell函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可。Shell函数定义的语法格式如下:说明:Shell的函数定义的时候可以使用function关键字,也可以不使用funname:函数的名字在每个语句后面可以写上;(分号)也可以不写Shell的函数没有形参的概念,当需要给函数传参可以使用位置参数进行传参returnint:参数返回,可以显示加:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)函数的返回值使用$?获取函数定义的简化写法函数定义的时候可以不写function关键字如果写了function关键字,可以不写函数名后面的括号(注意这种情况下函数名和{之间要有空格):具体使用哪种方式定义函数,执行的效果没有区别,完全取决于个人喜好!函数的参数Shell中的函数在定义时不能指明参数,但是在调用时却可以传递参数。函数参数是Shell位置参数的一种,在函数内部可以使用$n来接收,例如,$1表示第一个参数,$2表示第二个参数,依次类推。除了$n,还有另外三个比较重要的变量:$#可以获取传递的参数的个数;$@或者$*可以一次性获取所有的参数。示例:执行结果:函数的调用Shell不限制定义和调用的顺序,你可以将定义放在调用的前面,也可以反过来,将定义放在调用的后面。1、无参函数的调用调用无参函数,直接在需要的地方写函数名就行了,不需要写括号2、有参函数的调用如果传递参数,那么多个参数之间以空格分隔,函数名字后面都需要带括号:案例在案例中将着重演示使用Shell如何实现递归函数的定义和使用1、定义一个函数计算n(1<=n<=20)的阶乘执行结果:通过这个程序,我们需要了解Shell中递归函数的编写手段以及当函数的返回值超过255了之后我们如何处理。2、二分查找写一个脚本实现二分查找执行结果:
LoveIT 2020-05-16Linux -
Shell教程—Shell数组
数组中可以存放多个值。BashShell只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。Shell中的数组有两类:普通数组和关联数组。普通数组就是我们熟悉的一维数组,它的索引只能为整数;关联数组实质是一种key-value的集合,key和value既可以是整数也可以是字符串。一、Shell数组的基本语法1、普通数组(1)定义数组Shell中没有多维数组的概念,只有一维数组,它的语法格式如下:(2)查看数组(3)访问数组中的元素读取数组元素值的一般格式是:2、关联数组(1)定义数组关联数组可以理解为一种key-value集合,它的语法格式如下:(2)查看数组(3)访问数组元素读取关联不能用整型下标值,而应该使用在定义时给定的key,一般格式是:二、案例1、将/etc/hosts每行host读取存入hosts数组(简单)执行结果:2、编写一个脚本对给定文件中的数字进行排序,并输出在numbers.txt文本中有大量(大约10w个)随机生成的数字,现在要求对这些数字进行排序实现排序:执行结果:3、统计/etc/passwd文件中不同Shell的个数/etc/passwd文件中定义了许多Shell,现在需要统计不同Shell的个数执行结果:
LoveIT 2020-05-14Linux -
Shell教程—流程控制之循环语句for、while、until
BashShell中主要提供了三种循环方式:for、while和until一、for循环与其他编程语言类似,Shell支持for循环。for循环的运作方式,是将串行的元素意义取出,依序放入指定的变量中,然后重复执行含括的命令区域(在do和done之间),直到所有元素取尽为止。其中,串行是一些字符串的组合,彼此用$IFS所定义的分隔符(如空格符)隔开,这些字符串称为字段。for循环一般格式为:案例1:Shell打印执行目录的目录树tree自己写一个脚本,实现和tree命令一样可以将自动目录打印成可视化的文件数的功能。执行结果【部分】:二、while循环在案例一种我们在打印文件目录树中也是用了while循环。while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。while循环的语法:案例二:使用while实现从1加到n,n由用户输入,并且输出最终的和执行结果:无限循环的语法格式:(1)写法一:使用一个:作为条件,就可以实现无限循环(2)写法二:使用true关键字(3)写法三:使用for语句的C风格三、until循环until循环执行一系列命令直至条件为true时停止。until循环与while循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候—也只是极少数情况下,until循环更加有用。until语法格式:案例三:使用until打印九九乘法表执行结果:四、跳出循环在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。1、break命令break关键字的语法:breaknn表示跳出循环的层数,如果省略n,则表示跳出当前的整个循环。break关键字通常和if语句一起使用,即满足条件时便跳出循环。案例四:Shell脚本和用户交互执行结果:案例五:使用break跳出2层循环执行结果:2、continue命令continue关键字的语法为:n表示循环的层数:如果省略n,则表示continue只对当前层次的循环语句有效,遇到continue会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。如果带上n,比如n的值为2,那么continue对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带n的continue。这么说可能有点难以理解,稍后我们通过代码来演示。执行结果:从运行结果可以看出,遇到continue2时,不但跳过了内层for循环,也跳过了外层for循环。break和continue的区别break用来结束所有循环,循环语句不再有执行的机会;continue用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环。
LoveIT 2020-05-13Linux -
Shell教程—模式匹配case
case语句和if...elif...else语句一样都是多分支条件语句,不过和多分支if条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。case语句的语法结构:case语句应该注意一下几点:case语句会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程,之后就不会在往下走;如果数值不符,则依次比较下一个值;如果所有的值都不符合,则执行*)(*代表所有其他值)中的程序。case语句以case开头,以esac结尾。在每个分支程序之后要以;;(双分号)结尾,代表该程序段结束(千万不要忘记)。案例1:模式匹配用户输入选择在我们的脚本中经常有让用户输入Y/N表示是否确定执行接下来的操作,这个用if-else也可以做,但是当考虑到多个判断条件的时候if写出来的语句就显得过于繁杂,用case语句就可以简化案例二:实现一个简单的系统管理工具执行脚本,首先打印支持的工具和代号,然后用户输入工具的代号执行功能。
LoveIT 2020-05-12Linux -
Linux Vi/Vim常用操作
所有的UnixLike系统都会内建vi文书编辑器,其他的文书编辑器则不一定会存在。但是目前我们使用比较多的是vim编辑器。vim具有程序编辑的能力,可以主动的以字体颜色辨别语法的正确性,方便程序设计。vi/vim共分为三种模式,分别是命令模式(Commandmode),输入模式(Insertmode)和底线命令模式(Lastlinemode)。三种模式的切换可以用下图表示:1、命令模式下常用操作在shell终端下当我们输入vimxxx(xxx指的是文件名)就可进入到vim的编辑界面,此时vim默认处于命令模式中,在此模式下可以进行的光标移动、复制粘贴、搜索替换等操作移动光标的方法移动光标的方法功能示例h或左箭头键(←)光标向左移动一个字符20h或20←光标向左移动20个字符j或下箭头键(↓)光标向下移动一行15j或15↓光标向下移动15行k或上箭头键(↑)光标向上移动一行同理l或右箭头键(→)光标向右移动一个字符同理0或功能键[Home]将光标移动到此行的第一个字符$或功能键[End]将光标移动到此行的最后一个字符G移动到这个文档的最后一行nGn为数字。移动到这个文档的第n行。20G移动到这个文档的第20行gg相当于1G,移动到此文档的第一行n<Enter>n为数字。光标向下移动n行5<Enter>光标向下移动5行搜索替换的方法搜索替换/word向光标之下寻找一个名称为word的字符串。例如要在文档内搜寻abc这个字符串,就输入/abc即可?word向光标之上寻找一个字符串名称为word的字符串。nn是英文按键。代表重复前一个搜寻的动作。举例来说,如果刚刚我们执行/abc去向下搜寻abc这个字符串,则按下n后,会向下继续搜寻下一个名称为abc的字符串。如果是执行?abc的话,那么按下n则会向上继续搜寻名称为abc的字符串!NN是英文按键。与n刚好相反,它是向上寻找:n1,n2s/word1/word2/gn1与n2为数字。在第n1与n2行之间寻找word1这个字符串,并将该字符串取代为word2复制、粘贴、删除的方法删除、复制与粘贴x,X在一行字当中,x为向后删除一个字符(相当于[del]按键),X为向前删除一个字符(相当于[backspace]亦即是退格键)dd删除光标所在的那一行nddn数字,表示删除光标所在行一下的n行,例如20dd则是删除20行(常用)d^或d1G删除光标所在到第一行的所有数据dG全文删除yy复制光标所在的那一行数据nyyn为数字。复制光标所在的向下n行,例如20yy则是复制20行y^或y1G复制光标所在行到第一行的所有数据yG复制光标所在行到最后一行的所有数据ggyG全文复制p将已复制的数据在光标下一行贴上P将已复制的数据在光标上一行贴上u复原前一个动作(撤销)。【Ctrl】+r或.重做上一个动作2、编辑模式下常用操作在命令模式下我们按以下几个特定的字母就会进入到不同的编辑模式中,在编辑模式中我们就可以编辑文档。进入输入或取代的编辑模式i,I进入输入模式(Insertmode):i为从目前光标所在处输入,I为在目前所在行的第一个非空格符处开始输入。a,A进入输入模式(Insertmode):a为从目前光标所在的下一个字符处开始输入,A为从光标所在行的最后一个字符处开始输入。o,O进入输入模式(Insertmode):这是英文字母o的大小写。o为在目前光标所在的下一行处输入新的一行;O为在目前光标所在处的上一行输入新的一行!r,R进入取代模式(Replacemode):r只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下ESC为止;Esc按键退出编辑模式,回到命令模式3、底线模式下常用操作指令行的储存、离开等指令:w将编辑的数据写入硬盘档案中:w!若文件属性为只读时,强制写入该档案。不过,到底能不能写入,还是跟你对该档案的档案权限有关:q退出编辑器:q!若曾修改过文档,又不想保存,使用!为强制退出不保存。:wq保存并退出,若为:wq!则为强制保存并退出:setnu显示行号,设定之后,会在每一行的前缀显示该行的行号:setnonu取消显示行号
LoveIT 2020-05-11Linux -
Shell教程—流程控制之if
在我们常见的高级语言中都有if这个关键字,在shell中也有这个关键字,它也是用来做条件判断的。shell中的if的语法格式如下:一、if语句的基本语法1、if相当于C语言中的if语句最后那个fi必须要写,这也是他语法的一部分。2、ifelse相当于C语言中的if-else语句3、ifelse-ifelse从语法上来看,shell中的if和C、Java等语言的条件判断语句还是有些许差别的,并且不光是这些差别:(1)shell中的else分支如果没有语句,那就不要写出来,这样会报错(2)if后的condition一定要是一个能返回true或false的语句,虽然我们常常将1认为是true、0认为是false,但是这里的condition运算结果只能是true或false,否则,即使执行结果是1或0,都会认为condition这个条件是具备的,就不走其他分支了接下来我们就来学习一下,if的conditon该如何写。二、Shell条件测试Shell条件测试有两个命令test和[,他们两个的作用是一样的,用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。[命令和test命令是两个等价的命令,他们两可以相互替换。1、数值测试参数说明INTEGER1-eqINTEGER2INTEGER1isequaltoINTEGER2(两个数都相等为真)INTEGER1-geINTEGER2INTEGER1isgreaterthanorequaltoINTEGER2(大于等于为真)INTEGER1-gtINTEGER2INTEGER1isgreaterthanINTEGER2(大于为真)INTEGER1-leINTEGER2INTEGER1islssthanorequaltoINTEGER2(小于等于为真)INTEGER1-ltINTEGER2INTEGER1islessthanINTEGER2(小于为真)INTEGER1-neINTEGER2INTEGER1isnotequaltoINTEGER2(不等于为真)示例:判断两个数大小执行结果:上面程序中[]可以使用test命令替换,感兴趣的小伙伴可以自行测试一下。2、字符串测试参数说明-nSTRING字符串的长度不为零则为真-zSTRING字符串的长度为零则为真STRING1=STRING2两个字符串相等STRING1!=STRING2两个字符串不相等示例:判断输入的两个字符串是否相等执行结果:3、文件测试参数说明-eFILE|DIR如果文件或目录存在则为真(常用)-rFILE如果文件存在且当前用户可读则为真-wFILE如果文件存在且可写则为真-xFILE如果文件存在且可执行则为真-sFILE如果文件存在且至少有一个字符则为真-dDIR如果文件存在并且是目录则为真(常用)-fFILE如果文件存在且为普通文件(常用)-cFILE如果文件存在且为字符型特殊文件-bFILE如果文件存在且为块特殊文件-LFILE文件存在并且是链接文件FILE1-efFILE2FILE1和FILE2具有相同的设备和inode编号FILE1-ntFILE2FILE1比FILE2新FILE1-otFILE2FILE1比FILE2旧示例:编写一个脚本,让用户输入一个目录的路径,脚本分别输出该目录下文件和目录的个数。执行结果:4、逻辑运算符逻辑运算符可以用于连接逻辑表达式的各个条件参数说明举例!逻辑非运算[!$fileName="conf.d"]判断文件名知否叫conf.d如果是的话返回false-a逻辑与运算,遵循逻辑短路原则[-d$file-a`uname-s`="Linux"]判断文件是否是录目录并且当前系统是Linux,如果都是,那么返回true-o逻辑或运算,遵循逻辑短路原则[$(uname-s)="Linux"-o$(uname-s)="FreeBSD"]判断当前系统如果是Linux或FreeBSD的话返回true&&逻辑或,遵循逻辑短路原则a=100b=200[[$agt50]]&&[[$alt$b]]判断a是否大于50并且小于b,如果都满足,返回true||逻辑与,遵循逻辑短路原则a=100b=200[[$agt50]]||[[$blt500]]判断a是否大于50或b小于500,如果有一个满足,返回true在Shell中if判断中多条件的写法比如比较a>b且a<c,可以的写法如下:三、if练习1、判断用户是否存在执行脚本,输入一个用户名,脚本判断此用户是否存在,如果不存在时根据用户输入的Y/N来决定是否创建该用户2、一键检测服务器上docker是否安装、启动首先脚本判断docker是否安装,如果没有安装就要执行安装;如果已安装,就判断docker是否启动,如果没有启动,就启动docker服务。代码仅供参考,如有不妥之处还望大佬指出,不胜感激!!!
LoveIT 2020-05-11Linux -
Shell教程—Shell打印输出命令
在Linux中有两个常见的Shell输出命令echo和printf,他们都可以打印,但是又有些许差别,接下来我们就来了解一下他们吧。一、echo命令echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串的标准输出。命令格式:echo命令的常见用于如下:(1)打印普通字符(2)显示转义字符(3)显示变量的值(4)显示命令的执行结果二、ptintf命令学过C语言的同学应该对C中的标准输出函数printf不陌生,几乎编程一入门第一个了解的是main函数,第二个可能就是printf函数了,Linux中的printf命令模仿C程序库(library)里的printf()程序。printf由POSIX标准所定义,因此使用printf的脚本比使用echo移植性好。printf使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认printf不会像echo自动添加换行符,我们可以手动添加\n。printf命令的语法:参数说明:format-string:为格式控制字符串arguments:为参数列表。总体上来说,printf命令和C的printf()函数作用类似。这里对比C语言printf()函数来说明一下不同之处:(1)printf命令不用加括号(2)format-string可以没有引号,但最好加上,单引号双引号均可。实例:(3)参数多于格式控制符(%)时,format-string可以重用,可以将所有参数都转换。(4)arguments使用空格分隔,不用逗号。执行结果:剩下的printf命令还有一些格式替代符以及转移字符,这里就不详细的说了,基本和C语言中的printf函数类似。总结当我们需要简单的输出一下结果的时候使用echo就够了,很简单,也没有繁琐的参数,但是当我们需要格式化输出的时候,printf命令将是不二之选。
LoveIT 2020-05-08Linux -
Shell教程—Shell入门
Shell教程—Shell入门Shell是一个用C语言编写的命令解释器(commandinterpreter),是Unix操作系统的用户接口,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。下图所示用户、shell和操作系统的关系:Shell脚本(shellscript),是一种为shell编写的脚本程序,shell和shellscript是两个不同的概念。Shell编程跟JavaScript、php编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。一、Shell的种类Linux的Shell种类众多,我们可以使用命令cat/etc/shells查看系统中安装的shell。通常操作系统内核(kernel)与shell是独立的套件,而且都可被替换。不同的操作系统使用不同的shell,同一个kernel之上可以使用不同的shell。常见的shell分为两大主流:shBourneShell(/usr/bin/sh或/bin/sh),Solaris,hpux默认shellBourneAgainShell(/bin/bash),Linux系统默认的shellcshCShell(/usr/bin/csh)KShell(/usr/bin/ksh)ShellforRoot(/sbin/sh)BourneAgainShell,由于易用和免费,Bash在日常工作中被广泛使用。同时,Bash也是大多数Linux系统默认的Shell。查看当前系统使用的shell—echo$SHELL二、Shell环境变量定义1、临时环境变量所谓临时变量是指在用户在当前登陆环境生效的变量,用户登陆系统后,直接在命令行上定义的环境变量便只能在当前的登陆环境中使用。当退出系统后,环境变量将不能下次登陆时继续使用。2、将环境变量永久生效通过将环境变量定义写入到配置文件中,用户每次登陆时系统自动定义,则无需再到命令行重新定义。定义环境变量的常见配置文件如下:/etc/profile针对系统所有用户生效,此文件应用于所有用户每次登陆系统时的环境变量定义$HOME_name/.bash_profile针对特定用户生效,$HOME为用户的宿主目录,当用户登陆系统后,首先继承/etc/profile文件中的定义,再应用$HOME/.bash_profile文件中的定义。3、系统预定义的环境变量系统环境变量对所有用户有效,如:PATH、HOME、SHELL、PWD等等,如下用echo命令打印上述的系统环境变量:三、Shell脚本编程基本概念1、shell脚本的格式一个shell脚本通常包含如下部分:首行第一行内容在脚本的首行左侧,表示脚本将要调用的shell解释器,比如#!/bin/bash,就表示要代调用BourneAgainShell解释器来执行shell脚本#!符号,英文名叫Shebang([ʃɪˈbæŋ]),没有中文名。该符号能够被内核识别成是一个脚本的开始,这一行必须位于脚本的首行,/bin/bash是bash程序的绝对路径,在这里表示后续的内容将通过bash程序解释执行。注释注释符号#放在需注释内容的前面。内容任何可执行的Linux命令以及shell编程的语法关键字2、赋予shell脚本的执行权限在Linux中创建的一个文件默认是没有执行权限的,包括shell脚本没有执行权限是不能被执行的,需要我们给他权限:chmod755文件名或chmod+x文件名3、shell脚本的执行(1)输入脚本的绝对路径或相对路(2)bash或sh+脚本名注:当脚本没有x权限时,root和文件所有者通过该方式可以正常执行。(3)在脚本的路径前再加"."或source上面两种执行shell脚本的方法都是在子shell中执行的,没有在当前shell中执行,当我们需要在当前shell执行shell的时候,我们就需要使用这种方法。goto-usrlocal.sh脚本的内容如下:一张图说明差别:区别:第一种和第二种会新开一个bash,不同bash中的变量无法共享。但是使用../脚本.sh这种方式是在同一个shell里面执行的。四、Shell变量 变量是shell传递数据的一种方式,用来代表每个取值的符号名。当shell脚本需要保存一些信息时,如一个文件名或是一个数字,就把它存放在一个变量中。1、变量设置规则:(1)变量名称可以由字母,数字和下划线组成,但是不能以数字开头,环境变量名建议大写,便于区分。(2)在bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型。(3)变量用等号连接值,等号左右两侧不能有空格。(4)变量的值如果有空格,需要使用单引号或者双引号包括。2、变量分类(1)自定义变量用户自定义的变量由字母或下划线开头,由字母,数字或下划线序列组成,并且大小写字母意义不同,变量名长度没有限制。定义变量:变量名=变量值变量名必须以字母或下划线开头,区分大小写,ip1=192.168.2.115引用变量:$变量名或${变量名}查看变量:echo$变量名或${变量名}取消变量:unset变量名作用范围:在当前shell有效示例:在定义或引用变量的时候要特别注意:(1)""和''两个引号的作用不同,双引号是弱引用,内部可以使用变量;单引号是强引用,内部无法使用变量。(2)``,反引号的作用是命令替换,作用等价于$(),写在它们之中的命令会被优先执行。(2)系统环境变量保存和系统操作环境相关的数据。HOME、HOME、PWD、SHELL、SHELL、USER等等,可以使用env命令查看当前系统定义的所用系统变量,对于这些系统环境变量在我们编写shell脚本的时候可以直接使用。定义环境变量:引用环境变量:$变量名或${变量名}查看环境变量:echo$变量名或${变量名}取消环境变量:unset变量名环境作用范围:在当前shell和子shell有效示例:系统环境变量我们熟悉的比如配置Java的环境变量,在Linux系统中比如有个jdk1.8的JDK在/usr/local/java下,我们配置系统环境变量可以这么配置:(3)位置参数变量主要用来在执行命令的时候向脚本中传递参数或数据,变量名不能自定义,变量作用固定。格式含义$nn为数字,0代表命令本身,0代表命令本身,$1-$9代表第1到第9个参数,10以上的参数需要用大括号包含,如${10}。示例:要特别注意的是,$0表示的时shell脚本本生,在使用的时候要特别注意!!!(4)预定义变量预定义变量是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的,我们也可以直接使用这些变量。格式含义$?执行上一个命令的返回值执行成功,返回0,执行失败,返回非0(具体数字由命令决定)$$当前进程的进程号(PID),即当前脚本执行时生成的进程号$!后台运行的最后一个进程的进程号(PID),最近一个被放入后台执行的进程&$*代表命令行中所有的参数,把所有的参数看成一个整体。以"112…$n"的形式输出所有参数$@代表命令行中的所有参数,把每个参数区分对待。以"1""1""2"…"$n"的形式输出所有参数$#代表命令行中所有参数的个数。添加到shell的参数个数(5)read命令Linuxread命令用于从标准输入读取数值。read内部命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,可以读取文件中的一行数据。语法:参数说明:-a后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。-d后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。-p后面跟提示信息,即在输入前打印提示信息。-e在输入的时候可以使用命令补全功能。-n后跟一个数字,定义输入文本的长度,很实用。-r屏蔽\,如果没有该选项,则\作为一个转义字符,有的话\就是个正常的字符了。-s安静模式,在输入字符时不再屏幕上显示,例如login时输入密码。-t后面跟秒数,定义输入字符的等待时间。-u后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的。示例:脚本的功能很简单,就是先让用户输入是否据需执行脚本,如果输入的Y/y就据需执行,否则直接退出。当输入Y/y之后在让用户输入两个数字,之后输出两个数的和。执行结果:五、Shell数学计算1、整数计算(1)使用expr命令进行计算expr支持的操作符有:|、&、<、<=、=、!=、>=、>、+、-、*、/、%;expr支持的操作符中所在使用时需用\进行转义的操作符有:|、&、<、<=、>=、>、*;只支持整数运算。另外,expr的操作数和操作符之间要有一个空格,并且返回的结果需要用``或者$()来获取。执行结果:(2)使用$(())处理使用$(())可以直接输出结果,并且在引用变量的时候可以不用加$符号,对于有些特殊的运算符也不需要转义。同样的,它只支持整型运算。执行结果:(3)使用$[]运算$[]将中括号内的表达式作为数学运算先计算结果再输出;对$[]中的变量进行访问时前面需要加$;$[]支持的运算符与let相同,但也只支持整数运算。执行结果:(4)使用let命令运算let命令是这几个命令中我认为最好用的一个,因为他操作住够简单,而且支持几乎所有的运算符!!!包括括号优先、++、--等;参数可以不需要$,就可取到值进行运算;支持方幂运算leta=3**2";但依然只支持整数运算。执行结果:小试牛刀写一个脚本监控当前系统的内存暂用百分比%。Linux系统中查看系统内存使用情况的命令是free,但是free打印出来的的信息有两行Mem和Swap,Mem就是系统内存的使用详情,于是我们需要使用grep命令只取Mem这一行的数据,之后在使用awk分别获取Mem一行中第2和3个数据(总的内存、已经使用的内存)把他们相除即可。执行结果:2、浮点数计算(1)bc命令行计算器,可以进行整数运算和浮点数运算使用bc可以通过scale指定精度,但是只有在除法的时候才有生效,其他运算都是安装原来有几位就输出几位;bc支持除了位操作的所有运算。执行结果:(2)使用awk处理Awk是Linux中一个处理文本文件的语言,是一个非常好用的文本分析工具,之所以叫AWK是因为其取了三位创始人AlfredAho,PeterWeinberger,和BrianKernighan的FamilyName的首字符。关于Awk详细的请参考我的另一篇博客Linux教程—awk命令或者自行上网查找资料。执行结果:awk的变量跟shell的变量是独立的,所以需要使用-v进行变量传递;支持输出格式化,根据实际需要进行格式化输出;awk内置有非常多的函数,比如log、sqr、cos、sin等等。
LoveIT 2020-05-06Linux