jianjian
生日 1974.8.30
性别 男
来自 重庆
现在 上海 (1999-Now)
毕业学校 成都科技大学(1992-1996)
MSN/EMail jianjian@sunnet.org
IRC irc.sunnet.org:6667 #sndt #mylinux

虽然我也做过一些win32下面的C++开发,但是我的主要兴趣还是在UNIX下面的C开发。欢迎志同道合者来信交流。
长期开发WEB系统会令人生厌,经常性地开发一些独立的,规模较大的,比较底层的程序让人觉得能够找回不少兴奋的感觉。

  以我个人的经验来看,编写程序大致由如下几类知识做为基础:

	 一门精通的语言
	 所选择的操作系统的特质
	 所牵涉到的特定协议(if any)的特质
	 程序功能
	 数据结构
	 算法

当然还必须要有好的工具。为了在UNIX下面编写程序,我不得不额外再花很多时间去搞明白gcc, make, cvs, vim,然后又花了很多时间寻找到了最顺手的终端工具 SecureCRT, 所以你看,光把C语言学会,学精,有什么用?
要是在UNIX下面写程序,建议你无论如何都要学会,用熟vim, 这东西实在是太强大,强大到我现在在UNIX环境下没有VIM就做不了任何事情。如果你还习惯于在Windows环境下写好代码再FTP到UNIX上面去编译调试,那我劝你赶紧告别UNIX Coder的行列。

 


FLV snapshot and water-mark 2006-7

完成FLV metadata injection以后,紧接着就是FLV文件抓图和加水印的工作。比较通行的办法就是用ffmpeg来抓图,不过我实在不希望自己的整个系统流程那么复杂,而且ffmpeg抓出来的图尺寸相当大,然后还需要我的系统流程中增加一个环节把尺寸再变小,同时加上水印,实在是比较烦琐。系统流程越复杂,调用的非自有的外部程序越多,那么可能出现的环节中断的危险就越大,可自控性越低,所以这一个通宵就用在解决这个问题上了。

avcodec 库的确是个好东西,再加上 jpeglib, 两个库就能完成所有这些工作,不过这两个库需要蛮多的研究才能搞懂用法,尤其是avcodec, 真是强大,想想半个月前看到这个名词还一头雾水,现在已经用它在写程序了。用C语言有个麻烦就是你没有很多现成易用的库封装借口,但是也有个好处就是基本上所有的库都是C语言接口,而其他的语言不一定有,所以这也是C语言一直以来的优势和劣势。在一个系统中,把关键核心的技术点用C语言来解决,把周边的技术用其他语言来解决,是一个不错的平衡。

一个技术团队比较好的组成也应该是这样,我记得看到过一段蛮有意思的对话,提问者说:“为什么专门搞PHP,ASP的程序员总是解决不了比较高难的问题”,回答的人说:“你能指望一个连C语言的难度都不能克服的人有毅力和精神去钻研这么难的问题吗?”这个回答虽然有失偏颇,但也不无道理。一个团队中2-3个精英级的程序员负责解决高难度的东西,然后开发出调用借口给其他的程序员快速开发。不要让团队中的精英去把琐碎的问题也解决掉,那样只会浪费他们宝贵的精力和智慧。

水印用了一个简单的alpha算法实现,水印色重70%,目标图像色重30%。首先需要 jpg2bmp.c 负责将水印图片转换成我们自己格式的图像文件,其实蛮类似PPM,flvsnapshot.c 每次会先尝试读入这个水印图片,然后alpha到FLV截图文件的最顶部。加到最底部实在是没心思了,懒得去算位置了。


2006-7-23: 还是把水印加到右下角了。

其实这些计算坐标之类的琐碎技术应该让C语言的培养成员来完成,也是一个锻炼和熟悉。

 


FLV metadata Injection (sndt FMI) 2006-7

