如何编写动态服务器需求文档
在一些特殊应用场合,客户希望在服务器上生成文档的同时并填充数据,客户端的页面不显示打开文档,但是服务器上生成文档对服务器压力很大,目前服务器上生成文档第一种就是方式是jacob, 但是局限于windows平台,往往许多JAVA程序运行于其他操作系统,在此不讨论该方案。二是POI。 但是它的excel处理还凑合, word模块还局限于读取word的文本内容,写word文件的功能就更弱;还有一个要命的地方,处理doc格式和处理docx格式的类几乎完全不同,要分开针对不同的格式写不同的代码,这就意味着用户上传的docx格式文件如果使用了doc的扩展名,程序马上崩溃。而且个人认为poi结构混乱,编码比较复杂,开发过程非常消耗时间和精力。PageOffice提供了FileMakerCtrl组件,FileMakerCtrl是在客户端生成文档并上传到服务器,但是不会在Web网页里显示word文档,因此采用FileMakerCtrl生成word文件有两个优点:1 在客户端生成word文档,不会对服务器造成任何压力;2 生成的文档属于标准的word文档格式。
二、 核心代码
1制作模板,打开word模板文件,在文件中插入书签:PO_company、PO_year、PO_number,如下图所示:
2 动态填充word文档并上传到服务器
FileMakerCtrl fmCtrl = new FileMakerCtrl(request);
//设置PageOffice的服务页面
fmCtrlsetServerPage(requestgetContextPath()+"/poserverzz");
WordDocument doc = new WordDocument();
//给数据区域赋值,即把数据填充到模板中相应的位置
docopenDataRegion("PO_company")setValue("北京某某软件有限公司 ");
docopenDataRegion("PO_year")setValue("2018");
doc
fssaveToFile(requestgetSession()getSer
个人电脑(windows)可以作为服务器。
准备工作:
在局域网中只需知道自己在局域网的ip即可,互联网需要把个人电脑暴露在互联网下,有自己的公网ip。
一、作为web服务器
启动电脑iis信息服务,将制作好的网页文件放在信息服务站点目录下即可。iis仅支持asp或者aspnet,php需自行安装服务端应用程序。
二、作为ftp服务器
iis信息服务也有,亦可安装第三方软件。进行用户设置,权限设置即可完成。
三、作为软件服务器
自行编写服务端软件即可,较为专业。
如何把自己电脑设置成服务器
这里我们讲一下怎样把自己的电脑变成一台网站服务器来使用, 把自己的电脑用作网站服务器并不难。
无需别的硬件或软件就能够轻松实现, 只需利用Windows95OSR2中提供的"个人Web服务"(PersonalWebServer)就可以。
如果想功能更加多一些的话, 就需要用 WindowsNTServer40。 您甚至还可以创作自己的HomePage发布信息、进行文件传送或开展网上讨论等等。
总言而之, 过去我们只能在网络上看见的, 现在全部都可以自己来完成。
一、Web服务器功能的设置 安装完Windows95OSR2后, 应该考虑让别人用什么样的方法来访问我们的Web服务器, 如用电话拨号来访问, 就应该安装像Plus之类的电话拨号服务软件。
如果只能提供电话拨号服务, 建议采用后一种方案, 即用 WindowsNTServer40, 因为它能提供256条电话线同时拨号。
当然我们也可以自己先连上一个公共的网络服务器, 让别人从这个服务器访问我们的Web服务器, 但必须要有一个固定的IP地址 , 并在这个服务器的DNS(域名服务器)中注册。
如果只有IP地址, 但没有在DNS中注册, 那么局域网以外的用户就不能访问该个人Web服务器。
不管用什么方式, 我们都要为计算机设置TCP/IP协议和Web个人服务器功能。
这可以在"网上邻居"中设置。
方法非常简单, 只需在"网上邻居"的"属性"中添加TCP/IP协议, 然后在"TCP/IP属性"中的"IP地址"标签中选择"指定IP地址"并输入正确的 IP地址。 在"DNS配置"标签中选择"使DNS无效"。
安装个人Web服务器的方法如下:
(1)在"网上邻居"的"属性"对话框"配置"标签中单击"添加"按钮;
(2)在弹出的"选择网络组件类型"对话框中, 在"厂商"栏中选择"Microsoft", 在"网络服务"栏中选择"PersonalWebServer", 然后单击"确定"按钮。这时Windows95将自动配置系统。 机器启动后, 在Windows95所在驱动器的根目录下面将建立一个名为Webshare的子目录, 其下还有三个子目录:WWWroot、Scripst和FTProot, 分别存放HTML超文本文件、CGI脚本文件和FTP文件传送文件。
安装完成后, 先可以进行一下测试。 启动机器, 在"控制面板"中双击"个人Web服务器"图标, 在"服务"选项栏中分别选择"HTTP"和"FTP"并单击"启动", 这样就开启这两项服务功能。 也可以设成"自动方式", 这样每次开机后, 就自动启动Web服务器功能。 启动Web服务器功能后, 我们可以打开 WWW浏览器(如IE), 在地址栏中键入我们的IP地址, 马上就会出现Web个人服务器中自带的一个主页。
然后可以在一个局域网中(如Intranet 网)进行测试。 方法是在网上的其他计算机中, 用WWW浏览器键入用作个人Web服务器的计算机的注册名, 这时应当出现同样的主页。 如果不能看见, 可以在个人Web服务器上用DOS方式运行一下winipcfg命令, 看一看这台计算机的IP地址是不是正确。
下面介绍怎样在WindowsNTServer40中文版中设置Web服务器功能。
WindowsNTServer40中文版中的Web服务功能通过 IIS20(InternetInformationServer20)来实现。 在安装WindowsNTServer40中文版的过程中 , 系统会提示"是否安装IIS20?", 应回答"安装"。 这样启动WindowsNTServer40中文版后, 就有Web服务器的功能。 和 Windows95中的情况一样, 在WindowsNT中也同样存在用什么样的方法让别人访问Web服务器的问题。
如果是在局域网中, 要安装网卡、设置协议及IP地址等。 如果准备用调制解调器让别人拨号访问, 则要安装调制解调器、电话线路。 还要在"服务项目"中添加"RAS远程服务"功能, 并在这个功能的"属性"中设置协议等等。 如果对这些设置不太清楚的话, 可以参考有关WindowsNT的书籍。
安装完成后, 可以按照前面在Windows95OSR2 中的方法进行一下测试, 看看能不能在另外的计算机中用WWW浏览器看到IIS20提供的一个主页例子。
二、制作自己的主页 要制作主页, 先要安装一些必要的网页制作软件, 笔者使用的是FrontPage97。
先把个人Web服务器中自带的主页修改成自己的主页。
方法是进入这个主页的目录(Windows95在Webshare目录的WWWroot子目录中, WindowsNT在InetPub目录的WWWroot子目录中), 找到defaulthtm文件双击打开后, 选中工作条中的"编辑", 系统会自动调出FrontPage97, 这时就可以发挥自己的才能进行一番设计。
在这个主页中, 我们能够向外发布消息, 让别人下载放在Webshare目录下FTProot子目录中的文件, 但是还不能实现双向交流, 接收访问者的信息。
三、安装CGI系统 要使Web服务器能为别人提供"写"的功能, 必须在服务器上安装CGI(通用网关接口)系统。
通用网关接口是一个信息服务器主机对外信息服务的标准接口(见图2)。
事实上, 任何一种程序语言所设计的应用程序可以在服务器主机上执行, 并利用CGI接口来传送应用服务信息, 皆称之为"CGI程序语言", 而用这种语言编写的CGI程序又被称为"脚本"。
现在常用的有Shell、VB、C/C++、Java、Perl等语言。
以PerlforWin32为例, 您可以从Internet上下载自解压文件pw32i316exe, 然后安装。
我们先来看看怎样在Windows95OSR2版中的"个人Web服务"上安装Perl系统: 启动Windows95后, 用鼠标双击下载下来的pw32i316exe, 自解包就会自动运行解压并进行安装, 整个安装过程可以全部回quot;Y"完成。
完成后一般会在C盘根目录下建立一个名为Perl的目录, 并在其下建立几个子目录, 其中在Bin子目录中有一个名为 perlexe的可执行程序, 这就是Perl系统的主程序。
但是这时的Perl主程序并不能运行按Perl语言方法编写的CGI脚本, 所以还必须修改 Windows95的注册表。
方法是先运行注册表编辑器regedit, 然后依次打开"HKEY_LOCAL_MACHINE \System \Currentcontrlset \Services \W3svc \Parameters \ScriptMap\", 加入"pl'C:\PERL\BIN\perlexe%s%s'"。
退出注册表文件后, 重新启动计算机, 然后进入Perl 目录下的Eg子目录, 从中任选一个扩展名为pl的文件并双击, 这时系统会出现一个列表窗口, 询问使用什么程序来打开这个文件, 这时应该在可选的可执行文件列表中找到"perl"(如果找不到可以在"其他"项中按路径来指定), 并选定"始终使用该软件打开这种类型的文件"项, 单击"确定"即可。
在 WindowsNTServer40中的安装过程与Windows95基本一样, 只是修改注册表时应运行regedit32, 加入的内容为:"pl:REG_SZ:C:\Perl\Bin\perlexe%s%s"。
安装完成Perl系统后, 我们就可以写一个CGI脚本试一试。
下面的例子中, 程序1是用Perl语法规则写成的一个CGI脚本, 这是一个 ASCII文本文件, 可用一般的记事本来编写, 完成后用samplepl文件名存盘。
注意不论是在WindowsNTServer40还是在 Windows95中, 都应把这个文件放在Scripts的子目录中。
程序2是相应的HTML文件, 这个文件应放在WWWroot子目录中。
接下来我们可以运行测试。
打开WWW浏览器, 在地址栏中键入:http://您的Web服务器名/程序2的文件全名, 会出现一个填写界面(见图 3), 当输入数据完成后, 单击"确定"按钮, 马上就会把您填写的这些内容写在屏幕上(见图4)。
当然, 我们也可以从网上的另一台计算机来测试。 这仅仅是一个很简单的例子, 要想得到更好的结果(如问卷调查、网上留言、BBS等), 就要进一步学习一些有关Perl语言的知识。
总而言之, 通过不懈努力, 完全可以使我们的个人Web服务器达到专业水平。如何把自己电脑设置成服务器
在本文中我希望能给初学者一些帮助,大致讲一下编写windows服务程序需要的知识。
首先Microsoft Windows 服务(即,以前的NT 服务)使您能够创建在它们自己的Windows 会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这使服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。还可以在不同于登录用户的特定用户帐户或默认计算机帐户的安全上下文中运行服务。
服务是有状态的,当我们使用windows自带的服务管理程序scexe查看服务状态时可以显示服务的当前状态,这个状态是由我们在程序代码中进行控制的。你最好在服务初始化的时候将服务设置为SERVICE_START_PENDING,当初始化完毕时设为SERVICE_RUNNING,这些状态是系统自定义的状态,可通过msdn查看其他状态。这个状态信息你会在scexe中看到。
在编写windows服务程序过程中你需要关注的函数有:
1首先是main函数,由于windows服务不需要界面,所以大部分程序为win32控制台应用程序,所以程序主函数为main 而不是WinMain()。在主函数要做的主要工作就是初始化一个SERVICE_TABLE_ENTRY 分派表结构体,然后调用StartServiceCtrlDispatcher();这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中对应于你的服务的ServiceMain()函数。ServiceMain()函数将在下面提到。
此过程示例代码如下:
SERVICE_TABLE_ENTRY entrytable[2];
entrytable[0]lpServiceName="testservice";
entrytable[0]lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;
entrytable[1]lpServiceName=NULL;
entrytable[1]lpServiceProc=NULL;
StartServiceCtrlDispatcher(entrytable);
在这之后系统将自动创建一个线程去执行ServiceMain函数的内容,你应该将你要执行的任务在ServiceMain中循环,这样服务就开始运行了。
2.ServiceMain函数为void WINAPI ServiceMain(int argc, char argv)格式的函数,函数名字可以任意定义。它的作用就是:将你需要执行的任务放到该函数中循环执行即可。这就是服务程序的工作函数。在ServiceMain执行你的任务前,需要给SERVICE_TABLE_ENTRY 分派表结构体进行赋值,注意由于此时服务还没有开始执行你的任务所以我们将服务的状态设置为SERVICE_START_PENDING,即正在初始化。我们进行如下赋值:
servicestatusdwServiceType = SERVICE_WIN32;
servicestatusdwCurrentState = SERVICE_START_PENDING;
servicestatusdwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;
//在本例中只接受系统关机和停止服务两种控制命令
servicestatusdwWin32ExitCode = 0;
servicestatusdwServiceSpecificExitCode = 0;
servicestatusdwCheckPoint = 0;
servicestatusdwWaitHint = 0;
hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);
CtrlHandler为void WINAPI CtrlHandler(DWORD request)型的函数,函数名字可以任意设定。将在下一点讲到。
Hstatus为SERVICE_STATUS_HANDLE类型的全局变量。当需要改变服务状态时SetServiceStatus()函数需要它做为参数来标识一个服务。
3 void WINAPI CtrlHandler(DWORD request),函数的主要功能是,接收系统传递的控制命令,比如当你通过scexe关闭服务时,该函数会收到SERVICE_CONTROL_STOP消息,你就可以对服务进行必要的管理。在本例子程序中就只接收SERVICE_ACCEPT_SHUTDOWN和SERVICE_ACCEPT_STOP消息,这是通过前面给servicestatus赋值设定的。
这样一个基本的服务程序就完成了。
下面贴出我的示例代码仅供参考。该代码在vs2008中调试通过。本文结束的时候会附上如何安装服务。
#include <stdioh>
#include <Windowsh>
#define SLEEP_TIME 5000 //间隔时间
#define FILE_PATH "C:\\logtxt" //信息输出文件
bool brun=false;
SERVICE_STATUS servicestatus;
SERVICE_STATUS_HANDLE hstatus;
int WriteToLog(char str);
void WINAPI ServiceMain(int argc, char argv);
void WINAPI CtrlHandler(DWORD request);
int InitService();
int WriteToLog(char str)
{
FILE pfile;
fopen_s(&pfile,FILE_PATH,"a+");
if (pfile==NULL)
{
return -1;
}
fprintf_s(pfile,"%s\n",str);
fclose(pfile);
return 0;
}
void WINAPI ServiceMain(int argc, char argv)
{
servicestatusdwServiceType = SERVICE_WIN32;
servicestatusdwCurrentState = SERVICE_START_PENDING;
servicestatusdwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;//在本例中只接受系统关机和停止服务两种控制命令
servicestatusdwWin32ExitCode = 0;
servicestatusdwServiceSpecificExitCode = 0;
servicestatusdwCheckPoint = 0;
servicestatusdwWaitHint = 0;
hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);
if (hstatus==0)
{
WriteToLog("RegisterServiceCtrlHandler failed");
return;
}
WriteToLog("RegisterServiceCtrlHandler success");
//向SCM 报告运行状态
servicestatusdwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hstatus, &servicestatus);
//下面就开始任务循环了,你可以添加你自己希望服务做的工作
brun=true;
MEMORYSTATUS memstatus;
char str[100];
memset(str,'\0',100);
while (brun)
{
GlobalMemoryStatus(&memstatus);
int availmb=memstatusdwAvailPhys/1024/1024;
sprintf_s(str,100,"available memory is %dMB",availmb);
WriteToLog(str);
Sleep(SLEEP_TIME);
}
WriteToLog("service stopped");
}
void WINAPI CtrlHandler(DWORD request)
{
switch (request)
{
case SERVICE_CONTROL_STOP:
brun=false;
servicestatusdwCurrentState = SERVICE_STOPPED;
break;
case SERVICE_CONTROL_SHUTDOWN:
brun=false;
servicestatusdwCurrentState = SERVICE_STOPPED;
break;
default:
break;
}
SetServiceStatus (hstatus, &servicestatus);
}
void main()
{
SERVICE_TABLE_ENTRY entrytable[2];
entrytable[0]lpServiceName="testservice";
entrytable[0]lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;
entrytable[1]lpServiceName=NULL;
entrytable[1]lpServiceProc=NULL;
StartServiceCtrlDispatcher(entrytable);
}
如何安装服务:
运行命令提示符cmdexe
输入sc create testservicename binpath= D:\testexe
输入sc start testservicename 启动服务
输入sc query 会在最底部显示你的服务当前的状态
输入sc stop testservicename 停止服务
输入sc delete testservicename删除服务,该服务将在下次重启后删除,在重启之前将不能注册同一个名字的服务。
自己写俩软件 一个建个服务器组件 一个是客户端组件
客户端连接上服务器以后 发送数据 最前面加个标识符 比如前面加个Y是用户名 M是密码
然后服务器收到数据以后 判断以下收到的数据取文本最前面标识符是什么 然后相应自己写出到一个TXT文件里保存
以此类推 以这种发送封包然后服务器判断封包标识符来保存这些数据
服务器最后再加上 判断客户端登录的帐号 然后发送数据 也是用封包传送 等级 物品 金钱之类的东西 客户端登录成功以后 通过服务器来获取这些信息
如果你觉得麻烦的话直接保存到他本地的文件里 写配置项然后加密 保存到C盘他一般找不到的地方 然后每次打开的时候读取本地保存的配置项就可以了
代理服务器的应用非常广泛。比如,在企业网内部,它可以用来控制员工在工作时浏览的Internet内容,阻止员工访问某些类型的内容或某些指定的网站。代理服务器实际上扮演着浏览器和Web服务器之间的中间人的角色,能够对浏览器请求进行各种各样的处理,能够过滤广告和Cookie,能够预先提取Web页面,使得浏览器访问页面的速度更快,等等。
一、基础知识
不管以哪种方式应用代理服务器,其监控HTTP传输的过程总是如下:
步骤一:内部的浏览器发送请求给代理服务器。请求的第一行包含了目标URL。
步骤二:代理服务器读取该URL,并把请求转发给合适的目标服务器。
步骤三:代理服务器接收来自Internet目标机器的应答,把应答转发给合适的内部浏览器。
例如,假设有一个企业的雇员试图访问wwwcnibmcom网站。如果没有代理服务器,雇员的浏览器打开的Socket通向运行这个网站的Web服务器,从Web服务器返回的数据也直接传递给雇员的浏览器。如果浏览器被配置成使用代理服务器,则请求首先到达代理服务器;随后,代理服务器从请求的第一行提取目标URL,打开一个通向wwwcnibmcom的Socket。当wwwcnibmcom返回应答时,代理服务器把应答转发给雇员的浏览器。
当然,代理服务器并非只适用于企业环境。作为一个开发者,拥有一个自己的代理服务器是一件很不错的事情。例如,我们可以用代理服务器来分析浏览器和Web服务器的交互过程。测试和解决Web应用中存在的问题时,这种功能是很有用的。我们甚至还可以同时使用多个代理服务器(大多数代理服务器允许多个服务器链接在一起使用)。例如,我们可以有一个企业的代理服务器,再加上一个用java编写的代理服务器,用来调试应用程序。但应该注意的是,代理服务器链上的每一个服务器都会对性能产生一定的影响。
二、设计规划
正如其名字所示,代理服务器只不过是一种特殊的服务器。和大多数服务器一样,如果要处理多个请求,代理服务器应该使用线程。下面是一个代理服务器的基本规划:
等待来自客户(Web浏览器)的请求。
启动一个新的线程,以处理客户连接请求。
读取浏览器请求的第一行(该行内容包含了请求的目标URL)。
分析请求的第一行内容,得到目标服务器的名字和端口。
打开一个通向目标服务器(或下一个代理服务器,如合适的话)的Socket。
把请求的第一行发送到输出Socket。
把请求的剩余部分发送到输出Socket。
把目标Web服务器返回的数据发送给发出请求的浏览器。
当然,如果考虑细节的话,情况会更复杂一些。实际上,这里主要有两个问题要考虑:第一,从Socket按行读取数据最适合进一步处理,但这会产生性能瓶颈;第二,两个Socket之间的连接必需高效。有几种方法可以实现这两个目标,但每一种方法都有各自的代价。例如,如果要在数据进入的时候进行过滤,这些数据最好按行读取;然而,大多数时候,当数据到达代理服务器时,立即把它转发出去更适合高效这一要求。另外,数据的发送和接收也可以使用多个独立的线程,但大量地创建和拆除线程也会带来性能问题。因此,对于每一个请求,我们将用一个线程处理数据的接收和发送,同时在数据到达代理服务器时,尽可能快速地把它转发出去。
三、实例
在用java编写这个代理服务器的过程中,注意可重用性是很重要的。因为这样的话,当我们想要在另一个工程中以不同的方式处理浏览器请求时,可以方便地重用该代理服务器。当然,我们必须注意灵活性和效率之间的平衡。
图一显示了本文代理服务器实例(HttpProxyjava)的输出界面,当浏览器访问http://www-900ibmcom/cn/时,代理服务器向默认日志设备(即标准输出设备屏幕)输出浏览器请求的URL。图二显示了SubHttpProxy的输出。SubHttpProxy是HttpProxy的一个简单扩展。
图一
图二
为了构造代理服务器,我从Thread基类派生出了HttpProxy类(文章正文中出现的代码是该类的一些片断,完整的代码请从本文最后下载)。HttpProxy类包含了一些用来定制代理服务器行为的属性,参见Listing 1和表一。
Listing 1
/
一个基础的代理服务器类
/
import javanet;
import javaio;
public class HttpProxy extends Thread {
static public int CONNECT_RETRIES=5;
static public int CONNECT_PAUSE=5;
static public int TIME-OUT=50;
static public int BUFSIZ=1024;
static public boolean logging = false;
static public OutputStream log=null;
// 传入数据用的Socket
protected Socket socket;
// 上级代理服务器,可选
static private String parent=null;
static private int parentPort=-1;
static public void setParentProxy(String name, int pport) {
parent=name;
parentPort=pport;
}
// 在给定Socket上创建一个代理线程。
public HttpProxy(Socket s) { socket=s; start(); }
public void writeLog(int c, boolean browser) throws IOException {
logwrite(c);
}
public void writeLog(byte[] bytes,int offset,
int len, boolean browser) throws IOException {
for (int i=0;i<len;i++) writeLog((int)bytes[offset+i],browser);
}
// 默认情况下,日志信息输出到
// 标准输出设备,
// 派生类可以覆盖它
public String processHostName(String url, String host, int port, Socket sock) {
javatextDateFormat cal=javatextDateFormatgetDateTimeInstance();
Systemoutprintln(calformat(new javautilDate()) + " - " +
url + " " + sockgetInetAddress()+"<BR>");
return host;
}
表一
变量/方法 说明
CONNECT_RETRIES 在放弃之前尝试连接远程主机的次数。
CONNECT_PAUSE 在两次连接尝试之间的暂停时间。
TIME-OUT 等待Socket输入的等待时间。
BUFSIZ Socket输入的缓冲大小。
logging 是否要求代理服务器在日志中记录所有已传输的数据(true表示“是”)。
log 一个OutputStream对象,默认日志例程将向该OutputStream对象输出日志信息。
setParentProxy 用来把一个代理服务器链接到另一个代理服务器(需要指定另一个服务器的名称和端口)。
当代理服务器连接到Web服务器之后,我用一个简单的循环在两个Socket之间传递数据。这里可能出现一个问题,即如果没有可操作的数据,调用read方法可能导致程序阻塞,从而挂起程序。为防止出现这个问题,我用setSoTimeout方法设置了Socket的超时时间(参见Listing 2)。这样,如果某个Socket不可用,另一个仍旧有机会进行处理,我不必创建一个新的线程。
Listing 2
// 执行操作的线程
public void run() {
String line;
String host;
int port=80;
Socket outbound=null;
try {
socketsetSoTimeout(TIMEOUT);
InputStream is=socketgetInputStream();
OutputStream os=null;
try {
// 获取请求行的内容
line="";
host="";
int state=0;
boolean space;
while (true) {
int c=isread();
if (c==-1) break;
if (logging) writeLog(c,true);
space=CharacterisWhitespace((char)c);
switch (state) {
case 0:
if (space) continue;
state=1;
case 1:
if (space) {
state=2;
continue;
}
line=line+(char)c;
break;
case 2:
if (space) continue; // 跳过多个空白字符
state=3;
case 3:
if (space) {
state=4;
// 只分析主机名称部分
String host0=host;
int n;
n=hostindexOf("//");
if (n!=-1) host=hostsubstring(n+2);
n=hostindexOf('/');
if (n!=-1) host=hostsubstring(0,n);
// 分析可能存在的端口号
n=hostindexOf(":");
if (n!=-1) {
port=IntegerparseInt(hostsubstring(n+1));
host=hostsubstring(0,n);
}
host=processHostName(host0,host,port,socket);
if (parent!=null) {
host=parent;
port=parentPort;
}
int retry=CONNECT_RETRIES;
while (retry--!=0) {
try {
outbound=new Socket(host,port);
break;
} catch (Exception e) { }
// 等待
Threadsleep(CONNECT_PAUSE);
}
if (outbound==null) break;
outboundsetSoTimeout(TIMEOUT);
os=outboundgetOutputStream();
oswrite(linegetBytes());
oswrite(' ');
oswrite(host0getBytes());
oswrite(' ');
pipe(is,outboundgetInputStream(),os,socketgetOutputStream());
break;
}
host=host+(char)c;
break;
}
}
}
catch (IOException e) { }
} catch (Exception e) { }
finally {
try { socketclose();} catch (Exception e1) {}
try { outboundclose();} catch (Exception e2) {}
}
}
和所有线程对象一样,HttpProxy类的主要工作在run方法内完成(见Listing 2)。run方法实现了一个简单的状态机,从Web浏览器每次一个读取字符,持续这个过程直至有足够的信息找出目标Web服务器。然后,run打开一个通向该Web服务器的Socket(如果有多个代理服务器被链接在一起,则run方法打开一个通向链里面下一个代理服务器的Socket)。打开Socket之后,run先把部分的请求写入Socket,然后调用pipe方法。pipe方法直接在两个Socket之间以最快的速度执行读写操作。
如果数据规模很大,另外创建一个线程可能具有更高的效率;然而,当数据规模较小时,创建新线程所需要的开销会抵消它带来的好处。
Listing 3显示了一个很简单的main方法,可以用来测试HttpProxy类。大部分的工作由一个静态的startProxy方法完成(见Listing 4)。这个方法用到了一种特殊的技术,允许一个静态成员创建HttpProxy类(或HttpProxy类的子类)的实例。它的基本思想是:把一个Class对象传递给startProxy类;然后,startProxy方法利用映像API(Reflection API)和getDeclaredConstructor方法确定该Class对象的哪一个构造函数接受一个Socket参数;最后,startProxy方法调用newInstance方法创建该Class对象。
Listing 3
// 测试用的简单main方法
static public void main(String args[]) {
Systemoutprintln("在端口808启动代理服务器\n");
HttpProxylog=Systemout;
HttpProxylogging=false;
HttpProxystartProxy(808,HttpProxyclass);
}
}
Listing 4
static public void startProxy(int port,Class clobj) {
ServerSocket ssock;
Socket sock;
try {
ssock=new ServerSocket(port);
while (true) {
Class [] sarg = new Class[1];
Object [] arg= new Object[1];
sarg[0]=Socketclass;
try {
javalangreflectConstructor cons = clobjgetDeclaredConstructor(sarg);
arg[0]=ssockaccept();
consnewInstance(arg); // 创建HttpProxy或其派生类的实例
} catch (Exception e) {
Socket esock = (Socket)arg[0];
try { esockclose(); } catch (Exception ec) {}
}
}
} catch (IOException e) {
}
}
利用这种技术,我们可以在不创建startProxy方法定制版本的情况下,扩展HttpProxy类。要得到给定类的Class对象,只需在正常的名字后面加上class(如果有某个对象的一个实例,则代之以调用getClass方法)。由于我们把Class对象传递给了startProxy方法,所以创建HttpProxy的派生类时,就不必再特意去修改startProxy。(下载代码中包含了一个派生得到的简单代理服务器)。
结束语
利用派生类定制或调整代理服务器的行为有两种途径:修改主机的名字,或者捕获所有通过代理服务器的数据。processHostName方法允许代理服务器分析和修改主机名字。如果启用了日志记录,代理服务器为每一个通过服务器的字符调用writeLog方法。如何处理这些信息完全由我们自己决定——可以把它写入日志文件,可以把它输出到控制台,或进行任何其他满足我们要求的处理。writeLog输出中的一个Boolean标记指示出数据是来自浏览器还是Web主机。
和许多工具一样,代理服务器本身并不存在好或者坏的问题,关键在于如何使用它们。代理服务器可能被用于侵犯隐私,但也可以阻隔偷窥者和保护网络。即使代理服务器和浏览器不在同一台机器上,我也乐意把代理服务器看成是一种扩展浏览器功能的途径。例如,在把数据发送给浏览器之前,可以用代理服务器压缩数据;未来的代理服务器甚至还可能把页面从一种语言翻译成另一种语言……可能性永无止境。
0条评论