Ура!是HTTP/2!

几个月之前就想给站点加上HTTPS了,我这么个无名小站都被强制上过一次广告(看自己网站的时候发现的= =)。万一变成人家的支付宝红包/淘宝活动/并夕夕推广重灾区了怎么办。直到今天才下定决心来解决这件事情,唉,拖延症病入膏肓啊。

初次接触到HTTP/2这个技术是某一次在看某v开头(咳咳)的魔法工具的时候,见到了Akamai公司做的一个示例。至于Akamai是个啥公司呢,接触过烂橘子的朋友肯定知道,这是一家CDN提供商,提供的服务主要是网络资源的托管一类的。(举个类似的例子,七牛云?)再加上Wappalyzer里面那个蓝色的小闪电看起来好炫!于是就自己动手,丰衣足食了。

其实整个过程中步骤十分简单,大致可以描述如下:

  1. 下载用于申请免费SSL证书的certbot-auto脚本;
  2. certbot-auto -d <domain-name>用于给指定域名申请SSL证书,如果用的是nginx,而且配置文件不是放在/etc/nginx.conf的环境下的话(LNMP说的就是你),可以选择在certbot-auto后添加certonly参数来表示只需要该脚本来申请证书,配置什么的由自己来;
  3. ssl on语句告诉nginx该server需要启用SSL协议;
  4. 如果用的是certbot-auto certonly的话,申请成功后会把证书的地址告诉用户,将该地址添加到nginx对应server的配置中即可。ssl_certificatessl_certificate_key对应的分别是证书的公钥和私钥,使用certbot-auto生成后两者的名字对应为fullchain.pemprivkey.pem,记得不要搞错了;
  5. listen参数的端口从80改为443,再在后面添加ssl http2参数,用于启用HTTP/2;
  6. 新建一个server,监听相同的地址的80端口,然后加一句:
    return 301 https://$server_name$request_uri;

    以使得访问该网站的用户都强制使用HTTPS。当然,如果浏览器支持HTTP/2的话,会自动启用该协议的;

  7. 可以添加这么两行:
    ssl_stapling on;
    ssl_stapling_verify on;

    来缩短SSL证书的查询时间,提高用户体验,具体信息请直接搜ssl stapling。

然后整个人就卡在了第二步,说是什么tk域名的CAA记录查不到,超时,卡了一上午,中午都差点头疼的午睡没睡着。。后来发现是云主机默认的DNS服务器有问题,加了一个Google的8888服务器问题马上解决,我tm。。。

最后顺带提一句,之前在网上看到过HTTP/2本身其实是可以不用SSL协议的,但是网络安全是大势所趋,所以基本上上了HTTP/2的都会用SSL,甚至两者变成了必然关系,很少有见到裸HTTP/2的配置教程了。

好了,又到了快乐的C++快速上手时间 之前在这里讲过实际中.cpp.h的一种用法,然后这次涉及到了模板函数template。模板函数与传统函数不同,并无法直接使用,需要先传入所给定的参数类型,C++才能根据类型推断出该函数体实际所需要执行的指令。所以,应该将模板函数的函数体在头文件中实现。不然的话,比如说在Visual Studio中,若是还是傻傻地(像我一样)在头文件定义模板函数,然后跑到源文件中定义函数体的话,就会报出LNK2019,也就是常见的找不到函数引用的错误。

拓展阅读 -> https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file

根据书上写的,判断一个文法是否为LL(1)文法有三点。将其实际作用结合个人理解记录如下。

  1. [文法]没有左递归;
  2. 同一非终结符推导出的多个[右侧]的FIRST集合的交集为空(显然只有一个右侧的产生式不会违反这条规则);
  3. 若[某一产生式]可以推出ε,则要求该[产生式左侧]的[FIRST集合]和[FOLLOW集合]的交集为空。