从10天前开始研究Youtube模式,从而开始了对 MENCODER FFMPEG FLV 的研究。为了在FreeBSD下装上一个mencoder 我可怜的PIII 550机器跑了半晚上。后来,很自然的,跟大多数研究youtube的朋友们一样,遇到了FLV文件的 metadata 的丢失和注入问题,一开始用flvtools2来解决,不过效率是在可怕,假如靠它来做一个youtube,大概注入metadata的时间不比转换的时间短多少,于是自己动手,开始研究FLV文件格式,尤其是各种TAG以及AMF标准的data type的定义,真是前后关联错综复杂,看得眼睛又近视了几度,终于完成了自己的FLV METADATA注入程序。

其实这个程序两晚上就已经完成了,多花了一个通宵是因为误把 onMetaData 写作 OnMetaData ,一个字母的大小区别让我抓狂了一晚上,把上千行代码翻来覆去DEBUG了无数遍,总是以为问题出在那无数的 _write_UIxxx() 和 _write_AMF_xxx()上面,最后才发现所有的问题就是这一个字母,毕竟年龄大了。

SNDT FMI使用了SNDT FILEOBJ库,对文件的读取操作相当迅速。注入一个 10M 大小的 FLV 文件,从分析到计算,到完成最终文件只要0.8秒,而flvtools2需要30多秒 -- 相信我,我没骗你,的确要这么久。 SNDT FMI对FLV文件进行一次快速扫描,计算出帧数,时间长度等基本信息,然后进行METADATA的组合,将结果组合在BINOBJ中,然后将FLV文件头,我们自己的METADATA数据,以及源FLV文件的音频视频数据整合成为最终数据文件。SNDT FMI使用固定缓冲,无论文件多大,读分析的时候只使用256字节做为缓冲,组合写的时候为了提高运行速度,使用16K内存做为缓冲区。

flv.c

这个文件中是所有基本的AMF data type定义和读写操作函数,注意FLV的标准是整数以big-endian存放,跟SWF不同。但是它的double类型又是little-endian存放,最关键的是它并没提到这点,在解决这个问题的时候我实在不知道是我有病还是macromedia有病。

所有的数据格式都是遵循MACROMEDIA的FLV标准定义,而它的基本数据类型是AMF格式的。一个METADATA就是一个大的 ScriptTag, 它包含一个类型为EMCAARRAY的onMetaData event, 而这个EMCAARRAY 包含了播放器需要的各种基本数据,当然也包括我们最感兴趣的 duration,如果你不知道我在说什么,没关系,我自己也差点不知道我在说什么了。

flvfinal.c

这个文件生成 flvfinal 可执行文件 , ./flvfinal src.flv dst.flv 即可完成注入,当然我并未做更多检查,比如源文件是否已经有Metadata,这里都假设源文件是刚刚从mencoder转换过来的待修补格式。稍加修改可以将duration提取出来放入数据库,这样可以像youtube一样在每个视频旁边列出播放长度信息。

sunlib2/

这个目录下是 SunNet LIB 2.0, 几乎所有SunNet NDT的程序都是基于这个库, SNDT FMI 也不例外。

点此下载源文件 sndtfmi.tgz




Man page builder

这是一个为迅速编写man page而开发的程序,可以方便/迅速地将格式化的文本转换为man page兼容的格式。因为是自用,以及给UNIX的程序员使用,在UI方面并未考虑太多的友好性。另外,这个程序只能产生最基本的man page格式 -- 不过已经足够了,总比手动编写好。

 

快速上手:

→ 输入man page的原始内容,如下图

→ 用鼠标把每个章节名字的内容行选取(注意,要选取整行),然后按 Ctrl+B, 章节名字这行将会变成粗体高亮,其下的内容会自动缩进

→ 如此循环,将所有的章节名字都先标记为 Ctrl+B

→ 选取所有需要 高亮 或者 下划线 的内容,按 Ctrl+D 或者 Ctrl+F 或者 Ctrl+G, 程序会分别将它们标记为 红色 或者 下划线 或者简单的悬挂模式。

→ File->Save to BMF 将内容存为UNIX下man page兼容的格式,然后上传到UNIX上你自己的目录下(可以存为 *.bmf, 也可以选择 any file(*.*) 然后存为类似 sdefs.9 这样的文件名)

