如何用msys2编译最新版redis的Windows版

最近有个需求要用redis一些新特性,比如什么ZMPOP之类的。看了一下开发机上的redis版本居然还在3.x。

虽然我也不会用Windows上生产环境,但是为了redis专门起一个docker或者虚拟机也不太开心。

那还是自己编译吧。

以前我记得我用cygwin编译过redis 5.0.5。cygwin操作还是有点复杂了,不适合给别人教学。今天我们来用个简单的东西:msys2。

以下是编译安装步骤,真的很简单。跟着步骤做总能成功的。

  1. 在msys2的首页下载最新版的msys2。

  2. 安装好后启动msys2(默认位置是C:\msys64\msys2.exe)

    注:如果希望用Windows Terminal启动msys2,配置文件中的可执行文件应该填写C:/msys64/msys2_shell.cmd -defterm -here -no-start -msys

  3. 首先执行pacman -Syu来更新软件包

  4. 安装gcc和make,我们一会用它来编译pacman -S msys/gcc msys/make

    注:如果像我一样使用proxifier代理,你需要代理pacman.exe

  5. 个人比较喜欢vim,这里也装一下,省的一会还要回windows开VSCode。

    pacman -S vim

    然后vim /usr/include/dlfcn.h打开/usr/include/dlfcn.h,我们把49行的__GNU_VISIBLE宏的条件编译给注释掉,记着同时要把下面对应的#endif(在61行)也注释掉。

    如果你vim用的不是很熟练,你可以去msys2的安装目录,你会发现这个文件就在C:\msys64\usr\include里面,用你的VSCode打开它完成编辑。

  6. 准备一下redis的源码。我们来到redis首页,点Download按钮,打开了redis的下载页

    目前最新版本是7.0.2,我们直接点击Download 7.0.2的链接。就可以下载到redis源码。这里建议下最新版,等你进来这个页面的时候7.0.2应该不是最新版了,所以这里不给出链接了。

    下载回来的文件名应该看起来像是redis-7.0.2.tar.gz。我们找个地方解压它。比如我这里选了D:\tmp\redis-7.0.2。(记着选“解压到当前文件夹”,不然你就会解压到D:\tmp\redis-7.0.2\redis-7.0.2

  7. 我们回到msys2的命令行,cd到源码目录去:cd /d/tmp/redis-7.0.2(msys2对于windows盘符的映射是直接映射成根目录下的单字母目录的)

  8. 开始编译make

    编译的过程中会出一大堆警告,不要紧,只要不报错就是没事。

    最后你会看到打印出了Hint: It's a good idea to run 'make test' ;),这就说明编译成功了。

  9. 我们把编译结果导出来。使用make install命令,PREFIX是目标目录,记得用msys2内部的路径

    make PREFIX="/d/Cache/redis" install

    这样我们去D:\Cache\redis\bin就能找到编译结果了。

  10. 然后你双击redis-server.exe发现报错无法运行,这很正常。去C:\msys64\usr\bin复制一个msys-2.0.dll来。然后再双击就能运行了。

  11. 如果报错了怎么办:一般来说是有依赖没装好。查看报错信息看看是啥没装,然后用pacman -S安装一下。回到源码目录,执行make clean,然后回到第8步再来一次。

  12. 记得把/usr/include/dlfcn.h改回去。

关于NPOI的两个坑——保存到MemoryStream自动关闭和AddPicture生成的文件打不开的问题

今天踩了两个NPOI的坑,一个比一个气人。

故事背景都是要导出Excel或者Word。

1. 导出Excel,写入Stream被关闭

很普通的导出Excel,于是后端先去数据库里查数据,然后用NPOI生成Excel表格,把生成的结果传给前端,触发浏览器下载保存到本地。

按照这个思路,我们很快写出了代码,然而却发现导出存在问题!出问题的代码是这样的:

1
2
3
using var ms = new MemoryStream();
workbook.Write(ms);
ms.Seek(0, SeekOrigin.Begin); //重置流当前位置,给后面步骤第二次使用

结果运行一下,喜提异常一个:System.ObjectDisposedException: Cannot access a closed Stream.

我就想,我好好的一个Stream,怎么就Closed了呢?

一般我们在C#中使用Stream,原则是谁打开的,就谁负责关闭。这也是为什么我们要写using var ms = new MemoryStream()。由于using关键字的特性,这里打开的Stream会在结束这段代码时自动关闭并清理。

NPOI.XSSF 这个Excel组件可好,他直接自作多情的在Write(ms)的调用里面把Stream关了。如果是写文件,写到这里关了倒无可厚非,反正文件已经写完了。

但是我们这里本地不想保存这个文件,而是希望直接把结果推给浏览器。也就是这个Stream流需要再读一遍的,你直接给关了肯定不行啊。

最后也没办法,就继承了一个MemoryStream,然后重载Close,不让它关掉。

定义了NpoiMemoryStream,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NpoiMemoryStream : MemoryStream
{
public bool AllowClose { get; set; }
public NpoiMemoryStream()
{
AllowClose = true;
}

public override void Close()
{
if (AllowClose)
{
base.Close();
}
}
}

那么我们就把用法也对应改成:

修改后的代码:

1
2
3
4
5
6
using var ms = new NpoiMemoryStream();
ms.AllowClose = false; //先禁止Close
workbook.Write(ms);
ms.Flush();
ms.Seek(0, SeekOrigin.Begin); //重置流当前位置,给后面步骤第二次使用
ms.AllowClose = true; //重新允许Close,让这段执行结束后这个MemoryStream可以被正常关闭回收

2. 往Word文档里添加图片,导出后的docx文件Word打不开

这个就更坑了,需求是导出工单为Word文档。那么在工单界面上传的图片需要用插图的形式插在Word文档里面。

那么我们正常写代码,就成了下面这样:

有问题的代码:

1
2
3
4
using var imgStream = await efs.DownloadImage(imageUrl);
var imgRun = para.CreateRun(); //para 是 XWPFParagraph的对象,当前段落。
imgRun.AddPicture(imgStream, (int)PictureType.JPEG, imageFileName, Units.ToEMU(imageWidth), Units.ToEMU(imageHeight));
imgRun.AddCarriageReturn();

写好了,运行,没报错,docx生成了。那么我们用Word打开一下:

噔噔咚!

1
2
3
4
5
Word 在试图打开文件时遇到错误。
请尝试下列方法:
* 检查文档或驱动器的文件权限。
* 确保有足够的内存和磁盘空间。
* 用文本恢复转换器打开文件。

这是为什么呢?我尝试了很多可能,修改设定的长宽啊,不从网络下载图片而是先给个本地图片试试啊,给图片一个单独的Run啊,尝试把图片不再内联到文档里,而是插入成“四周型”啊,都没用。最后我确信了自己的代码没有问题,而问题出在NPOI.XWPF里面。

然后我去查看了生成的Word文档内部结构。发现了Bug的所在:

大家知道docx文件其实是一组xml的zip包。我们把生成的docx后缀名改成zip,解压,用VSCode打开,看看到底给我们生成了什么xml出来。

然后发现这个图片生成的XML是这样的:

1
<wp:docPr name="Drawing 0" descr="微信-01.png.jpg"></wp:docPr>

而我正常在Word里拖进去一张图片,生成的XML是这样的:

1
<wp:docPr id="237795" name="Drawing 0" descr="微信-01.png.jpg"></wp:docPr>

NPOI,你id呢?

只能用NPOI给出的内部工具手工解决一下:

修改后的代码:

1
2
3
4
5
6
using var imgStream = await efs.DownloadImage(imageUrl);
var imgRun = para.CreateRun(); //para 是 XWPFParagraph的对象,当前段落。
imgRun.AddPicture(imgStream, (int)PictureType.JPEG, imageFileName, Units.ToEMU(imageWidth), Units.ToEMU(imageHeight));
var docPr = ((NPOI.OpenXmlFormats.Dml.WordProcessing.CT_Drawing)imgRun.GetCTR().Items[0]).inline[0].docPr;
docPr.id = (uint)(new Random()).Next(1000, 1000000);
imgRun.AddCarriageReturn();

然后再尝试运行,生成的Word终于正常打开了。

以上两个问题感觉已经在NPOI中存在好多年了,然而并没有人修复过。目前也不知道java版的祖宗Apache POI是不是有同样的问题。但是碰到了的话确实很坑。

拯救右键菜单——关于如何解决.ts的视频点了右键后卡死,转圈很久才能弹出菜单的问题

问题描述

其实这个问题碰到很久了,但是最近又有挺多人来问我,该写个文章说明一下为什么,以及应对策略了。


screenshot_12

像我们这样经常看网络直播的,就经常有录下一场精彩的直播的需求。但是有些从Youtube上下载来的 .ts 后缀的 MPEG-2 TS VIDEO 视频文件,当你想要点右键进行复制/剪切或者重命名的时候,会发现右键菜单弹不出来,鼠标指针也开始不停的转圈。等了很久,右键终于弹出来了。

这个体验实在太差了。那么原因是什么呢?

问题原因

其实原因在于Windows资源管理器,为了针对这个视频文件做出快捷操作,Windows需要分析一些视频的信息,比如长、宽、文件大小、视频长度等,这些信息依赖于读取视频内部的Meta标签。然而有一些网站(说的就是你,Youtube!)不会把这些Meta信息写在视频文件中,那么Windows就会傻傻的从头扫描到文件尾,然后才默默的放弃,弹出一个右键菜单来。

当你只有一百多MB以下的小视频的时候倒感觉不出来,只要零点几秒就扫描完了。但是如果视频质量好,又比较长,很容易达到十几GB,那就要命了,扫描时间经常可以长达一两分钟。

缓解方案

这个问题(在微软亲自修复之前)显然没法彻底解决,只能缓解。

显而易见的,Windows资源管理器是靠后缀名判断这个文件是不是需要识别一下的,那么我们只要把ts改成tsp,就不会出现这个问题了。

但是改后缀名很不方便啊。

其实这个Windows资源管理器扩展对我们并没有太大用处,所以可以考虑禁用它。

  1. 首先下载一个叫做 ShellExView 的程序。
  2. 启动ShellExView,注意要用管理员方式启动。找到一个叫做“MF MPEG Property Handler”右键选择“禁用选择项目”。当右边的“已禁用”这列变成“是”就好了。

shellexview

  1. 关掉资源管理器窗口再重新打开,然后你可以试试右键点.ts的文件,右键菜单打开将会丝般顺滑。

那么,代价是什么呢?

大概就是Windows不知道这个文件的时长,还有缩略图可能没有了。如果你觉得这些不重要的话,那你完全可以忘了它,永远不再启用这个扩展。

如何使用PEM格式的证书文件和密钥生成pfx格式

.pfx是PKCS#12标准带有私钥的证书的后缀名,很多asp.net core部署https时需要使用pfx格式证书。而一般使用的PEM格式的证书为base64编码的纯文本,后缀可以为pem,也可以是crt。

使用openssl将pem格式的证书和私钥转换为一个pfx格式的带有私钥的证书:

如下

1
openssl pkcs12 -inkey website_key.pem -in website_cert.cert -export -out website_pfx.pfx

使用root用户进行npm install

简单的说,npm一般会使用 nobody 用户,很多时候没有权限。当然正常情况npm也不需要什么权限。但是总有一些自以为很nb的sb包要权限(说的就是你 clientjs ),这个时候就需要给它点权限才能正常npm install。但是用root账户npm install仍然是权限不足。这是因为npm内部不会使用当前执行的用户,而是继续用它的 nobody。如果需要用当前登录账户授权,就需要增加一个参数unsafe-perm,如果使用的root,还要增加allow-root。

所以就是这样:

1
npm install --allow-root --unsafe-perm=true

即可

获取Windows唯一识别码

我们时常会有唯一认证某台机器的需求,比如在软件注册和激活上,如何保证激活的软件副本和安装机器的对应。很常见的方式是获取一个唯一识别码,并传给后端进行存储。验证时匹配这个唯一识别码即可。

唯一识别码最好和系统无关,这样在用户重装系统后仍然可以激活软件。

以前我们常用的是网卡mac地址,但是网卡mac地址很容易修改,用户也可能有多张网卡并经常更换。

另外一种选择是CPU序列号,但是近年的Intel CPU不再区分同一批次中各个CPU的序列号,这样就有可能两台电脑获得的CPU序列号是一样的。在办公室环境中,由于基本大家电脑都是一批购买,很容易出现一个办公室里所有CPU序列号都一样的悲剧。

最后我选用了微软自己的推荐,也是Win10激活使用的唯一识别码。微软叫它CSP UUID,实际上就是主板识别码。和硬件相关且无法很容易的修改,是最佳的唯一识别码选择。

CMD中可以使用以下命令获取。

1
wmic csproduct get uuid

C#参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Management;
string GetSystemId()
{
string systemId = null;
using (ManagementObjectSearcher mos = new ManagementObjectSearcher("select * from Win32_ComputerSystemProduct"))
{
foreach(var item in mos.Get())
{
systemId = item["UUID"].ToString();
}
}
return systemId;
}

.NET Core GRPC使用初探

最近需要使用.NET Core搞一个需要和服务器通信的项目。通信协议是protobuf定义的。

我想既然客户端和服务器都是.NET Core,协议都protobuf写好了,那干脆上个gRPC呗。

于是研究了一下。发现.NET Core的gRPC还挺好用的。

以下为我今天研究的内容:


协议库

建立一个 .NET Standard Library 项目,添加如下依赖项:

( .NET Standard Library 可以同时兼容 .NET Core 和 .NET Framework )

NuGet:

  • Google.Protobuf
  • Grpc
  • Grpc.Tools

为该项目添加一个源文件,后缀名为 .proto 。在该文件中描述所需的消息协议以及RPC的接口定义。

.proto 文件中的 package 字段定义了生成库的命名空间。

选中这个 .proto 文件,在 Visual Studio 的 属性 窗格中,为该文件选择编译属性:

Advanced 选项中, Build Action 为: Protobuf compiler

gRPC 选项中, gRPC Stub Classes 为: Client and Server

Protobuf 选项中, Class Access 为: Public ; Compile Protobuf 为: True

直接生成该项目即可得到编译的带有rpc和protobuf协议库的dll文件。


gRPC Server

服务器端需要实现gRPC协议中定义的接口。

新建一个 .NET Core 项目作为gRPC Server。为该项目添加引用上面的协议库项目,或者通过dll、私有NuGet引用该库。

1. 实现RPC接口。

对于 .proto 文件中定义的一个 service 协议块,应在该服务对应的gRPC Server上给出一个实现。

若 命名空间为GrpcTest, service名称为DoSomething。应实现一个class继承自 GrpcTest.DoSomething.DoSomethingBase

请使用IntelliSence快速生成接口中应重载的函数框架。具体的说,为每一个rpc调用都实现一个
public override async Task<返回值消息类型> RPC调用名(参数类型 request, ServerCallContext context) 的函数。

然后在本项目中实现这些函数的具体逻辑。

2. 开启 gRPC 监听服务器。

简单的代码如下:

1
2
3
4
5
6
7
8
Grpc.Core.Server server = new Grpc.Core.Server{
Service = {
GrpcTest.DoSomething.BindServices(new DoSomethingImpl())
},
Ports = {
new ServerPort("0.0.0.0", 30000, ServerCredentials.Insecure)
}
};

然后只需

1
server.Start();

即可启动 gRPC 服务器。

但是注意server.Start()会立即返回,请使用信号量或者其他IO中断让主线程挂起,否则主线程会迅速退出。


gRPC Client

客户端一侧在指定服务器地址和端口号后,像调用普通的函数一样调用rpc接口即可。

新建一个 .NET Core 项目作为gRPC Client。同样通过添加项目引用、添加dll或私有NuGET的方式添加上面协议库的引用。

1. 获得gRPC调用对象

在实例化gRPC服务对象的时候,需要为gRPC服务传入channel对象,该对象定义了gRPC Server的地址和端口号。

示例代码如下:

1
2
Grpc.Core.Channel channel = new Grpc.Core.Channel("127.0.0.1:30000", ChannelCredentials.Insecure);
var client = new DoSomething.DoSomethingClient(channel);

此时client对象即为可用的gRPC服务对象。

2. 进行gRPC调用

像进行本地函数调用一样进行gRPC调用。

每个rpc调用都会生成两个函数,一个原名,为同步版本,在结果返回之前调用函数不会返回。

另一个为异步版本,在原名后追加Async。发送的数据和收到的数据都会按照协议文件中的消息定义自动解析。


备注

代码部分如下

  1. 协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
syntax = "proto3";

package GrpcTest;

service DoSomething {
rpc Eat (PairNumber) returns (SystemInfo) {}
rpc Cat (PairNumber) returns (PairNumber) {}
}

message PairNumber {
int32 up = 1;
int32 down = 2;
}

message SystemInfo {
string message = 1;
}
  1. gRPC Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Threading.Tasks;
using Grpc.Core;

namespace GrpcTest
{
public class DoSomethingImpl : DoSomething.DoSomethingBase
{
public override async Task<PairNumber> Cat(PairNumber request, ServerCallContext context)
{
return await Task.Run(() =>
{
var result1 = request.Up + request.Down;
var result2 = request.Up * request.Down;
return new PairNumber { Up = result1, Down = result2 };
});

}

public override async Task<SystemInfo> Eat(PairNumber request, ServerCallContext context)
{
return await Task.Run(() =>
{
var result1 = request.Up + request.Down;
var result2 = request.Up * request.Down;
string result = $"Grpc test num1={request.Up} and num2={request.Down}, result1={result1} but result2={result2}. That's all.";
return new SystemInfo { Message = result };
});
}
}


class Program
{
static void Main(string[] args)
{
Server server = new Server
{
Services = { DoSomething.BindService(new DoSomethingImpl()) },
Ports = { new ServerPort("0.0.0.0", 36667, ServerCredentials.Insecure) }
};
server.Start();

Console.WriteLine("RouteGuide server listening on port 36667");
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();

server.ShutdownAsync().Wait();
}
}
}
  1. gRPC Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Grpc.Core;
using System;
using System.Threading.Tasks;

namespace GrpcTest {
class Program
{
static async Task RunRpc()
{
Channel channel = new Channel("127.0.0.1:36667", ChannelCredentials.Insecure);
var client = new DoSomething.DoSomethingClient(channel);

var pn = new PairNumber { Up = 16, Down = 24 };
var res = await client.CatAsync(pn);
Console.WriteLine("Server Response: " + res.ToString());

var res2 = await client.EatAsync(pn);
Console.WriteLine("Server Response: " + res2.Message);

await channel.ShutdownAsync();
}
static void Main(string[] args)
{
RunRpc().Wait();
}
}
}

编译Windows版的带rtmp的nginx

看了一下这两年博客文章产量骤减,这样是不行的呀。

上次写关于nginx-rtmp-module的话题还是两年半之前的2016年。当年,即使是在Ubuntu上,安装带rtmp模块的nginx还要手动编译,因此有了那一篇文章。

不过Ubuntu 18.04开始,rtmp-module已经进了apt库,借着这两年直播越来越火的东风,从一个小众应用变成了众多用户使用的重要组件。同样地,有很多在生产环境上的大型应用也在使用这个nginx-rtmp-module。

如今,如果你是Ubuntu 18.04或者更新的系统,只需要运行下面的命令就行了

1
apt install nginx libnginx-mod-rtmp

不过最近接到了一个需求,是一个老系统的监控平台改造。服务器是Winserver的,换成Ubuntu不太现实,而且平台也是ASP.NET写的,并且是用SQL Server做数据库的,整个就是微软全家桶的东西。

但是rtmp还要上,那怎么办呢?

那就编译windows版本呗。

于是我在踩了许多许多坑后决定写一篇文章。这篇文章不仅是记录了我这几天踩过一堆坑后,最终是如何把这个东西编译出来的。

更要感谢XhmikosR’s build里找到了一个我一直搞不定的MSYS prebuilt。

以下开始正文:


1. 准备编译环境

  • 基础编译环境:VS2019,安装的时候勾上C++桌面开发,这样才会有我们需要的编译C的工具。
  • Perl编译器:我用的是ActivePerl 5.26.3。这个在activestate官网上下载最新的win64版本安装就行了。
  • MSYS:这就是把我搞死的地方的,踩的绝大多数的坑都是这里的。

下面直接放最终解决方案:去 http://xhmikosr.1f0.de/tools/msys/ 下一个MSYS_MinGW-w64_GCC_710_x86-x64_Full.7z
解压以后把bin目录加到path里,重启,就好了。

按正常方法来的话,应该是下载MinGW安装器,然后勾上MinGW-base和MSYS-base,点安装。等MinGW安装器给你把这两个东西装好。
然后同样的方法,把MSYS目录下的bin加到path里重启。但是由于大家都知道的某种原因,这种方式在我这里几乎不可用。

2. 准备源码和依赖库

虽然官方编译指南建议使用hg,不过我还是用的惯git,反正github上的nginx镜像更新也挺快。就直接git clone https://github.com/arut/nginx-rtmp-module.git了。不过我推荐点进github的release,下载一个最新版本release的源码tar.gz包

然后随便放到哪解压。

解压之后的源码目录里面,创建一个叫objs的目录,然后再在里面创建一个lib目录,然后我们开始下依赖。

编译nginx本体需要的zlib、pcre和openssl,都直接用官网下载最新版本的源码tar.gz包即可。如果担心新版本编译可能会出问题,那就先参考官方编译指南中,这三个依赖库的版本,下载和那个一致的版本肯定是没问题的。

这些东西全部解压在lib目录下。而且注意不要有嵌套目录,就是lib/zlib打开就是源码,其他两个库同理。

然后再下载nginx-rtmp-module。可以直接git clone到lib目录下,也可以去release下载一个tar.gz包。

注意:下载的这些依赖库和nginx-rtmp-module的tar.gz包不要直接存在objs/lib目录里,也不要解压了就删掉,预防编译失败。

3. 编译准备

我们需要改一点东西来完成编译。这又是一个坑,在编译过程中出现下列错误,编译失败:

1
2
3
......
build/lib/nginx-rtmp-module/ngx_rtmp_core_module.c(611): error C2220: 警告被视为错误 - 没有生成“object”文件
......

所以我们把编译参数的W4改成W3。在源码的auto/cc/msvc的83行,修改为:

1
CFLAGS="$CFLAGS -W3"

这样该警告就不会视为错误了。

在源码根目录启动MSYS的bash(如果你正确的加入了path又没有装WSL之类的重名工具,那么直接在cmd里敲bash回车就行了。)

然后参考官方编译指南的配置命令,注意修改几个依赖库的路径,正确指向objs/lib下的正确目录名的地方(很可能有版本号的差异)。

并在最后加上--add-module=objs/lib/nginx-rtmp-module

修改后的配置命令示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
auto/configure \
--with-cc=cl \
--with-debug \
--prefix= \
--conf-path=conf/nginx.conf \
--pid-path=logs/nginx.pid \
--http-log-path=logs/access.log \
--error-log-path=logs/error.log \
--sbin-path=nginx.exe \
--http-client-body-temp-path=temp/client_body_temp \
--http-proxy-temp-path=temp/proxy_temp \
--http-fastcgi-temp-path=temp/fastcgi_temp \
--http-scgi-temp-path=temp/scgi_temp \
--http-uwsgi-temp-path=temp/uwsgi_temp \
--with-cc-opt=-DFD_SETSIZE=1024 \
--with-pcre=objs/lib/pcre-8.43 \
--with-zlib=objs/lib/zlib-1.2.11 \
--with-openssl=objs/lib/openssl-1.1.1b \
--with-openssl-opt=no-asm \
--with-http_ssl_module \
--add-module=objs/lib/nginx-rtmp-module

按回车执行。如果没有报错,现在就生成了一份对应的Makefile。

4. 编译

在开始菜单的VS2019目录下可以找到“Developer Command Prompt for VS 2019”,启动它,并转到nginx源码目录下。

键入命令

1
nmake

开始编译

如果编译失败了,可以使用nmake clean清理源码目录。但是注意objs目录会被整个删掉,因此你需要重新创建objs/lib并重新往里放置依赖库。

5. 编译成功之后的事

编译完成了之后,在objs目录下就会生成nginx.exe了。由于windows下的nmake install很混乱。因此这里我们手工操作。

在随便哪个空目录把nginx.exe复制过去,这个就将作为nginx的主目录。

在和nginx.exe同级别,创建conf、html、logs、temp四个目录。然后把源码目录conf里的所有文件复制到conf目录下面。把源码目录docs/html里的所有文件复制到html目录下。

创建一个bat文件用于关闭nginx,内容如下:

1
2
@echo off
nginx -s stop

启动nginx时,只需双击nginx.exe即可。你将看到黑窗口一闪而过,这就是启动了。需要关闭的时候请双击刚才创建的那个bat。

6. 开启rtmp功能

编译conf/nginx.conf,按照nginx-rtmp-module的官方教程编写rtmp配置,然后重启nginx就行了。

注意,在Windows下:execs、static pulls和auto_push这几个功能无法使用。

cmd如何调整consolas字体的大小

不是consolas很难看,但是直接调又找不到consolas,实际上是微软对亚洲字体要求真的很严格。

chcp 437
在属性里调整,改好以后
chcp 936

おまけ:

从头设置来一遍吧:

1. 给consolas添加微软雅黑的fallback:

定位到HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink
添加一个“Consolas”,类型为“REG_MULTI_SZ”,值为:

MSYH.TTC,微软雅黑,128,96
MSYH.TTC


2. 让chcp 437选项中出现consolas:

定位到HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont
添加一个“00”或者“000”,类型为“REG_SZ”,值为“Consolas”

3. chcp倒腾一遍。完成。

本地看到https的小绿锁【改】——你们Chrome真的很严格

三年前写了一篇博客讲如何建立自签名的CA给本地https调试时候用。没想到最近没用了。看到 https://127.0.0.1 的前面再次打上了一条红色的斜线还有“不安全”三个大字,心里很不是滋味。

查了一下发现你们Chrome现在不许在使用者name里直接写IP了,而且域名和ip都要写到Altername里去。于是就来说说怎么重新看到https的小绿锁。

没读过三年前那篇文章的读者,千万不要去看!只看这篇就够了!


先来存一个root ca的配置文件,保存成 ca.cnf 。对应项大家看着改改。其中countryName是国家名,只能两个字母。最好查查对应国家的两字母简称名。下面的字符串的名字都可以随便写。

[ req ]
distinguished_name  = req_distinguished_name
x509_extensions     = root_ca

[ req_distinguished_name ]
countryName             = CN
countryName_min         = 2
countryName_max         = 2
stateOrProvinceName     = Shanghai
localityName            = Pudong
0.organizationName      = Noname Kani\'s Aiai Sub
organizationalUnitName  = Software Imports
commonName              = nonamekanica.cyaron.moe
commonName_max          = 64
emailAddress            = [email protected]
emailAddress_max        = 64

[ root_ca ]
basicConstraints            = critical, CA:true

然后在安装好openssl工具的bash命令行下执行下面的命令:

openssl req -x509 -newkey rsa:2048 -out ca.crt -outform PEM -keyout ca.key -days 10000 -verbose -config ca.cnf -nodes -sha256 -subj \"/CN=Noname Kani\'s Aiai Sub ROOT CA\"

执行完毕之后本地会多出两个文件:ca.crt 就是根证书,ca.key 就是ROOT CA的私钥。

然后按照正常流程给域名发request,因为我们这次把127.0.0.1写在alt name里,所以CN我就写了localhost。

openssl req -newkey rsa:2048 -keyout server.key -out server.req -subj \"/CN=localhost\" -sha256 -nodes

同样这个执行完后也会多出两个文件:server.req Request文件,和 server.key 服务器私钥

然后写一个扩展文件,保存成 localhost.ext,如果还有其他的IP和域名就按照这个格式继续往下加

subjectAltName = @alt_names
extendedKeyUsage = serverAuth

[alt_names]
DNS.1   = localhost
DNS.2   = localkani.cyaron.moe
IP.1   = 127.0.0.1

最后用CA证书和CA私钥给req文件签发证书:

openssl x509 -req -CA ca.crt -CAkey ca.key -in server.req -out server.crt -days 3650 -extfile localhost.ext -sha256 -set_serial 0x1111

-set_serial 后面是证书的序号,可以随便设。

就这样了。这段执行完后就会签出server.crt,拿着这个文件和上一步生成的key和再上一步生成的根证书就可以去部署服务器了。

测试了一下,完美绿锁。

图我就不贴了,反正也没人看。