这个念头是这样产生的,我帮室友的同学写一个 表白 程序(!),完成后类似于这样:
这时候,强迫症就犯了:如何把那一快 表白无关 的代码给藏到头文件里面,只留一个 心 在 .c
文件里?由于是给人家的,若是还有个头文件感觉不太好。而且,若是可以实现包含远程头文件的话,做 OJ 的题目的时候就好玩了,因为 OJ 只能交一个文件。想想,你把一大堆代码放在头文件里边,交上去的只有个 main()
函数那该多爽:)
首先尝试
#include "http://server-ip/header.h"
然而编译器以为这是个文件名,当然找不到了。
然后我想,编译器遇到这个文件,应该该是使用 fopen()
去打开它,是不是这是个链接, fopen()
就不认了哪?那要是个文件哪?那如何让一个本地文件指向一个网络 IP 哪?对,就是 Unix socket 文件,使用 ssh 的本地端口转发功能,把本地的一个文件转发到服务器上去:
ssh -fNTL /tmp/header.sock:localhost:1234 user@server
然而再次失败:(
PS: 关于 ssh 的端口转发,可以去看看 ssh 的 Manual page,或者等我的下一篇文章了:)
细想之下,发现 Socket 文件和普通文件还是有区别的,不能直接通过 open()
系统调用(库函数 fopen()
调用了 Linux 的系统调用 open()
)来打开。那咋办?既然编译器对包含的头文件一律使用 fopen()
来打开,那就从根本上没办法了。不过天无绝人之路,既然编译器不主动去连接网络获取文件,那我们就把这个工作交给其他人来做。
nc server-ip server-port | gcc -Wall main.c
先看后边的,很熟悉了吧,调用 gcc 编译 main.c , -Wall
用来打开 gcc 的所有警告,这是个好习惯。再看前边, nc 就是 netcat 的缩写,cat 这个命令用过吧,把输入转发到输出的工具。netcat 只不过是把网络上边的“输入”和“输出”进行转发,仅此而已,没有多复杂,别怕:)
nc 的基本用法就是上边的那样,意思就是让它从 server-ip
这个 IP 的端口 server-port
读取数据,然后发送到标准输出——被重定向到了 gcc 的标准输入。没错,nc 就是负责了从网络获取数据的工作。现在,我们只需要在 C 语言的源文件里边包含 "/dev/stdin"
就可以让 gcc 从它的标准输入去读取这个头文件了——来自网络的头文件。然而,我们的服务器端还没有提供这个服务啦!这个同样简单,只需要用到 nc 的 -l
参数:告诉 nc 去监听(Listen)指定的端口,并且把它的标准输入转发到这个端口——和上边完全相反,上边是把来自网络的数据转发到标准输出。看到没有,nc 完全就是一个支持 net 的 cat 啦:)
好,我们在服务器端打开这个远程传输文件的服务:
nc -l server-port < header.h
端口自己选一个就行。这里 nc 的标准输入被重定向到我们想包含的头文件了。所以,编译器编译的流程就成了这样:
编译器处理到 main.c 中的 #include "/dev/stdin"
时,它用 fopen()
打开 "/dev/stdin" 去读取——实际上就是去读取它的标准输入,然而被我们重定向到 nc 的输出了,再看 nc,它连接 server-ip:server-port
上的 nc,获取到 header.h 的内容,发给 gcc ——偶耶!成功了!现在我们只需要给人家一个没多少含金量的源文件,就能编译出庞大的程序了,是不是很好玩的样子?
然而,运行 gcc 的时候有一个重定向,瞬间没了神秘感了嘛。这不,在看 nc 的 Manual page 时,意外发现了一个同样好用的命令—— mkfifo 。容我介绍一下 FIFO(First In, First Out),它和 Unix socket 一样,同是特殊文件,通常用来跨进程传输数据,一个进程向 FIFO 写数据,另一个读。当然所写的数据是不会存在于硬盘的,内核在内部完成了数据的传输。FIFO 的另一个名字可能好理解一点——命名管道,管道就是通讯的嘛,而有名字了就不怕丢了,其他的进程也能找到了:)
小实验——体验 FIFO:
首先创建一个 FIFO:
mkfifo /tmp/f
然后向它写入数据:
echo hello > /tmp/f
你会发现,echo 并没有退出,这是因为还没有人去读取 FIFO 里面的数据,一旦我们通过
cat /tmp/f
读出来,echo 也就退出了。好吧,不出你所料,cat 输出了 hello。(强迫症的话 rm /tmp/f
吧)
可以看出,这个 FIFO 起到了中转数据的作用——把 echo 的输出重定向到 cat 的输入了——This is just what stdio does!(当时我心里蹦出的真的是这句英语!难道是在终端生活太久所致 ^o^ )这里的 FIFO 和标准输入/输出扮演了同样的角色嘛。没错,我们就用 FIFO 来代替上一个方法中的 /dev/stdin
,是不是已经想出来如何做了?好,暂停3秒钟,想一想该如何做?
好吧,来看看你的想法一样不一样,我们先创建一个 FIFO :
mkfifo /tmp/fifohdr
然后在 C 语言源文件里包含它
#include "/tmp/fifohdr"
最后,把 nc 从网络获取来的数据写入 FIFO 就好:
nc server-ip server-port > /tmp/fifohdr
这样我们就可以通过普通的命令来编译了:)
gcc -Wall main.c
OK,一切就绪,可以去装逼了!我把开头提到的那个 表白 程序的所有代码放进了服务器上的头文件,只在源文件里放上了那颗爱心 ^o^ 看到的人会不会被吓一跳?
快去试试吧 ^o^ 任何问题欢迎骚扰 <here@udoubi.top>
最后说点题外话,Manual page 是你的朋友,我 Google 了半天毫无头绪的问题,看会 man 真的就解决了,ssh 的端口转发如此,FIFO 也如此。(楞着干什么,快去看看 nc 和 mkfifo, FIFO 的 Manual page 啊,真让人操劳。。。)
好吧,我承认,我是有点 轻微的 某症, /rm /tmp/fifohdr
吧。