[I/O多路复用]Select Poll & Epoll Part2

转载自 https://www.jianshu.com/p/397449cadc9a

前面大概提了一下在 network 中如何通过阻塞及同步异步还有多路复用来实现 I/O,现在终于可以讲到世纪linux中是如何使用API来实现I/O模型的. 这对比Operating书来说要实际和有意义.

blocking IO - 阻塞IO & nonblocking IO - 非阻塞IO & IO multiplexing - IO多路复用 & signal driven IO - 信号驱动IO 都可以归类为synchronous IO - 同步IO,而select、poll、epoll本质上也都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

在介绍select、poll、epoll之前,首先介绍一下Linux操作系统中基础的概念

  • 用户空间 / 内核空间
    现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。
    操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
  • 进程切换
    为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的,并且进程切换是非常耗费资源的。
  • 进程阻塞
    正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得了CPU资源),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
  • 文件描述符
    文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
  • 缓存I/O
    缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

述符数量,返回0表示超时,返回-1表示出错;

Epoll

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

Linux中提供的epoll相关函数如下:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。

2. epoll_ctl 函数注册要监听的事件类型。四个参数解释如下:

  • epfd 表示epoll句柄
  • op 表示fd操作类型,有如下3种
    • EPOLL_CTL_ADD 注册新的fd到epfd中
    • EPOLL_CTL_MOD 修改已注册的fd的监听事件
    • EPOLL_CTL_DEL 从epfd中删除一个fd
  • fd 是要监听的描述符
  • event 表示要监听的事件

epoll_event 结构体定义如下:

struct epoll_event {
    __uint32_t events;  /* Epoll events */
    epoll_data_t data;  /* User data variable */
};

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

3. epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

  • epfd 是epoll句柄
  • events 表示从内核得到的就绪事件集合
  • maxevents 告诉内核events的大小
  • timeout 表示等待的超时事件

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  • 水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件
  • 边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。

LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。

ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

总结

一张图总结一下select,poll,epoll的区别:

selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)
最大连接数1024(x86)或2048(x64)无上限无上限
fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。


既然select,poll,epoll都是I/O多路复用的具体的实现,之所以现在同时存在,其实他们也是不同历史时期的产物

  • select出现是1984年在BSD里面实现的
  • 14年之后也就是1997年才实现了poll,其实拖那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求
  • 2002, 大神 Davide Libenzi 实现了epoll

[I/O多路复用]Select Poll & Epoll

第一步先理解 阻塞/非阻塞/同步/异步. 其实计算机当中的很多概念并不只是从操作系统中衍生出来,各个领域都会有大致相同,但实则定义不一致的概念.就比如网路和操作系统的概念区别.

Unix网络编程中的五种IO模型

  • Blocking IO - 阻塞IO
  • NoneBlocking IO - 非阻塞IO
  • IO multiplexing - IO多路复用
  • signal driven IO - 信号驱动IO
  • asynchronous IO - 异步IO

这里讨论的主要是网络编程中的.同时signal driven主要是thread,直接处理IO的操作不多,所以这里不做讨论.

首先对于一个 network IO,就涉及到两个概念

  • application 调用这个IO的进程
  • kernel 系统内核

那他们经历的两个交互过程是:

  • 阶段1 wait for data 等待数据准备
  • 阶段2 copy data from kernel to user 将数据从内核拷贝到用户进程中

这大致和操作系统中的 thread_block 思路一致, 整个实现就是冲走了一遍 lock 机制. 不过就如同操作系统需要很多不同的机制来应对不同的问题,从而产生了Hoarne & Mesa Monitor Semaphore & Condition. 虽然四者可以互相实现,但是工程上需要做区分.

首先是Blocking IO, 又称阻塞IO. 在 linux 中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程大概如下图:

当用户进程调用了 recvfrom 这个 syscall, kernel 就开始了 IO 的第一个阶段,也就是收集数据, 对于network IO 来说,很多时候数据一开始还没有到达(比如,还没有收到一个完整的udp包),这个时候kernel 就要等待足够多的时间来.而在用户进程这里,整个进程都会被阻塞. 当kernel 一致等到数据准备好的时候,它就会从kernel中拷贝到用户内存.然后kernel 返回结果.用户进程才会接触block的状态,重新运行起来.

所以,blocking IO 的特点就是在IO执行的两个阶段就被block了.

这里与操作系统最大的区别就是network IO的延时通常比system 的数据流延时慢很多个时钟周期. 在操作系统中Blocking IO 一般是tick priority trigger 的.

其次是NoneBlockingIO,又称非阻塞IO

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

从图中可以看出,当用户进程发出recvfrom这个系统调用后,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个结果(no datagram ready)。从用户进程角度讲 ,它发起一个操作后,并没有等待,而是马上就得到了一个结果。用户进程得知数据还没有准备好后,它可以每隔一段时间再次发送recvfrom操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,用户进程其实是需要不断的主动询问kernel数据好了没有。

I/O多路复用(multiplexing)是网络编程中最常用的模型,像我们最常用的select、epoll都属于这种模型。以select为例:

看起来它与blocking I/O很相似,两个阶段都阻塞。但它与blocking I/O的一个重要区别就是它可以等待多个数据报就绪(datagram ready),即可以处理多个连接。这里的select相当于一个“代理”,调用select以后进程会被select阻塞,这时候在内核空间内select会监听指定的多个datagram (如socket连接),如果其中任意一个数据就绪了就返回。此时程序再进行数据读取操作,将数据拷贝至当前进程内。由于select可以监听多个socket,我们可以用它来处理多个连接。

在select模型中每个socket一般都设置成non-blocking,虽然等待数据阶段仍然是阻塞状态,但是它是被select调用阻塞的,而不是直接被I/O阻塞的。select底层通过轮询机制来判断每个socket读写是否就绪。

当然select也有一些缺点,比如底层轮询机制会增加开销、支持的文件描述符数量过少等。为此,Linux引入了epoll作为select的改进版本。

异步I/O在网络编程中几乎用不到,在File I/O中可能会用到:

这里面的读取操作的语义与上面的几种模型都不同。这里的读取操作(aio_read)会通知内核进行读取操作并将数据拷贝至进程中,完事后通知进程整个操作全部完成(绑定一个回调函数处理数据)。读取操作会立刻返回,程序可以进行其它的操作,所有的读取、拷贝工作都由内核去做,做完以后通知进程,进程调用绑定的回调函数来处理数据。

总结

我们来总结一下阻塞、非阻塞,同步和异步这两组概念。

先来说阻塞和非阻塞:

  • 阻塞调用会一直等待远程数据就绪再返回,即上面的阶段1会阻塞调用者,直到读取结束。
  • 而非阻塞无论在什么情况下都会立即返回,虽然非阻塞大部分时间不会被block,但是它仍要求进程不断地去主动询问kernel是否准备好数据,也需要进程主动地再次调用recvfrom来将数据拷贝到用户内存。

再说一说同步和异步:

  • 同步方法会一直阻塞进程,直到I/O操作结束,注意这里相当于上面的阶段1,阶段2都会阻塞调用者。其中 Blocking IO - 阻塞IO,Nonblocking IO - 非阻塞IO,IO multiplexing - IO多路复用,signal driven IO - 信号驱动IO 这四种IO都可以归类为同步IO。
  • 而异步方法不会阻塞调用者进程,即使是从内核空间的缓冲区将数据拷贝到进程中这一操作也不会阻塞进程,拷贝完毕后内核会通知进程数据拷贝结束。

下面的这张图很好地总结了之前讲的这五种I/O模型(来自Unix Network Programming)


hw2 Task2 改龙哥的read速度极快

不能再快了的read

credit : http://zonelikewonderland.tk/#/user/blog/details/481e27461fe6602e138b154ca8379587