→ File->Save to RTF 将内容存为RTF格式,以方便下次用 File->Open RTF 打开继续编辑。

→ 将生成的 *.bmf 文件上传至UNIX(以FreeBSD 为例, 假设文件名是 1.bmf):

mv 1.bmf mytest.9
gzip mytest.9; mv mytest.9.gz /usr/local/man/man9
man mytest

或者直接

nroff -man 1.bmf | less 查看结果

范例结果:

注意事项:

→ 编辑内容里面最好不要有两行以上的连续空行

→ Ctrl+G (悬挂模式) 只能应用在每一行的最开始的部分,也就是说你必须从每一行的第一个字符开始选定应用,如果第一个字符是空格,那么你也需要选上这个空格。

→ 每段字符串最好只应用一种格式,比如应用了高亮就不要再应用下划线

→ 内容进行修改以后记得分别Save to RTF 和 Save to BMF.

技巧:

→ 在应用高亮或者下划线的时候,如果对象内容是一个单词,则可以将光标放在这个单词里面,直接按Ctrl+F 或者 Ctrl+D 或者 Ctrl+G ,程序会自动帮你寻找出对应的单词部分并应用格式,而不用你自己去用鼠标选取整个单词。这可以节约很多的时间。

点此下载 BuildMan 1.0, 点此下载范例 RTF 文件, 点此下载范例文件生成的man page文件

关于悬挂模式:

关于悬挂模式,有个值得注意的地方。 通常我们在 buildman 里面, 假如在如下情况下应用悬挂模式:

ECANTWRITE Indicate that the function can't write to given file, and you should do something like blah blah ... and blah blah ..

ECANTREAD Indicate that the function can't read from given file, and you should blah blah

那么出来的效果就会是

ECANTWRITE Indicate that the function can't write
   to given file, and you should do something
   
like blah blah ... and blah blah ..

ECANTREAD Indicate that the function can't read
   from given file, and you should blah blah

这是我们所希望的悬挂效果.但是在如下情况中,我们本来希望得到这样的悬挂结果:

int doread(int fd, _u_char * readbuf, _u_int32
  
maxlen, _u_int32 timeout, _u_int32 count) ;

int dowrite(int fd, _u_char * writebuf, _u_int32
  maxlen, _u_int32 timeout, _u_int32 count) ;

按普通的方法就不行了,因为我们不能把 int doread 和 int dowrite 一起选定并应用Ctrl+G, 否则的话 int doread 和 int dowrite 会一起变成高亮(当然如果你不介意也无所谓),就如同下面的效果:

int doread(int fd, _u_char * readbuf, _u_int32
  
maxlen, _u_int32 timeout, _u_int32 count) ;

int dowrite(int fd, _u_char * writebuf, _u_int32
  maxlen, _u_int32 timeout, _u_int32 count) ;

如果你介意的话,就这么办: 在行首的 int 前加一个空格,并把这个空格选定应用Ctrl+G, 然后再单独将 doread 和 dowrite 设为 Ctrl+D 的高亮,就可以了。具体的效果可以看看下载的范例。

下载

点此下载 BuildMan 1.0, 点此下载范例 RTF 文件, 点此下载范例文件生成的man page文件



ServU-MySQL


ServU-MySQL ,提供了让ServU FTP服务器跟MYSQL服务器共享帐号数据的功能,利用它的帮助,系统管理员不用再去ServU内建大批系统帐号,只需要提供一个MySQL服务器存放用户帐号数据即可。对于那些已经有使用MySQL的资源下载论坛,ServU-MySQL尤其实用,论坛用户可以使用他们在论坛上的用户帐号密码就可以登录FTP下载。不论用户帐号在MySQL表中的用户数据字段如何组织,只需要对ServU-MySQL的对应查询稍作修改就可以实现共享。

