趁刚写完这个项目,把一些学到的东西写一下。

1, Python的socketserver是个写服务端TCP程序的神器,ThreadingTCPServer非常方便好用。

2, Python的selectors模块是多路复用的神器,DefaultSelector解决了好多问题。

3, 使用selector来监听Popen的进程的stdout和stderr时,需要把stdout和stderr用fcntl模块设置成非阻塞的:fcntl.fcntl(proc.stdout, fcntl.F_SETFL, os.O_NONBLOCK)

4, 自己启动的子进程,如果不是daemon mode,记得用wait函数或者os.waitpid(pid, os.WNOHANG)来回收。

5, strace用来分析程序运行时做的系统调用非常有帮助,strace -p <pid> -f -e read 用来监听某个pid进程的系统调用,可以把所有关于read的调用都打出来;strace -o run.log -f python a.py 可以跑某个程序(这个例子里是python a.py),并把程序运行期间的系统调用都输入到run.log文件里。

6, 通过strace来监听程序有没有调用到read(0,可以对一般的程序来判断这个程序是不是要从标准输入stdin读取内容,也就是等待用户输入,但这个方法并不精确,理由有二:一是stdin严格来说用户是一直可以输入内容进去的,不需要非等到read(0的系统调用,read(0只是程序目前想从FD为0的标准输入stdin里阻塞式的读取数据,如果此时stdin里已经有了,那么程序就会直接读取,stdin里的内容完全可以是用户输入好的;二是,使用dupdup2dup3的系统函数,可以将其他打开的文件的FD指向stdin,例如这个文件的FD为4,在dup2(4, 0)的系统调用后,那么read(4其实就是想从stdin里读取内容,所以从stdin里读内容不一定非要是read(0可以是其他任意的FD数字,要看它之前是否有其他FD指向stdin。

7, 用strace分析了Golang的一个需要读取用户输入的程序,发现不管我输入多长,Golang的read(0系统调用,都是每次只读一个字符,比如我输入"hello\n",Go要read(0, buf, 1)读取6次才能把这个字符串读完,也就是做了6次read(0的系统调用,反观其他语言,比如C,是直接read(0, buf, 1024),即一次调用会尝试读取1024个,虽然最后读到的是6个字符,但是只要一次read的系统调用就够了。Go为啥每次只读一个字符,匪夷所思。

8, Node.js来读取用户输入的时候,是使用的read(12,,原因就是它新开了其他的文件(FD为12),并使用dup3把12指向了0,这样当read(12,时,其实就是从标准输入读取内容。

9, 使用strace来跟踪Java程序的系统调用时,一定要给strace使用-f选项,-f会跟踪所有启动进程/线程的系统调用,Java启动一个程序时会启动很多线程,如果不使用-f,strace输出的系统调用函数里你可能找不到想要的。

10, 大多数编程语言的stdout都有buffer,想要去掉这个buffer的话,对C语言来说可以加stdbuf -o0在前面,比如stdbuf -o0 ./helloworld;对Python来说,可以直接加一个-u的选项,比如python3 -u hello.py;对Ruby来说,可以先在/tmp下新建一个rbopt.rb的文件,内容为$stdout.sync = true\n$stdin.sync = true,然后使用-r选项让ruby在跑实际的ruby代码前先跑这个文件(相当于把stdout和stdin的buffer都去掉了),例如ruby -r /tmp/rbopt.rb hello.rb

11, Docker是个好东西,ENTRYPOINT可以直接指定跑一个程序(不能退出,也不能跑daemon模式)。如果一定要让自己的程序跑daemon模式,可以在跑之后再跑一个tail -f /dev/null,这样可以阻止container退出。

12, Python里想编译一个py文件到pyc文件,可以直接在命令行跑:python3 -c "import py_compile; py_compile.compile('config.py', 'config.pyc')"

13, docker top <container id>显出的进程id和docker exec <container id> ps axu的进程pid不同,前者显示的进程的pid是在host中的,后者显示的pid是在container中。比如你的程序在container里的pid可能是1,但是用docker top打出来的pid因为是在host机器上的,可能是一个非常大的四位数或者五位数。

14, 使用Docker的Python包来启动一个container时,可以指定privileged参数,让它等于True可以给这个container更高的系统权限,否则在Docker里跑的程序,即使是跑在root的用户下,也可能因为权限问题报错。

15, 前端使用的jq-console包很好用(MIT License),好评。