const int buffsize=1e5;
char buf[buffsize],*pp=buf-1;
int readsize=0,freadsize=0;
inline void readinit(){
    fread(buf+(((readsize+buffsize-1)%buffsize>=buffsize/2-1)?0:buffsize/2),1,buffsize/2,stdin);
    freadsize+=buffsize/2;
}
inline int read(){
    if(readsize+buffsize/2>freadsize)readinit();
    while((++readsize,*++pp)<'-')if(pp==buf+buffsize-1)pp=buf-1;
    register int x=*pp&15;
    if(pp==buf+buffsize-1)pp=buf-1;
    while((++readsize,*++pp)>'-'){
        x=x*10+(*pp&15);
        if(pp==buf+buffsize-1)pp=buf-1;
    }
    if(pp==buf+buffsize-1)pp=buf-1;
    return x;
}

比我那个快。

专访 0x238e:畅想未来的交通世界

https://www.chainnews.com/articles/172600090308.htm

在本次 9102 BITRUN 全球极客大赛上,还有一支来自于上海科技大学的大一团队也同样引人关注,他们在 48 小时内实现了一个软硬件交互的基于区块链的车联网项目,并最终获得了本次 Hackathon 的 9102 创新特奖,那就是 0x238e 团队。

现在我们已经看到了自动化驾驶的雏形,那么未来的车联网的世界将会是什么样的场景?让我们和 0x238e 一起探秘未来!

很惊讶会有团队在 48 小时内完成一个软硬件结合的车联网 DApp 作品—VChain,这在目前的区块链行业中都是少之又少的。虽然这个作品只是个雏形,但已经展现出这些科技少年们对未来世界强烈的探索欲和挑战欲。这一点也与 CPChain 的愿景是一致的。我们已向 0x238e 团队发出了邀请,请他们来 CPChain 办公室参观,并期望未来能够保持沟通,甚至进一步孵化 VChain 项目。
——CPChain CTO 赵滨

首先介绍一下你们的团队吧

我们是来自上海科技大学的 0x238E 团队,共曹松晖、罗雨威、杨易为、叶者、张龙文五位同学,均来自 CS (秃头)专业。

曹松晖同学是 IoT 工程师,负责车端和传感器的环境搭建和程序开发。

罗雨威同学作为程序媛和产品经理,负责项目的整体统筹和内容细化。

杨易为同学作为团队中唯一会写智能合约的大腿,负责智能合约的开发以及智能合约和车端及用户端的对接。同时他作为项目提出者,提供了大部分文案以及灵感。

叶者同学作为一只全栈小工,负责了用户界面的设计与开发,同时也开发了后端代理服务用作前端与区块链和车端的交互。

张龙文同学是一个优秀的全能程序员兼架构师兼设计师,负责整个项目的架构以及车端的主程序开发,同时负责了车端与传感器数据和区块链数据的协调对接。作为一个优秀的设计师,他也顺带完成了团队的 PPT。

罗雨威、叶者、曹松晖、张龙文、杨易为(从左往右)

队名叫 0x238e 有什么含义吗?

团队:就是 9102 的十六进制啦,毕竟当时要报名了都还没想好队名,就直接随便一点。不过没想到的一点是巧合地获得了 9102 创新特奖。

简单介绍一下这次的作品吧

团队:作品全称 Vehicle Chain (简称 VChain),是一个基于区块链和车联网的驾驶辅助决策和保险评估系统

VChain 的价值来自于驾驶辅助决策的应用和商业模式的创新,前者依托区块链的安全和计算的无延迟,后者是数据聚合和移动的结果。

1、在一个范围内,多个车传感器及他们的数据事务,由 CPChain 创建的区块链信任。
2、人工智能决策,基于物联网大数据的人工智能决策。异构数据互操作,形成大数据聚合。
3、数据资产的交互与应用,数据资产在利用主链可验证的特点,惠及保险评估等新兴商业模式。

为什么会想到做这样一个作品呢?