具体解释如下: 通过结合LL文法的[预测分析程序]这个实现方法的[预测分析表]可以比较直观地进行解释。LL(1)由于前看符号只有一位,可将其视作[行]为单一[非终结符],[列]为单一[终结符]的一张表格。将表格由每一固定行、列号确定的空间称作一个格子的话,则格子里放的是产生式。显然,为了由当前的分析过程和下一符号确定产生时的推导方向的话,每一个格子里只应该放最多一个产生式。当然,空下来的格子放的都是出错标记,因为当前文法不允许在该情况下进行推导。而上面的三条规则解决了如下问题:

  1. 第一条解决了格子跳转的位置问题。左递归加上最左推导,会将自身不断添加到当前推导过程的最左侧,也就是格子的跳转结果指向自己。由于文法固定,格子的内容是不会变化的,这个时候就相当于跳进了一个while(1)的循环中。而显然这样的推导是失败的,因为它始终不会有结果。通过禁止左递归,避免了跳转过程中出现某些环的问题。(并非不允许环的存在,只是因为在不读入字符的情况下的环会导致无限死循环,所以只能说是禁止了“有害的”环的存在。)
  2. 第二条解决了格子里放产生式的数量问题。只有在同一非终结符、同一输入字符的时候,才会出现多个产生式放在同一格子的问题。预测分析表的部分生成工作可以看作这么一个过程:
    FOR <EVERY Non-terminal P>
      FOR <EVERY Pattern α IN Production P->α1|α2|...>
          FOR <EVERY Terminal e IN FIRST(α)>
              prediction_table[P][e] = P->α

    其中P表示任意非终结符,α表示P的任意单个产生式右部,e表示α的FIRST集合里的任意元素(显然是终结符)。这么一看很容易发现,表格只有二维,而循环有三层,按照概率学的角度来说,肯定是允许在不同的α取值中,遇到同一个e的(即允许不同推导右侧的FIRST集合里有相同终结符)。从程序的角度上来说,格子里的原值要被覆盖,因为设计时是要求能够根据P和e唯一确定一个操作的;而从语法的角度上来说,格子的原值要保留,因为这代表了一条语法规则,覆盖的话会导致语法发生变化。这就产生了矛盾。所以,为了从根本上避免该情况的产生,要求在语法设计时就要避免格子出现重复赋值的问题。回到上面,不难发现解决方法之一就是要求不同的FIRST(α)里面不能有相同的e。而用专业术语来表达的话,这正是第二条规则。

  3. 第三条同样解决了格子里放产生式数量的问题。实际上,FOLLOW集合同样是要在预测分析表里放产生式。FOLLOW集合的目的是,告诉预测分析程序,在允许使用ε(即空字)合法结束当前产生式推导的同时,保证会有下一产生式,能够从当前的输入字符开始,开始新的推导。因而同样要在预测分析表里插入标记以指导跳转:
    FOR <EVERY Non-terminal P>
      IF <EXISTS P->ε>
          FOR <EVERY Terminal e IN FOLLOW(P)>
              prediction_table[P][e] = P->ε

    相比于第二条中的工作,这里只有两层循环,因而工作内部不会出现任何问题(因为根据集合的性质,空集作为元素是不可能重复的呀)。但是该段代码与第二条中操作的是同一个预测分析表啊!因而又可能出现格子覆盖的问题。显然,解决方法又是只要两者没有相同的e就行了。这里的两者指的是FOLLOW(P)和FIRST(P)。虽然第二条的代码段中使用的是FIRST(αi),但是在认为所有元素都等价的时候(即不需要再区分每个e对应的α),完全可以算一下,FIRST(αi)并起来就是FIRST(P)。(甚至不用算好吧)

今天来当当自己的课代表:

  1. “文法没有左递归”解决了在预测分析表中无限循环跳转的问题;
  2. 对于P->α1|α2|...∩(αi)=∅解决了在一个预测分析表里放多个非空产生式的冲突问题;
  3. 对于存在P->εFIRST(P) ∩ FOLLOW(P)=∅解决了在一个预测分析表里空产生式与非空产生式的冲突问题。