系统管理员从此可以获得简便的方式来维护管理FTP帐号(比如WEB方式,甚至直接mysql command line),并且ServU-MySQL提供了分组资源限制功能,这个组有点类似ServU的用户组,但是它是跟MySQL帐户表中的组对应的。没有什么解释能够比一个例子更易懂,下面就是举例解说:

假设某个论坛的用户按级别分成了三个组, 分别为 1, 2, 3,论坛管理员可以定制:

组1的成员可以获得每秒100K的下载速率, 并且组1的成员可以最多同时连入100个。
组2的成员可以获得每秒50K的下载速率, 并且组1的成员可以最多同时连入50个。
组3的成员可以获得每秒20K的下载速率, 并且组1的成员可以最多同时连入10个。

同时ServU-MySQL还有如下限制功能: 同一IP只能连入一个帐号,那些试图在同一IP连入几个不同帐号的行为被禁止。 同一帐号只能同时允许一个人使用,用户不能把帐号给别人,然后几个人从不同的地方一起连。

ServU-MySQL对数据库的链接是永久的,所以用户的频繁登录验证并不会造成效率浪费。

需要 ServU-MySQL 的用户可以联系我获取, MSN/Email jianjian@SunNet.ORG , 针对实际情况稍作定制就可以使用。


Anti-Spam!

中国的IP屡次被国外的反垃圾邮件机构禁止,是因为虽然垃圾邮件这东西全世界都有,但是唯以中国最甚。在宽带普及的今天,那些spammer为了个人利益,或者大量出售email地址,或者就下载一个spammer软件利用自己家里安装的宽带开始发群发邮件,这些垃圾邮件一方面让邮件用户疲于删除,另一方面,这些发送垃圾邮件的垃圾们还把垃圾邮件的From设置成无辜者的邮件地址,着实可恶。

在法律开始惩罚这些垃圾们之前,我们可以找出这些垃圾的共同点,采用手段杜绝其中大部分邮件。

对付中国特色的垃圾邮件,首先要杜绝那些假冒发送者身份的spammer,这些人几乎都是安了个宽带坐在家里的无证经营的野鸡。单靠IP黑名单不是好办法,一来太累,而来对付那些流动作案的野鸡不得力(恰恰是这部分野鸡造成了绝大多数的垃圾邮件)。黑名单始终是后发制人,在IRTF没有确定最终解决办法之前,我们可以用如下的办法和流程先发杜绝大部分的垃圾(也许是99%甚至100%):

理念:连接到我们服务器的除了是合法的本地用户,就只能是其他服务提供商的正规SMTP服务器。

1: 首先让自己的smtp服务器实现本地用户发信的身份认证,这样可以保证我们的本地用户毫无障碍地通过我们发信。
2: 如果来者不是本地用户,就验证要求它是一个合法的SMTP服务器。 取他的 Mail from : 声明的mailhost值, 并且严格要求一定要是 name@host 格式,比如 xxx@anti-spam.org.cn, 否则枪毙。 取到 mailhost以后, 解析出其中的host,在这里就是 anti-spam.org.cn, 随即我们通过dns查询 anti-spam.org.cn所声明的 MX 值列表, 这里只有一个IP值就是 211.157.2.93, 现在把来者的IP与211.157.2.93相比较, 如果相差太远,就判定为spammer,枪毙。 比较的办法分严格类型和宽松类型,严格类型就是要求发送者的IP与它号称的mail from所指定的host的MX记录在一个C CLASS段,宽松类型则要求在一个B CLASS段(MSN.COM的邮件要求必须使用B CLASS认证,其他大部分SMTP SERVER只需要C CLASS验证就可以了)。
3: 如果一个拥有合法domain的人脑子抽筋,用他自己的服务器来发垃圾邮件,那就轮到翠花,上黑名单--这种情况毕竟少之又少,有庙的和尚还是不肯乱来的。