团队:在这样一个半自动驾驶已然成熟,自动驾驶已经在路上的时间点,驾驶辅助决策的安全基础、应用在超车无感支付方面和数据的后续利用成了广泛讨论的议题。雾计算、大数据分析和人工智能决策技术是否可以深度融合,于是我们想到了车联网这个场景。

同时,我们畅想了未来的全自动无人驾驶场景中交通的调度,发现如果我们希望能够在全自动驾驶中保证车辆的自由性(例如,不变成像地铁 / 火车 / 飞机一样的中心化调度,而是去中心的),我们势必要引入一个车联网。同时,为了记录和妥善保存局部车联网中车辆的决策以及传感数据,我们想到了借助区块链技术。

你们觉得未来的车联网是什么样的?是每一个车辆都是一个单独的处理终端,还是会有一个统一的云管理系统?

叶者:个人而言,未来的车联网应该分为局部和全局两块。在局部车联网中每辆车作为单独的处理终端,形成一个去中心化的协商决策系统,在决策完成之后将记录上传至统一的全局车联网。同时,全局车联网也可以分发一些路况、管制信息等。

我对你们的超车无感支付很感兴趣可以给大家介绍一下么?

张龙文 : 超车无感支付系统是用来解决自动化交通情况下的调度决策问题。不可否认的是,在未来的道路上行驶的先后也是一种资源,需要合理的调度和分配。本功能在交通系统中引入基于代币支付的调度方案,旨在合理分配优先级,车辆间通过物联网的协商交易成本几乎为零,基于科斯定理可以得到使整个交通系统效率最大化的结果。这个超车无感支付系统有如下优点:1. 公平性地交易;2. 便利赶路者;3. 收益指数增长,减少兜车赚钱行为。

区块链技术在这个项目中起到了什么作用?

保证数据的真实可靠,未经篡改。

张龙文 : CPChain 的区块构建速度很快,可以用于秒级别的安全验证以及决策记录。

杨易为:CPChain 有很多很好的特性,比如三链合一的框架,存储可以在端上是一个很好的解决价格问题的

无人驾驶的时代距离我们还有多远?

罗雨威:或许 30 年吧。无人驾驶时代,意味着无人驾驶交通工具的普及,配套道路系统的更新升级。目前技术上已具有可行性,具体交通系统的改造还需要政府与广大公民达成共识。

曹松晖:纯无人驾驶希望不大:总不能指望在塔克拉玛干沙漠自动驾驶;但是我们的作业想定涉及到的背景应该用不了 30 年:现在非常热门的 5G,其目的之一就是要为无人驾驶 / 自动驾驶提供基础设施,结合各大传统车企以及诸多新兴车企的发力,基础设施和技术应该很快就不成问题了,最大的障碍应该是政府与公民达成共识

想象一下,你觉得未来的交通系统将会是什么样的?还会有哪些现在我们还没有看到的东西?

罗雨威:1. 未来的道路或许将是架构可变化的。道路可通过感知路面压力、车辆位置,判断此段道路是否为拥挤状况,从而在拥挤路段的上行路段进行道路架构变化,以升降、拓宽等方式扩展道路,增加车容量。

  1. 以上道路配合陆空两用车。道路变化可提供起飞跑道。垂直起降亦可(不过直升式耗时较长)
  2. 自动化驾驶使得每一次出行都获得最优决策,达到最高效率。
  3. 可能同时出现两个极端:一个是全自动驾驶;另一个是可以到远郊中远离交通干线的地方手动驾驶,体验驾驶乐趣,不涉及超车问题,因为只有自己,无车可超。后者需要联系前一个话题:远郊且远离交通干线的地方可能没那么多道路基础设施(飙车游乐场)

哈哈,未来的车还是一定得跑在地面上么?我看到的科幻片里面都是像飞船一样的呢。

