整体架构

功能目录

docs :相关文档
libdnet-stripped :开源网络接口库
liblinear:开源大型线性分类库
liblua:开源Lua脚本语言库
libnetutil:基本的网络函数
libpcap:开源抓包库
libpcre:开源正则表达式库
macosx:xcode项目文件
mswin32:vs项目文件
nbase:Nmap封装的基础使用函数库
ncat:netcat网络工具,由Nmap实现
ndiff:比较Nmap扫描结果的实用命令
nmap-update:负责Nmap更新操作
nping:Nmap项目组实现的新版的Hping,探测与构建包
nselib:Nmap的Lua脚本
nsock:Nmap实现的并行的SocketEvent处理库
scripts:Nmap提供常用的扫描检查的lua脚本
todo:开发任务
zenmap:python的图形界面程序

主体程序逻辑

入口程序在main.cc,主要功能

  • 检查环境变量NMAP_ARGS
  • 检查有没有–resume参数
  • 判断是resume之前扫描,还是新请求

然后是根据传入参数去调用 nmap.cc的nmap_main()函数。下面是精简后的源码:


int main(int argc, char *argv[]) {char command[2048];int myargc;char **myargv = NULL;char *cptr;int ret;int i;set_program_name(argv[0]);if ((cptr = getenv("NMAP_ARGS"))) {if (Snprintf(command, sizeof(command), "nmap %s", cptr) >= (int) sizeof(command)) {error("Warning: NMAP_ARGS variable is too long, truncated");}/* copy rest of command-line arguments */for (i = 1; i < argc && strlen(command) + strlen(argv[i]) + 1 < sizeof(command); i++) {strcat(command, " ");strcat(command, argv[i]);}myargc = arg_parse(command, &myargv);if (myargc < 1) {fatal("NMAP_ARGS variable could not be parsed");}ret = nmap_main(myargc, myargv);arg_parse_free(myargv);return ret;}if (argc == 3 && strcmp("--resume", argv[1]) == 0) {if (gather_logfile_resumption_state(argv[2], &myargc, &myargv) == -1) {fatal("Cannot resume from (supposed) log file %s", argv[2]);}return nmap_main(myargc, myargv);}return nmap_main(argc, argv);
}

然后程序教育nmap_main().
nmap_main里,表面看起来扫描的循环是从2065行开始:

for (targetno = 0; targetno < Targets.size(); targetno++) {
currenths = Targets[targetno];
前后的代码都比较多,下次再抽时间细致分析。

这里引用一个别人做的流程图:

主体程序位置在nmap.cc内的nmap_main函数

新建一个主机的单例对象

#ifndef NOLUA/* Only NSE scripts can add targets */NewTargets *new_targets = NULL;/* Pre-Scan and Post-Scan script results datastructure */ScriptResults *script_scan_results = NULL;
#endif

开始主程序

Target类

target.cc定义的是主机的类,扫描信息也是保存在target对象。nmap_main创建target时,使用了单例模式。

int nmap_main(int argc, char *argv[]) {int i;std::vector<Target *> Targets;time_t now;struct hostent *target = NULL;time_t timep;char mytime[128];struct addrset *exclude_group;
#ifndef NOLUA/* Only NSE scripts can add targets */NewTargets *new_targets = NULL;/* Pre-Scan and Post-Scan script results datastructure */ScriptResults *script_scan_results = NULL;
#endifunsigned int ideal_scan_group_sz = 0;Target *currenths;char myname[FQDN_LEN + 1];int sourceaddrwarning = 0; /* Have we warned them yet about unguessablesource addresses? */unsigned int targetno;char hostname[FQDN_LEN + 1] = "";struct sockaddr_storage ss;size_t sslen;#ifdef LINUX/* Check for WSL and warn that things may not go well. */struct utsname uts;if (!uname(&uts)) {if (strstr(uts.release, "Microsoft") != NULL) {error("Warning: %s may not work correctly on Windows Subsystem for Linux.\n""For best performance and accuracy, use the native Windows build from %s/download.html#windows.",NMAP_NAME, NMAP_URL);}}
#endifnow = time(NULL);local_time = localtime(&now);if (o.debugging)nbase_set_log(fatal, error);elsenbase_set_log(fatal, NULL);if (argc < 2){printusage();exit(-1);}Targets.reserve(100);
#ifdef WIN32win_pre_init();
#endif// 命令行参数解析printf("命令行参数解析\n");parse_options(argc, argv);// Linux平台设置只读非堵塞printf("Linux平台设置只读非堵塞\n");tty_init(); // Put the keyboard in raw mode#ifdef WIN32// Must come after parse_options because of --unprivileged// Must come before apply_delayed_options because it sets o.isr00twin_init();
#endif// 延迟处理的操作printf("延迟处理的操作\n");apply_delayed_options();/*
这里用到的变量route_dst_hosts是由参数 --route-dst debugging模式定义的目标列表。定义如下:
static std::vector<std::string> route_dst_hosts;
前面命令行解析后会对其赋值。
*/for (unsigned int i = 0; i < route_dst_hosts.size(); i++) {const char *dst;struct sockaddr_storage ss;struct route_nfo rnfo;size_t sslen;int rc;dst = route_dst_hosts[i].c_str();printf("解析参数 route_dst_hosts:%s\n", dst);// 解析目标printf("解析目标\n");rc = resolve(dst, 0, &ss, &sslen, o.af());if (rc != 0)fatal("Can't resolve %s: %s.", dst, gai_strerror(rc));printf("%s\n", inet_ntop_ez(&ss, sslen));if (!route_dst(&ss, &rnfo, o.device, o.SourceSockAddr())) {printf("Can't route %s (%s).", dst, inet_ntop_ez(&ss, sslen));} else {printf("%s %s", rnfo.ii.devname, rnfo.ii.devfullname);printf(" srcaddr %s", inet_ntop_ez(&rnfo.srcaddr, sizeof(rnfo.srcaddr)));if (rnfo.direct_connect)printf(" direct");elseprintf(" nexthop %s", inet_ntop_ez(&rnfo.nexthop, sizeof(rnfo.nexthop)));}printf("\n");}route_dst_hosts.clear();if (delayed_options.iflist) {print_iflist();exit(0);}/* If he wants to bounce off of an FTP site, that site better damn well be reachable! */// FTP bounce scan模式,nmap -b参数定义if (o.bouncescan) {printf("nmap -b参数\n");if (!inet_pton(AF_INET, ftp.server_name, &ftp.server)) {if ((target = gethostbyname(ftp.server_name)))memcpy(&ftp.server, target->h_addr_list[0], 4);else {fatal("Failed to resolve FTP bounce proxy hostname/IP: %s",ftp.server_name);}} else if (o.verbose) {log_write(LOG_STDOUT, "Resolved FTP bounce attack proxy to %s (%s).\n",ftp.server_name, inet_ntoa(ftp.server));}}fflush(stdout);fflush(stderr);timep = time(NULL);// 扫描的简要信息 记录到xmlStrncpy(mytime, ctime(&timep), sizeof(mytime));chomp(mytime);if (!o.resuming) {/* Brief info in case they forget what was scanned */char *xslfname = o.XSLStyleSheet();xml_start_document("nmaprun");if (xslfname) {xml_open_pi("xml-stylesheet");xml_attribute("href", "%s", xslfname);xml_attribute("type", "text/xsl");xml_close_pi();xml_newline();}xml_start_comment();xml_write_escaped(" %s %s scan initiated %s as: %s ", NMAP_NAME, NMAP_VERSION, mytime, join_quoted(argv, argc).c_str());xml_end_comment();xml_newline();xml_open_start_tag("nmaprun");xml_attribute("scanner", "nmap");xml_attribute("args", "%s", join_quoted(argv, argc).c_str());xml_attribute("start", "%lu", (unsigned long) timep);xml_attribute("startstr", "%s", mytime);xml_attribute("version", "%s", NMAP_VERSION);xml_attribute("xmloutputversion", NMAP_XMLOUTPUTVERSION);xml_close_start_tag();xml_newline();output_xml_scaninfo_records(&ports);xml_open_start_tag("verbose");xml_attribute("level", "%d", o.verbose);xml_close_empty_tag();xml_newline();xml_open_start_tag("debugging");xml_attribute("level", "%d", o.debugging);xml_close_empty_tag();xml_newline();} else {xml_start_tag("nmaprun", false);}// 记录扫描日志printf("记录扫描日志\n");log_write(LOG_NORMAL | LOG_MACHINE, "# ");log_write(LOG_NORMAL | LOG_MACHINE, "%s %s scan initiated %s as: %s", NMAP_NAME, NMAP_VERSION, mytime, join_quoted(argv, argc).c_str());log_write(LOG_NORMAL | LOG_MACHINE, "\n");/* Before we randomize the ports scanned, lets output them to machineparseable output */// 在随机端口扫描前,把可以解析的端口输出机器if (o.verbose){printf("在随机端口扫描前,把可以解析的端口输出机器\n");output_ports_to_machine_parseable_output(&ports);}#if defined(HAVE_SIGNAL) && defined(SIGPIPE)signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE so our program doesn't crash becauseof it, but we really shouldn't get an unexpectedSIGPIPE */
#endifif (o.max_parallelism && (i = max_sd()) && i < o.max_parallelism) {error("WARNING: Your specified max_parallel_sockets of %d, but your system says it might only give us %d.  Trying anyway", o.max_parallelism, i);}// 端口号是否溢出if (o.debugging > 1){printf("端口号是否溢出\n");log_write(LOG_STDOUT, "The max # of sockets we are using is: %d\n", o.max_parallelism);}// At this point we should fully know our timing parametersif (o.debugging) {log_write(LOG_PLAIN, "--------------- Timing report ---------------\n");log_write(LOG_PLAIN, "  hostgroups: min %d, max %d\n", o.minHostGroupSz(), o.maxHostGroupSz());log_write(LOG_PLAIN, "  rtt-timeouts: init %d, min %d, max %d\n", o.initialRttTimeout(), o.minRttTimeout(), o.maxRttTimeout());log_write(LOG_PLAIN, "  max-scan-delay: TCP %d, UDP %d, SCTP %d\n", o.maxTCPScanDelay(), o.maxUDPScanDelay(), o.maxSCTPScanDelay());log_write(LOG_PLAIN, "  parallelism: min %d, max %d\n", o.min_parallelism, o.max_parallelism);log_write(LOG_PLAIN, "  max-retries: %d, host-timeout: %ld\n", o.getMaxRetransmissions(), o.host_timeout);log_write(LOG_PLAIN, "  min-rate: %g, max-rate: %g\n", o.min_packet_send_rate, o.max_packet_send_rate);log_write(LOG_PLAIN, "---------------------------------------------\n");}/* Before we randomize the ports scanned, we must initialize PortList class. */// 端口与地址初始化if (o.ipprotscan){printf("端口与地址初始化\n");PortList::initializePortMap(IPPROTO_IP,  ports.prots, ports.prot_count);}if (o.TCPScan())PortList::initializePortMap(IPPROTO_TCP, ports.tcp_ports, ports.tcp_count);if (o.UDPScan())PortList::initializePortMap(IPPROTO_UDP, ports.udp_ports, ports.udp_count);if (o.SCTPScan())PortList::initializePortMap(IPPROTO_SCTP, ports.sctp_ports, ports.sctp_count);// 打乱端口顺序if (o.randomize_ports) {printf("打乱端口顺序\n");if (ports.tcp_count) {shortfry(ports.tcp_ports, ports.tcp_count);// move a few more common ports closer to the beginning to speed scan// 常见端口往前放printf("常见端口往前放\n");random_port_cheat(ports.tcp_ports, ports.tcp_count);}if (ports.udp_count)shortfry(ports.udp_ports, ports.udp_count);if (ports.sctp_count)shortfry(ports.sctp_ports, ports.sctp_count);if (ports.prot_count)shortfry(ports.prots, ports.prot_count);}// --exclude_group 命令行参数:排除地址处理(排除主机或网络)printf("--exclude_group 命令行参数:排除地址处理(排除主机或网络)\n");exclude_group = addrset_new();/* lets load our exclude list */if (o.excludefd != NULL) {load_exclude_file(exclude_group, o.excludefd);fclose(o.excludefd);}if (o.exclude_spec != NULL) {load_exclude_string(exclude_group, o.exclude_spec);}if (o.debugging > 3)dumpExclude(exclude_group);// NES 环境
printf("NES 环境\n");
#ifndef NOLUAif (o.scriptupdatedb) {o.max_ips_to_scan = o.numhosts_scanned; // disable warnings?}// 版本扫描if (o.servicescan){printf("版本扫描\n");o.scriptversion = true;}if (o.scriptversion || o.script || o.scriptupdatedb)open_nse();/* Run the script pre-scanning phase */// 预分析扫描if (o.script) {printf("预分析扫描\n");new_targets = NewTargets::get();script_scan_results = get_script_scan_results_obj();script_scan(Targets, SCRIPT_PRE_SCAN);printscriptresults(script_scan_results, SCRIPT_PRE_SCAN);while (!script_scan_results->empty()) {script_scan_results->front().clear();script_scan_results->pop_front();}}
#endifif (o.ping_group_sz < o.minHostGroupSz())o.ping_group_sz = o.minHostGroupSz();// hstate 是一个list,初始为空,循环执行后保存各主机表达式字符串地址HostGroupState hstate(o.ping_group_sz, o.randomize_hosts, argc, (const char **) argv);// 主程序循环do {// 计算 host group 大小ideal_scan_group_sz = determineScanGroupSize(o.numhosts_scanned, &ports);// 主机发现成功,同加入到 host group,再后续处理while (Targets.size() < ideal_scan_group_sz) {o.current_scantype = HOST_DISCOVERY;// 主机发现currenths = nexthost(&hstate, exclude_group, &ports, o.pingtype);// 如果没有发现主机,就进行下一次循环if (!currenths)break;if (currenths->flags & HOST_UP && !o.listscan)o.numhosts_up++;if ((o.noportscan && !o.traceroute
#ifndef NOLUA&& !o.script
#endif) || o.listscan) {/* We're done with the hosts */// 如果 命令行参数-sn(不进行端口扫描) 且没有指定traceroute和脚本的话,扫描结束// 如果 -sL(只列出ip),扫描也结束if (currenths->flags & HOST_UP || (o.verbose && !o.openOnly())) {xml_start_tag("host");write_host_header(currenths);printmacinfo(currenths);//  if (currenths->flags & HOST_UP)//  log_write(LOG_PLAIN,"\n");printtimes(currenths);xml_end_tag();xml_newline();log_flush_all();}delete currenths;o.numhosts_scanned++;if (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + Targets.size())continue;elsebreak;}// -S ip (配置要伪造的IP)if (o.spoofsource) {printf("-S ip (配置要伪造的IP)\n");o.SourceSockAddr(&ss, &sslen);currenths->setSourceSockAddr(&ss, sslen);}/* I used to check that !currenths->weird_responses, but in somerare cases, such IPs CAN be port successfully scanned and evenconnected to */// 一些情况下,主机有返回状态,全状态为HOST_DOWNif (!(currenths->flags & HOST_UP)) {printf("一些情况下,主机有返回状态,全状态为HOST_DOWN\n");if (o.verbose && (!o.openOnly() || currenths->ports.hasOpenPorts())) {xml_start_tag("host");write_host_header(currenths);xml_end_tag();xml_newline();}delete currenths;o.numhosts_scanned++;if (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + Targets.size())continue;elsebreak;}// RawScan ,如SYN/FIN/ARPif (o.RawScan()) {printf("RawScan ,如SYN/FIN/ARP \n");if (currenths->SourceSockAddr(NULL, NULL) != 0) {if (o.SourceSockAddr(&ss, &sslen) == 0) {// 直接设置IPprintf("直接设置IP\n");currenths->setSourceSockAddr(&ss, sslen);} else {// 解析主机名printf("解析主机名\n");if (gethostname(myname, FQDN_LEN) ||resolve(myname, 0, &ss, &sslen, o.af()) != 0)fatal("Cannot get hostname!  Try using -S <my_IP_address> or -e <interface to scan through>\n");o.setSourceSockAddr(&ss, sslen);currenths->setSourceSockAddr(&ss, sslen);if (! sourceaddrwarning) {error("WARNING: We could not determine for sure which interface to use, so we are guessing %s .  If this is wrong, use -S <my_IP_address>.",inet_socktop(&ss));sourceaddrwarning = 1;}}}// 网络设备(网卡)名称if (!currenths->deviceName())fatal("Do not have appropriate device name for target");/* Hosts in a group need to be somewhat homogeneous. Put this host inthe next group if necessary. See target_needs_new_hostgroup for thedetails of when we need to split. */// 同一个组内主机要是同性质的,这里判断目标是否加到list列表内if (Targets.size() && target_needs_new_hostgroup(&Targets[0], Targets.size(), currenths)) {printf("同一个组内主机要是同性质的,这里判断目标是否加到list列表内\n");returnhost(&hstate);o.numhosts_up--;break;}o.decoys[o.decoyturn] = currenths->source();}Targets.push_back(currenths);}// 没有发现主机if (Targets.size() == 0){printf("没有发现主机, break\n");break; /* Couldn't find any more targets */}// Set the variable for status printingo.numhosts_scanning = Targets.size();// Our source must be set in decoy list because nexthost() call can// change it (that issue really should be fixed when possible)if (o.RawScan()){printf("Raw扫描:RawScan\n");o.decoys[o.decoyturn] = Targets[0]->source();}/* I now have the group for scanning in the Targets vector */// 定义了端口扫描,进入扫描的主体if (!o.noportscan) {printf("定义了端口扫描,进入扫描的主体\n");// Ultra_scan sets o.scantype for us so we don't have to worryif (o.synscan){printf("syn扫描:synscan\n");ultra_scan(Targets, &ports, SYN_SCAN);}if (o.ackscan){printf("ack扫描:acksan\n");ultra_scan(Targets, &ports, ACK_SCAN);}if (o.windowscan){printf("windows扫描:windowscan\n");ultra_scan(Targets, &ports, WINDOW_SCAN);}if (o.finscan){printf("fin扫描:finscan\n");ultra_scan(Targets, &ports, FIN_SCAN);}if (o.xmasscan){printf("xmas扫描:xmasscan\n");ultra_scan(Targets, &ports, XMAS_SCAN);}if (o.nullscan){printf("空扫描:nullscan\n");ultra_scan(Targets, &ports, NULL_SCAN);}if (o.maimonscan){printf("maimon 扫描:maimonscan\n");ultra_scan(Targets, &ports, MAIMON_SCAN);}if (o.udpscan){printf("udp扫描:udpscan\n");ultra_scan(Targets, &ports, UDP_SCAN);}if (o.connectscan){printf("连接扫描:connectscan\n");ultra_scan(Targets, &ports, CONNECT_SCAN);}if (o.sctpinitscan){printf("sctp init 扫描:sctpinitscan\n");ultra_scan(Targets, &ports, SCTP_INIT_SCAN);}if (o.sctpcookieechoscan){printf("sctp cookit 回显扫描:sctpcookieechoscan\n");ultra_scan(Targets, &ports, SCTP_COOKIE_ECHO_SCAN);}if (o.ipprotscan){printf("ip端口扫描:ipprotscan\n");ultra_scan(Targets, &ports, IPPROT_SCAN);}/* These lame functions can only handle one target at a time */// 这些蹩脚的函数一次只能处理一个目标if (o.idlescan) {printf("idlescan:这些蹩脚的函数一次只能处理一个目标\n");for (targetno = 0; targetno < Targets.size(); targetno++) {o.current_scantype = IDLE_SCAN;keyWasPressed(); // Check if a status message should be printedidle_scan(Targets[targetno], ports.tcp_ports,ports.tcp_count, o.idleProxy, &ports);}}if (o.bouncescan) {printf("bouncescan:这些蹩脚的函数一次只能处理一个目标\n");for (targetno = 0; targetno < Targets.size(); targetno++) {o.current_scantype = BOUNCE_SCAN;keyWasPressed(); // Check if a status message should be printedif (ftp.sd <= 0)ftp_anon_connect(&ftp);if (ftp.sd > 0)bounce_scan(Targets[targetno], ports.tcp_ports, ports.tcp_count, &ftp);}}// 服务扫描if (o.servicescan) {printf("servicescan:服务扫描\n");o.current_scantype = SERVICE_SCAN;service_scan(Targets);}}// 系统扫描if (o.osscan) {printf("osscan:系统扫描\n");OSScan os_engine;os_engine.os_scan(Targets);}if (o.traceroute){printf("traceroute:跟踪路由\n");traceroute(Targets);}#ifndef NOLUAif (o.script || o.scriptversion) {printf("script:脚本扫描\n");script_scan(Targets, SCRIPT_SCAN);}
#endif// 输出扫描结果for (targetno = 0; targetno < Targets.size(); targetno++) {printf("输出扫描结果\n");currenths = Targets[targetno];/* Now I can do the output and such for each host */if (currenths->timedOut(NULL)) {xml_open_start_tag("host");xml_attribute("starttime", "%lu", (unsigned long) currenths->StartTime());xml_attribute("endtime", "%lu", (unsigned long) currenths->EndTime());xml_close_start_tag();write_host_header(currenths);xml_end_tag(); /* host */xml_newline();log_write(LOG_PLAIN, "Skipping host %s due to host timeout\n",currenths->NameIP(hostname, sizeof(hostname)));log_write(LOG_MACHINE, "Host: %s (%s)\tStatus: Timeout\n",currenths->targetipstr(), currenths->HostName());} else {/* --open means don't show any hosts without open ports. */if (o.openOnly() && !currenths->ports.hasOpenPorts())continue;xml_open_start_tag("host");xml_attribute("starttime", "%lu", (unsigned long) currenths->StartTime());xml_attribute("endtime", "%lu", (unsigned long) currenths->EndTime());xml_close_start_tag();write_host_header(currenths);printportoutput(currenths, &currenths->ports);printmacinfo(currenths);printosscanoutput(currenths);printserviceinfooutput(currenths);
#ifndef NOLUAprinthostscriptresults(currenths);
#endifif (o.traceroute)printtraceroute(currenths);printtimes(currenths);log_write(LOG_PLAIN | LOG_MACHINE, "\n");xml_end_tag(); /* host */xml_newline();}}log_flush_all();o.numhosts_scanned += Targets.size();/* Free all of the Targets */while (!Targets.empty()) {currenths = Targets.back();delete currenths;Targets.pop_back();}o.numhosts_scanning = 0;} while (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned);#ifndef NOLUAif (o.script) {script_scan(Targets, SCRIPT_POST_SCAN);printscriptresults(script_scan_results, SCRIPT_POST_SCAN);while (!script_scan_results->empty()) {script_scan_results->front().clear();script_scan_results->pop_front();}delete new_targets;new_targets = NULL;}
#endifaddrset_free(exclude_group);if (o.inputfd != NULL)fclose(o.inputfd);printdatafilepaths();printfinaloutput();free_scan_lists(&ports);eth_close_cached();if (o.release_memory) {nmap_free_mem();}return 0;
}

Nmap源码分析(整体架构)相关推荐

  1. PX4 Autopilot源码分析 - 总体架构

    PX4 Autopilot源码分析 - 总体架构 应用场景 单独飞控 飞控+任务计算机场景 软件架构 译自PX4 user guide,原文请参阅: https://docs.px4.io/maste ...

  2. Nmap源码分析(脚本引擎)

    Nmap提供了强大的脚本引擎(NSE),以支持通过Lua编程来扩展Nmap的功能.目前脚本库已经包含300多个常用的Lua脚本,辅助完成Nmap的主机发现.端口扫描.服务侦测.操作系统侦测四个基本功能 ...

  3. Nmap源码分析(基本框架)

    Nmap是一款非常强大的开源扫描工具.自己在使用过程中忍不住想仔细阅读一下它的源码.源码里面汇集了众多安全专家的精巧设计与优雅写法,读起来令人心旷神怡而又受益匪浅. 这里我们以阅读nmap6.0的代码 ...

  4. spring transaction源码分析--事务架构

    1. 引言  事务特性 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性.事 ...

  5. FairFuzz 论文简读+源码分析+整体流程简述

    FairFuzz: A Targeted Mutation Strategy for Increasing Greybox Fuzz Testing Coverage 一.论文阅读 Abstract ...

  6. Backbone源码分析-Backbone架构+流程图

    作者:nuysoft/高云/nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. Backbone0.9.1源码分析分析系列 jQuery1.6.1源码分析系 ...

  7. 一步步去阅读koa源码,整体架构分析

    阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想.大到架构设计,小到可取的命名风格,还有设计模式.实现某类功能使用到的数据结构和算法等等. 使用koa 其实某个框架阅读源码的时候,首 ...

  8. 【深入浅出jQuery】源码浅析--整体架构(转)

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. Nmap源码分析(服务与版本扫描)

    在进行端口扫描后,Nmap可以进一步探测出运行在端口上的服务类型及应用程序的版本.目前Nmap可以识别几千种服务程序的签名(Signature),覆盖了180多种应用协议.比如,端口扫描检测到80端口 ...

最新文章

  1. C# 学习笔记(9)线程
  2. Web高级征程:《大型网站技术架构》读书笔记系列
  3. 最大功率点跟踪_ADI公司推出集成最大功率点跟踪和I2C的80V降压升压电池充电控制器...
  4. 矩阵连乘 动态规划 详解
  5. linux中rlwrap安装
  6. 其它 博客园 自己写的文章 标题含有小写字母 查看文章时 标题就变成大写的了...
  7. AD批量修改电阻封装记得按CTRL+A
  8. 怎么做应力应变曲线_做了这么多年材料,这些力学性能测试你做对了吗?
  9. mybatis 中collection中需要 open close separator
  10. 安卓手机修改音量键为HOME和BACK
  11. 深度学习方面的论文整理
  12. oracle中LOB字段相关概念(自动创建LOB索引段和重建索引方法)
  13. 针对大学城的二手书市场
  14. 外网访问arm嵌入式linux系统_嵌入式Linux系统移植的四大步骤
  15. 初学者,简单易用的猎码安卓中文编程开发工具!
  16. 求解多元非线性方程组的解(功分器dB值与功分比转化)
  17. PHP Unicode编码与解码_Unicode转中文_中文转Unicode字符
  18. (plugin uglify) Error: Unexpected token: keyword «const»
  19. 你真的会做小程序按钮吗?看了字节35K前端的样式设计,悟了
  20. 暗黑破坏神3ptr服务器位置,暗黑破坏神3——野蛮人1.05 PTR服务器测评 附BOSS攻略...

热门文章

  1. 精选| 2021年5月R新包推荐(第54期)
  2. 本地安装UCSC基因组浏览器
  3. 从 Windows 换到 Mac,真没有想象中的那么难
  4. 1268:【例9.12】完全背包问题
  5. canvas笔记-图形变换(位移translate、缩放scale、变换矩阵transform)
  6. Java获取方法信息(某一函数,方法也是一个对象)
  7. Java工作笔记-使用Hibernate连接mysql数据库并进行增、删、改、查!
  8. C++ STL string的构造函数
  9. 微机个人笔记-存储单元编址
  10. 32岁妈妈适合转行做软件测试吗,32+大龄妈妈,AMH偏低,可丽蓝助力好孕一次就中...