让 php 用 nginx 打包 zip

php 本身有 zip 模块,可以生产 zip 文件。但是这个 zip 模块只能使用本地文件来打包。如果需要打包输出的文件来自网络,就得先保存临时文件。在文件数量多或者文件大的时候就很杯具。另外,由 php 来输出大的打包文件会占用 php 进程大量时间,影响并发能力。

nginx 有一个第三方模块,mod_zip 。同样可以输出 zip 包。和 X-Accel-Redirect 有点类似,只需要 php 输出相应文件的路径等信息,然后给一个特殊的响应头即可。

nginx zip 模块使用的响应头是 X-Archive-Files: zip 。加上这个响应头,nginx zip 模块就会处理响应正文,完成打包输出。

比如:

printf("%s %d %s %s\n", $crc32, $size, $url, $path );

逐条输出要打包的文件。

$crc32 是 16 进制的文件 crc32 值。也可以不提供,用 “-” 代替。不过这样就没法用 Range 分块下载,断点续传了。
$size 是文件大小的十进制整数。
$url 是要打包的源地址。如果要打包一个本地文件,可以先在 nginx 中做一个 internal path。
$path 是 zip 包中的路径。

不过这样没法创建空目录。一方面,zip 格式开始就没有定义空目录,后来的标准和软件都是通过加一个 / 结尾的 0 大小文件来实现的。这时,就需要先在 nginx 中做一个 internal 的 0 大小文件,比如位于 /_0 。然后输出

printf("%s %d %s %s\n", '00000000', 0, '/_0', $path.'/');

如果要支持中文路径,可以使用 X-Archive-Charset: utf8 这样的响应头,内容为你输出的编码。nginx zip 模块会按标准转换成 utf8 的标准格式。不过各个软件对这个 zip 的标准支持不一,比如 windows 的 zip 目录就不支持,只能以 gbk 编码直接输出。其他软件对编码支持效果也各不相同。测试过的 winrar,7zip,windows zip 目录中,winrar 倒是都可以很好支持。7zip 可能会把部分中文空目录变成 0 大小文件。所以,这点还需要自己斟酌处理。

go 语言 Makefile 指定依赖包位置

编译 go 程序可以使用自带的一些 Makefile 脚本来简化编写 Makefile 。官方的文档过于简略,没提到需要指定依赖包位置的方法。翻过那几个脚本代码后,发现原来有 LDIMPORTS 和 GCIMPORTS 可以指定。

比如:

include $(GOROOT)/src/Make.inc

LDIMPORTS=-L ./pkg/_obj
GCIMPORTS=-I ./pkg/_obj

TARG=tool
GOFILES=\
	tool.go\

include $(GOROOT)/src/Make.cmd

GCIMPORTS 指定编译阶段的参数,对 Make.cmd,Make.pkg 都有效。LDIMPORTS 指定链接阶段的参数,这个对 Make.pkg 就没用了。

另外,还可以用类似 CLEANFILES+= pkg/_obj ,在 make clean 的时候来清理更多的东西。

以及

all: pkg/_obj tool

pkg/_obj:
	cd pkg; make

这样的方法在依赖包未编译时,自动编译依赖包。

让 linux 交互式命令行程序支持方向键等功能

自己写交互式命令行程序,通常都是从标准输入输出读写。运行的时候,只能敲完整一个命令,然后回车。想要按方向键移动光标,按 del 往后删除都只会出现个 ^[[A 这样的东西。要实现那些功能,得处理终端命令,这个活并不轻松。虽然有 readline 这样的库,但还是会带来很多麻烦。

好在 unix 的哲学就是一个程序干一件事情,并把它做到最好。于是就有一个 rlwrap 的程序。用这个程序启动你的工具,你的程序就立马有了那些功能。还可以按上下键翻阅历史命令,ctrl+r 来搜索历史输入等。

 

go 语言并发机制 goroutine 初探

go 语言的一个很大的优势就是可以方便地编写并发程序。go 语言内置了 goroutine 机制。这是一种类似 coroutaine(协程) 的东西。但是又不完全相同。

比如这个例子:

package main
import (
    "fmt";
    "strconv"
)
func main() {
    ch := make(chan int)
    task("A", ch)
    task("B", ch)
    fmt.Printf("begin\n")
    <-ch
    <-ch
}
func task(name string, ch chan int) {
    go func() {
        i:= 1
        for {
            fmt.Printf("%s %d\n", name, i)
            i++
        }
        ch <- 1
    }();
}

运行以后,发现会 A B 两个 goroutine 会交替执行,并像传统的协程需要手动 schedule 。看起来很神奇。

稍稍改一下代码,把

fmt.Printf("%s %d\n", name, i)

改成

print(name + " " + strconv.Itoa(i) + "\n")

再看看。神奇的效果消失了,只有 A 被运行。

那么 fmt.Printf 和 print 有什么差别,导致了这个结果呢?

大致翻了一下 go 的代码,看出 go 语言在对 c lib 的包装上用了个 cgo 的方式。而在通过 cgo 调用 c 库的时候,会在调用时自动 schedule 切换走,在调用结束的时候再返回。这两个结果的差异就在于,fmt.Printf 是通过 cgo 包装的,而 print 则是原生实现的。所以在调用 fmt.Printf 的时候,就自动实现了调度。

传统的 coroutaine 在访问网络、数据库等 io 操作时仍然会阻塞,失去并发能力,所以通常需要结合异步 io 来实现。而现有的库如果本身未提供异步功能,就很难办,往往需要重新实现。而且,即使有异步 io 功能,也需要额外的开发,才能在表现上和以往顺序程序相同的方式。

go 语言的这种实现方式很好的解决了这个问题,可以充分利用现有的大量 c 库来包装。

同时,也还是可以使用 runtime.Gosched() 来手动调度。在运算量大的场景下,也还是必要的。

在使用 print 的例子里,如果使用 runtime.GOMAXPROCS(2),又可以重新并行起来。这时,两个 goroutine 是在两个独立的线程中运行的。这又是 goroutine 和协程的一个不同点。不过启用多个线程并不见得能让程序更快。因为跨线程的上下文切换代价也是很大的。在上面那个简单的例子里,还会让程序变得更慢。降低上下文切换开销也是协程的一个优势所在。


之后又对 goroutine 的实现研究了一下,发现了之前的一些错误。详见:再探-goroutine

让 wget 用上 socks 代理

由于众所周知的原因,国内访问国外网络经常会有问题,必须通过代理服务器。而在服务器上下载,通常都使用 wget ,而不会有 gui 环境来运行 firefox 之类的浏览器。wget 只能使用 http 代理,而无法直接使用 socks 代理。

有一个 tsocks 的工具,可以让其他程序使用上 socks  代理。对 debian 系 linux ,可以通过 apt-get install tsocks 来安装。其他发行版也很容易。

然后修改 /etc/tsocks.conf :

server = 127.0.0.1
server_type = 5
server_port = 1080

设置代理服务器地址,类型,端口。

然后给 wget 套上 tsocks 运行,就可以访问代理了(如果之前设置了 http 代理,要先清除掉)。

tsocks wget http://…&#8230;

这个方法不仅仅能用于 wget ,对于其他的本身不支持代理的工具也有效,比如某些软件包管理器如 pear, gems 等。