团队:如果车辆不跑在地面,那就更加需要我们的这套系统啦。飞车的交通规划就不是二维的而是三维的了,所以他的“路况”就会比现在的要复杂很多,这个时候势必要引入自动驾驶才能保证整个交通系统的秩序与安全。同时,这些自动驾驶的飞车也更加需要车联网这样一个局部联网的竞争与协商协议,来保证交通效率,使驾驶者获得更好的交通体验。

现有的空管系统虽然可以实现三维空间的分配,甚至可以精确到某架飞机应当在某一秒经过某一位置,但是它的传感器(雷达)无法满足城市内部车流的定位(精度不够,扫描频率不够),而现有的飞机用的应答机专业度过高,而且车流量比飞机高几个数量级。

我听过一句话叫做“最好的机器是浑身是仪表的机器”因为只有这样的机器才能给到实时的反馈,未来的车也会这样的么

罗雨威:我觉得可能,毕竟传感器是机器的眼睛。不过不妨反向思考。如果已达到全自动驾驶,仅需将程序内置,每位客户事先通过输入参数,系统内即可生成针对所有进入车联网的车辆的行驶方案,而无需任何传感器,即可保证所有用户安全快速高效出行

杨易为:对程序猿来说这是必然,对用户来说那必须要简单明了。

在整个开发过程中遇到了哪些有趣的事情又遇到了什么困难呢?

张龙文 : 巨佬杨易为帮某位成员的 linux 装显卡驱动,最终成功的把系统搞没了。

蒟蒻曹松晖在搞 Nvidia 的 nano 时,发现 SD 卡不够大,写不下完整的映像,于是折腾缩减版映像折腾了一晚上(太蒻了)

罗雨威:拼装小车过程中,考虑如何使车辆平稳受力、避免器件间短接,在没有螺丝螺母及坚实的车架的情况下,纯手工打孔以及使用扎带将小车各部件搭成一个整体。(一件暖心事:第一晚某位成员选择在室外入睡,一位未曾透露姓名的好心人帮 ta 放置了双层睡袋。)

叶者:整个开发过程中我遇到的最大的困难是没有人拼星爸爸的外卖,作为一个咖啡深度爱好者最后只能一个人忍痛一次喝了三杯(上科大同学们打 Hackathon 居然连咖啡都不喝,太恐怖了。)哦对,还有比赛完之后出去吃了一顿不仅把奖金吃光了还没赶上火车(罗雨威:原因正是吃大餐 hhhh)

杨易为:编程很精妙,我还有很多框架和底层的东西没学,但幸好这次的 fusion api 和 solidity 可以用 python 编,省了很多力气。比较遗憾的是看文档的时间点比较晚了。那个时候大家大概都想放弃了,各种 bug 需要调,框架也没有很清晰,最终成功完赛也是一个确幸,这次总算能成为一个很好的经验。

用cuda破解ZIP存档密码


jabrown在2017年10月30日星期一提交-01:51
这将是有关如何破解受密码保护的ZIP档案的快速演练。

您将需要Hashcat和John Ripper大号。

如果使用的Linux系统没有zip2john软件包,则John Jumbo将需要编译。

将Hashcat和Ripper拆封并编译后,我们就可以获取ZIP文件的哈希值。

要获取哈希值,请运行。 / path / to / john / jumbo / run / zip2john {zip文件}

这将创建一个以冒号(:)分隔的字符串。我们仅在使用Hashcat破解哈希值时使用哈希值。

创建仅使用哈希值运行的哈希列表。 / path / to / john / jumbo / run / zip2john {zip文件} | cut -d':'-f 2> {哈希列表文件}

一旦有了哈希值,便可以使用Hashcat对其进行破解。

Hashcat命令对Winzip哈希使用单词列表攻击。 hashcat -a 0 -m 13600 {哈希列表} {wordlist}

由于Linux内核,GPU驱动程序和哈希破解系统的硬件的独特问题,我们使用的命令有所不同。

hashcat


 

只需几秒钟即可破解“ password”的简单密码。

拉链裂纹
 

使用正确的工具和单词列表,轻松破解ZIP密码非常容易。