其实最好的办法是BIND里面增加一个类似MX的资源字段,比如 MS(Mail Sender)域,列出该domain name所声明的所有smtp服务器的IP地址列表,所有不来自这些IP的,但是号称属于该domain的mail sender均不可信任,这样就可以彻底堵住SMTP协议中任意伪造mail from的天生漏洞。 我询问了一下isc bind team, 回信说“yes, such a thing could be added, and is under discussion in the IRTF ASRG. various competing proposals have been made. BIND will implement whatever standard the IRTF/IETF makes in this area.”看来是有希望,一旦这种机制成立并实现,那我们就可以精确比较以彻底防止伪造mail sender的spammer了。

下面是我修改的qmail-smtpd.c的有关部分, 以+号开头的是添加的代码。之所以在 smtp_rcpt()而不是 smtp_mail()里面验证发信者的mail from, 是因为我们自己还有其他原因。

+ void die_badmailfrom() {out("451 sorry, your envelope sender has unacceptable format\r\n"); flush(); _exit(1);}

+ void die_mightspam() {out("451 sorry, your envelope sender's mail host doesnt match your IP, looks like spammer\r\n"); flush(); _exit(1);}

void smtp_rcpt(arg)
char *arg;
{
if (!seenmail) { err_wantmail(); return; }
if (!addrparse(arg)) { err_syntax(); return; }
if (flagbarf) { err_bmf(); return; }
if (relayclient)
{
--addr.len;
if (!stralloc_cats(&addr,relayclient)) die_nomem();
if (!stralloc_0(&addr)) die_nomem();
}
else
{
if (!addrallowed()) { err_nogateway(); return; }
+ if( fakehelo && remoteip && !isgodip )
+ {
+ /*
+ * If the helo is faked, we try to find the MX IP of his mail_from host,
+ * and check his remote ip against to the MX IP
+ */
+ unsigned long r;
+ int j;
+ stralloc sa = {0};
+ ipalloc ia = {0};
+ struct ip_address ipa;

+ /* make up the sa as from @host */
+ for(j=0;j<mailfrom.len;j++)
+ {
+ if(mailfrom.s[j]=='@')
+ {
+ if(!stralloc_copyb(&sa, &mailfrom.s[++j], mailfrom.len-j))
+ die_nomem();
+ goto _n;
+ }
+ }
+ die_badmailfrom();
+_n:
+ r = now() + getpid();
+ dns_init(0);
+ j = dns_mxip(&ia,&sa,r);
+ switch(j)
+ {
+ case DNS_HARD:
+ case DNS_SOFT:
+ case DNS_MEM:
+ die_nomem();
+ break;
+ }
+ for(j=0;j < ia.len;++j)
+ {
+ if(!ip_scan(remoteip, &ipa))
+ continue;
+ if(!memcmp(ia.ix[j].ip.d, ipa.d, 2)) /* 2 是 B类网检查,3是C类网,4是完全精确匹配 */
+ {
+ alloc_free(sa.s);
+ alloc_free(ia.ix);
+ goto _ok;
+ }
+ }
+ die_mightspam();
+ }
}
+_ok:
if (!stralloc_cats(&rcptto,"T")) die_nomem();
if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
if (!stralloc_0(&rcptto)) die_nomem();
out("250 ok\r\n");
}

--------------------------------------
这是我坐在家里,伪造sender向 vip.sina.com 发邮件的记录:
$ telnet 202.106.182.160 25
Trying 202.106.182.160...
Connected to TCE-E-7-182-160.bta.net.cn.
Escape character is '^]'.
220 vip.sina.com.cn ESMTP
helo dude
250 vip.sina.com.cn
mail from : <qzheng@anti-spam.org.cn>
250 ok
rcpt to: whoever@vip.sina.com
250 ok
data
354 请继续 - go ahead
Subject: Test mail

.
250 ok 1077223315 qp 39644


这是伪造sender向加装anti-spam的sunnet.org 发邮件的记录:
$ telnet 61.151.248.17 25
Trying 61.151.248.17...
Connected to 61.151.248.17.
Escape character is '^]'.
220 SunNet.ORG SMTP server ESMTP
helo dude
250 SunNet.ORG SMTP server
mail from: <qzheng@anti-spam.org.cn>
250 ok
rcpt to: whoever@sunnet.org
451 sorry, your envelope sender's mail host doesnt match your IP, looks like spammer
Connection closed by foreign host.

 


