1、引言
很多人都看过Eric Steven Raymond写的<<The Cathedral and the Bazaar>> (大教堂与集市) 这篇文章吧。
这篇文章讲述了传统的开发小组开发方式和基于Internet的分散的开发方式(Linux的开发方式,GNU软件的
开发方式)的区别,并且根据自己的一个程序的开发例子来讲述了The Bazaar开发方式的若干条重要原则。
不过,国内很多程序员,工作的时候还是采用的传统的开发方式,很难有机会在工作中体验这些原则。那么,
这个例子就给了大家又一个体验这些原则的过程。
这个例子,主要是运用了一些编程的技术,比如,socket编程,信号,进程,还有一些unix socket编程的较
高级论题。当然,这些都不是主要的,重要的是,体验一下集市的开发方式。
2、开发这个proxy程序的背景
我工作的时候,处在一个比较封闭的网络环境中。我的机器在局域网 (LAN) 之中,与外界的Internet相连采
用了代理的方式,有若干台unix服务器作为代理服务器,运行squid作为http的代理,运行socks作为socks 5
代理。应该说,这样的待遇,还算不错,:-), 要浏览网站,squid够用了;要运行ICQ, OICQ之类的程序,用
socks也够了。但是,我遇到了一个比较麻烦的问题,在这样的网络环境中,我没有办法用Outlook等工具收取
非来自公司邮件服务器的邮件(比方说,@linuxaid.com.cn, @163.net, @sina.com.cn 等等);也没有办法用
Gravity等工具来收取USENET上的讨论。当然,折衷的办法还是有,我可以用linux下的一些支持socks的邮件
客户端软件和新闻组阅读软件。但是,这样势必造成一些麻烦( 实际上我也这样做过 ),当我需要收取邮件
或者阅读新闻组的时候,我必须重新启动机器转换到linux操作系统中去,而当我要办公的时候,我又不得不
重新启动机器再转换到windows操作系统中来 ( 我不得不说,linux作为办公的桌面还是不如windows, 虽然这
句话肯定会惹恼很多linux fan :-) )。作为一个程序员,我当然不能忍受这种麻烦。我必须想办法来解决这
个问题。经过考虑,我有了一个好的想法。
这体现了The Bazaar原则一:
Every good work of software starts by scratching a developer's personal itch.
每一个软件的开发都是带有开发者自己的烙印。
3、初期设计
我需要的是一个程序,他能够做"二传手"的工作。他自身处在能同时连通外界目标服务器和我的机器的位置上。
我的机器把请求发送给他,他接受请求,把请求原封不动的抄下来发送给外界目标服务器;外界目标服务器
响应了请求,把回答发送给他,他再接受回答,把回答原封不动的抄下来发送给我的机器。这样,我的机器
实际上是把他当作了目标服务器( 由于是原封不动的转抄,请求和回答没有被修改 )。而他则是外界目标
服务器的客户端( 由于是原封不动的转抄,请求和回答没有被修改 )。我把这种代理服务程序叫做"二传手"。
原理图如下:
-------------- ----------------- --------------------
------------------> ---------------->
我的机器 代理服务程序 目标服务器
<------------------ <----------------
-------------- ----------------- --------------------
4、例子重用
The Bazaar原则二:
Good programmers know what to write. Great ones know what to rewrite (and reuse).
好的程序员知道写什么。而伟大的程序员知道重写和重用什么。
基于这个原则,我当然不会从头来写这个程序(其实,这个程序是一个很小的程序,没有必要一定要这么做。
但是,为了给大家,同时也是给我自己一个集市化的开发方式的体验,我还是这么做了,我先是写出来了一个
简单的程序---附在本文最后----然后才想起来去找找有没有类似的程序 :-), 结果浪费了很多时间)。
在网上找了找,花了大概半个小时( 和我写出第一个简单程序所花的时间差不多 :-) ),找到了这个程序。
程序如下:
------------------------------------------------------------------------------------------------
/****************************************************************************
program: proxyd
module: proxyd.c
summary: provides proxy tcp service for a host on an isolated network.
programmer: Carl Harris (ceharris@vt.edu)
date: 22 Feb 94
description:
This code implements a daemon process which listens for tcp connec-
tions on a specified port number. When a connection is established,
a child is forked to handle the new client. The child then estab-
lishes a tcp connection to a port on the isolated host. The child
then falls into a loop in which it writes data to the isolated host
for the client and vice-versa. Once a child has been forked, the
parent resumes listening for additional connections.
The name of the isolated host and the port to serve as proxy for,
as well as the port number the server listen on are specified as
command line arguments.
****************************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#define TCP_PROTO "tcp"
int proxy_port; /* port to listen for proxy connections on */
strUCt sockaddr_in hostaddr; /* host addr assembled from gethostbyname() */
extern int errno; /* defined by libc.a */
extern char *sys_errlist[]; /* defined by libc.a */
void parse_args (int argc, char **argv);
void daemonize (int servfd);
void do_proxy (int usersockfd);
void reap_status (void);
void errorout (char *msg);
/****************************************************************************
function: main
description: Main level driver. After daemonizing the process, a socket
is opened to listen for connections on the proxy port,
connections are accepted and children are spawned to handle
each new connection.
arguments:
argc,argv you know what those are.
return value: none.
calls: parse_args, do_proxy.
globals: reads proxy_port.
****************************************************************************/
main (argc,argv)
int argc;
char **argv;
{
int clilen;
int childpid;
int sockfd, newsockfd;
struct sockaddr_in servaddr, cliaddr;
parse_args(argc,argv);
/* prepare an address struct to listen for connections */
bzero((char *) &servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = proxy_port;
/* get a socket... */
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
fputs("failed to create server socket
",stderr);
exit(1);
}
/* ...and bind our address and port to it */
if (bind(sockfd,(struct sockaddr_in *) &servaddr,sizeof(servaddr)) < 0) {
fputs("faild to bind server socket to specified port
",stderr);
exit(1);
}
/* get ready to accept with at most 5 clients waiting to connect */
listen(sockfd,5);
/* turn ourselves into a daemon */
daemonize(sockfd);
/* fall into a loop to accept new connections and spawn children */
while (1) {
/* accept the next connection */
clilen = sizeof(cliaddr);
newsockfd = accept(sockfd, (struct sockaddr_in *) &cliaddr, &clilen);
if (newsockfd < 0 && errno == EINTR)
continue; /* a signal might interrupt our accept() call */
else if (newsockfd < 0)
/* something quite amiss -- k