Windows的局域网文件共享用的是SMB服务,使用139和445这两个端口。(没错,就是WannaCry最喜欢的那个)

之前被WannaCry病毒搞怕了,于是手动建立了两条防火墙规则,屏蔽了139和445端口的TCP和UDP连接,而最近需要局域网传文件,又得把这个服务打开。

开了之后结果没有任何反应,用局域网ip访问自己都说是远程服务器不响应连接。然后telnet了一下,连接失败。后来考虑看看是不是服务的问题,用了本地回环地址,结果居然能telnet连接,更是可以用资源管理器访问。然后由于有局域网远程连接的需要,有线网卡上配置了一个单独的网段,结果用这个ip可以访问到共享文件。(emm,日常上网用的是无线网卡)

然后咕果上查了一大堆,有以下几个解决方案:

  1. 检查Server和Workstation服务;
  2. 检查Lanmanserver服务;
  3. 检查Print Spooler服务;
  4. 重启网卡服务协议。

除开2看起来不像是Windows自带的服务之外,其余的依次从头到尾试了一遍。最后一个重启网卡服务协议是啥呢,一个在巨硬论坛上找到的邪门方法。说是先把网卡-属性-Microsoft网络的文件和打印机共享勾去掉,保存,再重新点进来勾上就好了。但是在我的实际使用过程中,并未奏效。到是啥呢,顺手禁用然后重新启用了一下网卡,然后就可以访问了。

然后就可以访问了。

??????

然后就可以访问了???这是什么神仙操作???

(顺便说一句,在测试各种解决方案之前重启过很多次计算机,然而并没有用)

最近在做视频帧处理(前景提取),显然这个要求视频帧按照顺序输出。帧的处理顺序没有关系,因为没有使用推测功能。(要是开了帧预测的话,显然就只能顺序处理了,由于帧之间有依赖关系,所以最多只能做单帧内部的处理优化)

所以本次的思路基本是,寻找一个可以乱序执行,但是一定要能够按照任务的加入顺序获取执行结果的模型。看了一下,直接用threading.Thread,没有看到简洁地获取执行结果的方法;而用multithreading.pool.Pool,使用map函数的callback参数注册回调,在之前的尝试中没有成功过。然后在StackOverflow上看到有人推荐concurrent.futures.ThreadPoolExecutor。在Python里看到future这个词往往都是有惊喜的,这次也不例外。大致流程如下:

使用pool = ThreadPoolExecutor(maxThreadCount)注册一个全新的线程池 -> 使用pool.submit(function, args...)提交一个任务,并获得类似于线程句柄一类的返回对象,可以用来控制线程,包括在运行之前取消该任务、获取运行状态(是否完成)以及获取完成的返回值等。这几乎满足了之前提出的需求:只需要再自建一个满足先进先出的队列,就可以实现按帧的顺序读取了。

框架大致如下:

import multiprocessing as mp
from threading import Thread
from concurrent.futures import ProcessPoolExecutor

processPool = ProcessPoolExecutor(max(mp.cpu_count(), 1))
procList = []
maxQueueLength = 10

def produce(...):
    # 用来执行具体工作,比如处理单帧视频
    ...

def producer_func(...):
    # 用来控制多线程生成和执行情况的“主线程”,调用produce函数
    <condition-loop>
        ....
        # 确保队列不会过长,节省资源,时间换空间
        while len(procList) > maxQueueLength:
            continue
        procList.append(processPool.submit(produce, (...)))

if __name__ == '__main__':
    # 不阻塞主线程,将后续的任务还要放在这里执行,比如图像的显示
    Thread(target=producer_func, ...).start()

    while True:
        if len(procList) <= 0:
            continue
        elif not procList[0].done():
            continue
        res = procList[0].result()
        ...
        if <condition>:
            break