sunmwd MySQL 中间件


在高负载高并发的环境中,两层架构已经被证明是很难满足的,所以必须要引入三层架构以解决SQL服务器成为频颈的情况。
sunmwd (SunNet MiddleWare)开发于2001年, sunmwd 1.1版本在榕树下(rongshu.com)运行了三年,每天都在经受上千万次查询的考验,其性能令人相当满意。因为其源代码是为商业公司开发的,所以不能公布。
sunmwd 以线程方式运行在Linux/FreeBSD环境,主要是以connection pool的模式保持对SQL服务器的永久链接,其对客户接口则相当快速,以减少传统两层架构中高并发对SQL服务器导致的线程等待现象,并减少SQL服务器不停接受新的链接请求,资源分配,请求处理,客户注销的巨大时间消耗。
sunmwd 中同时还允许有预植入的 sunmwd call, 这样外部客户程序可以以一条简单的命令实现复杂的SQL调用处理。

如下是工业应用中的sunmwd的status状态

(linux):~$ telnet localhost 7483
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
100 SunNet MiddleWare <jianjian@SunNet.ORG> started from 2004-3-11 23:23
login jianjian ****
102 Login successful.
stat
204 Server has been run 49 days 5 hours 9 minutes, total 157,322,317 connected, currently 5 clients, 10 threads (8 waitting), 2,199,330,229 questions

这个中间件在50天之内经受了1.5亿次连接,21亿多次查询。

Sunmwd已经于2002年出售给一家软件公司商业化。


HTTPD.RSD

HTTPD.RSD 是后来为了榕树下文章阅读而专门编写的HTTPD服务器,它不使用fork, 以轮询方式处理客户请求,并且在UNIX环境下采用了DLL技术,附属于HTTPD.RSD的各项应用可以变写成 .so 的动态连接库并存放于HTTPD.RSD的指定目录。 比如 http://article.rongshuxia.com/viewart.rs?aid=10000, 这个 viewart.rs 其实是一个动态连接库 viewart.rs.so. 由于不使用fork()以及apache的运行完毕即退出的CGI机制, viewart.rs.so 可以常驻内存,这样就有了机会将文章预格式化以后存放在内存中,当下次有人再阅读这篇文章的时候,即直接从内存中拿出来,减少了大量磁盘读取,格式化文章的时间消耗,同时又加入了内存淘汰机制,在定期的淘汰过程中,那些长久不被访问的文章会被自动淘汰出内存,以保持一个合理的,良好的内存占用率。从而获得最佳的时间--空间兑换.

运行于榕树下的WEB聊天室 http://img.rongshuxia.com:8033/vchat.rs?channel=rongshu&user=guest 也是另一个附属于HTTPD.RSD的DLL应用。

HTTPD.RSD 运行在 Linux/FreeBSD 环境下, 因其源代码所有权属于商业公司,所以不能公布。

对于HTTPD.RSD,有朋友用ab做过benchmark, 并发1000多个连接flood请求文章查看时响应速度依旧在零秒级。目前我正在开展移植HTTPD.RSD到WIN32的工作。


HSL(HIRC Scripting Language) v2.0

HSL是2001年开发HIRC 2.0的时候实现的脚本解释语言引擎,其语法类似C,但是HSL v1扩展性不高,并且只能用BCB在WIN32环境下编译。因此开发了HSL2.0以做为 HIRC 3.0的技术基础并试图在将来可能的情况下应用于UNIX环境(尤其是游戏服务器的脚本系统),只是因为到后来我已经对IRC开发完全失去兴趣,所以HSL2.0引擎从来没投入过使用,甚至HSL2.0从来没开发完整过。但是HSLv2.0的基本引擎已经完成, 所需要做的只是为HSL的函数库增加各种预植入的HSL系统级功能调用。
下面是一个很短的HSL 脚本:

int i=10; /* declare the variable i and assign the value 10 */
print("The char is "+itoa(i)+"\n"); /* This will print the string "The char is 10" */
i++; /* increase i */
print("now i is "+itoa(i)+"\n"); /* Now print "now i is 11" */
int b; /* declear b */
print("get the random: "+itoa((b=random(1000)))+"\n"); /* Get random value from [0,1000] and assign to b , and print the string*/
if(b%2) /* if b is odd */
print("b is odd\n");
else
print("even\n");

HSL v2.0引擎能够快速解释并执行类似的脚本,其中 print(), itoa(), random() 就是以C语言预植入HSL引擎的库函数。
您可以看到, HSL语法虽然类C, 但是它支持了C++中的字符联.

HSL v2.0是顺序解释并执行脚本, 支持 if else, while, break等条件流程控制,并且支持函数原型预定义, 以提供脚本错误的详细说明
HSL 的开发是相当令人头痛的,因为你是在用一种语言开发另外一种语言,所以经常被自己搞迷糊:我现在是在用C语言呢,还是在用HSL语言。
点击这里下载已经完成的HSL v2.0引擎源代码.


LZW

这是在研究GIF图象格式的时候实践LZW压缩算法的源程序. 下载源代码


HIRC

HIRC2的最新版本在 这里下载 因为工作原因,我已经暂停HIRC的开发(也许永远不会再开发了) 如果你需要HIRC2的源代码,可以写信问我索取. (Borland CPP Builder 5.0/win32)



udp_proxy

透明代理UDP应用,主要用于继续架设 CS 1.1服务器 这里下载源代码
chaind hash --mw_hash[c,h] 的实现中有两个bug被修正:
其中一个是在free链表的时候没有对NULL指针做判别。 另外一个很不容易发现的问题是: 假设有两个key,内容分别是 "c", "code", 而它们的hashkey恰好相同,"code"位于"c"的前一个节点,那么当 我们搜寻"c"的时候,我们定位到他们所在的链表头, 此时我们先遇到 "code", 而我们的bcmp("code","c",1)会 返回"相同",也就是说 "code" 被认为是"c". 现在加入了一个 keylen 字段,修复了这个bug. 这个问题是在实践LZW算法的时候发现的。 这个程序是最近为了在FreeBSD/Linux下面架设免CDKEY的CS Server而写的,同时也可以透明代理oicq, icq之类的 应用UDP的程序。 WON.NET不给我们这些1.1的爱好者验证了,所以只好写了这个程序来做免CDKEY的服务器

编译udp_proxy非常简单:(FreeBSD下面你需要gnu make--gmake,也就是Linux自带的make了)

$ tar zxf udp_proxy.tgz
$ cd udp_proxy
$ gmake clean dep all

即可完成
架设免CDKEY的CS SERVER,首先你需要把你的CS Server运行在LAN GAME MODE, (加上 +sv_lan 1 -nomaster 参数), 同时也需要把它的IP地址指定在loopback(或者其他的内部IP上面),端口也要调开,举例来说,SunNet CS Server 的公用IP地址是 61.151.251.135, 端口27015, 那么我首先用如下命令将CS Server运行于LAN GAME:

./hlds_run '-game cstrike +ip 127.0.0.1 +port 27014 +maxplayers 21 +map de_dust2 -nomaster +sv_ lan 1' &

然后再运行udp_proxy来代理外部玩家的udp packets:

./udp_proxy -H 61.151.251.135 -P 27015 -h 127.0.0.1 -p 27014 -t 30

这样其他CS玩家就可以用 61.151.251.135:27015 上服务器来玩了

如果你要在你的服务器上为朋友架设一个上qq的透明代理,使用如下命令即可:

./udp_proxy -P 8000 -h 202.96.170.166 -p 8000 -t 20

udp_proxy跟socks服务器不同之处,在于udp_proxy是不需要任何身份验证的透明代理,客户端软件不支持任何代理,只要把服务器地址填为udp_proxy的地址即可。