按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
个Java 应用。事实上,我们在这儿已完全跳过了 Web 服务器,仅仅需要从程序片到服务器上运行的 Java 应
用之间建立一个连接即可。
正如大家不久就会体验到的那样,尽管看起来非常简单,但实际上有一些意想不到的问题使局面显得稍微有
些复杂。用 Java 1。1 写程序片是最理想的,但实际上却经常行不通。到本书写作的时候,拥有 Java 1。1 能
力的浏览器仍为数不多,而且即使这类浏览器现在非常流行,仍需考虑照顾一下那些升级缓慢的人。所以从
安全的角度看,程序片代码最好只用Java 1。0 编写。基于这一前提,我们不能用 JAR 文件来合并(压缩)程
序片中的。class 文件。所以,我们应尽可能减少。class 文件的使用数量,以缩短下载时间。
好了,再来说说我用的 Web 服务器(写这个示范程序时用的就是它)。它确实支持Java,但仅限于 Java
1。0!所以服务器应用也必须用Java 1。0 编写。
15。5。1 服务器应用
现在讨论一下服务器应用(程序)的问题,我把它叫作NameCollecor (名字收集器)。假如多名用户同时尝
试提交他们的E…mail 地址,那么会发生什么情况呢?若 NameCollector 使用TCP/IP 套接字,那么必须运用
早先介绍的多线程机制来实现对多个客户的并发控制。但所有这些线程都试图把数据写到同一个文件里,其
中保存了所有E…mail 地址。这便要求我们设立一种锁定机制,保证多个线程不会同时访问那个文件。一个
“信号机”可在这里帮助我们达到目的,但或许还有一种更简单的方式。
如果我们换用数据报,就不必使用多线程了。用单个数据报即可“侦听”进入的所有数据报。一旦监视到有
进入的消息,程序就会进行适当的处理,并将答复数据作为一个数据报传回原先发出请求的那名接收者。若
数据报半路上丢失了,则用户会注意到没有答复数据传回,所以可以重新提交请求。
服务器应用收到一个数据报,并对它进行解读的时候,必须提取出其中的电子函件地址,并检查本机保存的
数据文件,看看里面是否已经包含了那个地址(如果没有,则添加之)。所以我们现在遇到了一个新的问
题。Java 1。0 似乎没有足够的能力来方便地处理包含了电子函件地址的文件(Java 1。1 则不然)。但是,用
C 轻易就可以解决这个问题。因此,我们在这儿有机会学习将一个非 Java 程序同 Java 程序连接的最简便方
式。程序使用的Runtime 对象包含了一个名为exec()的方法,它会独立机器上一个独立的程序,并返回一个
Process (进程)对象。我们可以取得一个OutputStream,它同这个单独程序的标准输入连接在一起;并取
得一个 InputStream,它则同标准输出连接到一起。要做的全部事情就是用任何语言写一个程序,只要它能
从标准输入中取得自己的输入数据,并将输出结果写入标准输出即可。如果有些问题不能用Java 简便与快速
地解决(或者想利用原有代码,不想改写),就可以考虑采用这种方法。亦可使用Java 的“固有方法”
(Native Method ),但那要求更多的技巧,大家可以参考一下附录A 。
1。 C 程序
这个非 Java 应用是用 C 写成,因为 Java 不适合作 CGI 编程;起码启动的时间不能让人满意。它的任务是管
理电子函件(E…mail )地址的一个列表。标准输入会接受一个E…mail 地址,程序会检查列表中的名字,判断
是否存在那个地址。若不存在,就将其加入,并报告操作成功。但假如名字已在列表里了,就需要指出这一
552
…………………………………………………………Page 554……………………………………………………………
点,避免重复加入。大家不必担心自己不能完全理解下列代码的含义。它仅仅是一个演示程序,告诉你如何
用其他语言写一个程序,并从 Java 中调用它。在这里具体采用何种语言并不重要,只要能够从标准输入中读
取数据,并能写入标准输出即可。
//: Listmgr。c
// Used by NameCollector。java to manage
// the email list file on the server
#include
#include
#include
#define BSIZE 250
int alreadyInList(FILE* list; char* name) {
char lbuf'BSIZE';
// Go to the beginning of the list:
fseek(list; 0; SEEK_SET);
// Read each line in the list:
while(fgets(lbuf; BSIZE; list)) {
// Strip off the newline:
char * newline = strchr(lbuf; 'n');
if(newline != 0)
*newline = '0';
if(strcmp(lbuf; name) == 0)
return 1;
}
return 0;
}
int main() {
char buf'BSIZE';
FILE* list = fopen(〃emlist。txt〃; 〃a+t〃);
if(list == 0) {
perror(〃could not open emlist。txt〃);
exit(1);
}
while(1) {
gets(buf); /* From stdin */
if(alreadyInList(list; buf)) {
printf(〃Already in list: %s〃; buf);
fflush(stdout);
}
else {
fseek(list; 0; SEEK_END);
fprintf(list; 〃%sn〃; buf);
fflush(list);
printf(〃%s added to list〃; buf);
fflush(stdout);
}
}
} ///:~
该程序假设 C 编译器能接受'//'样式注释(许多编译器都能,亦可换用一个C++编译器来编译这个程序)。
553
…………………………………………………………Page 555……………………………………………………………
如果你的编译器不能接受,则简单地将那些注释删掉即可。
文件中的第一个函数检查我们作为第二个参数(指向一个 char 的指针)传递给它的名字是否已在文件中。在
这儿,我们将文件作为一个FILE 指针传递,它指向一个已打开的文件(文件是在main()中打开的)。函数
fseek()在文件中遍历;我们在这儿用它移至文件开头。fgets()从文件 list 中读入一行内容,并将其置入缓
冲区 lbuf——不会超过规定的缓冲区长度BSIZE 。所有这些工作都在一个while 循环中进行,所以文件中的
每一行都会读入。接下来,用 strchr()找到新行字符,以便将其删掉。最后,用 strcmp()比较我们传递给函
数的名字与文件中的当前行。若找到一致的内容,strcmp()会返回 0。函数随后会退出,并返回一个1,指出
该名字已经在文件里了(注意这个函数找到相符内容后会立即返回,不会把时间浪费在检查列表剩余内容的
上面)。如果找遍列表都没有发现相符的内容,则函数返回0。
在main()中,我们用 fopen()打开文件。第一个参数是文件名,第二个是打开文件的方式;a+表示“追
加”,以及“打开”(或“创建”,假若文件尚不存在),以便到文件的末尾进行更新。fopen()函数返回的
是一个FILE 指针;若为 0,表示打开操作失败。此时需要用 perror()打印一条出错提示消息,并用 exit()
中止程序运行。
如果文件成功打开,程序就会进入一个无限循环。调用gets(buf)的函数会从标准输入中取出一行(记住标
准输入会与 Java 程序连接到一起),并将其置入缓冲区buf 中。缓冲区的内容随后会简单地传递给
alreadyInList()函数,如内容已在列表中,printf()就会将那条消息发给标准输出(Java 程序正在监视
它)。fflush()用于对输出缓冲区进行刷新。
如果名字不在列表中,就用fseek()移到列表末尾,并用 fprintf()将名字“打印”到列表末尾。随后,用
printf()指出名字已成功加入列表(同样需要刷新标准输出),无限循环返回,继续等候一个新名字的进
入。
记住一般不能先在自己的计算机上编译此程序,再把编译好的内容上载到 Web 服务器,因为那台机器使用的
可能是不同类的处理器和操作系统。例如,我的Web 服务器安装的是 Intel 的CPU,但操作系统是Linux,所
以必须先下载源码,再用远程命令(通过telnet)指挥Linux 自带的C 编译器,令其在服务器端编译好程
序。
2。 Java 程序
这个程序先启动上述的 C 程序,再建立必要的连接,以便同它“交谈”。随后,它创建一个数据报套接字,
用它“监视”或者“侦听”来自程序片的数据报包。
//: NameCollector。java
// Extracts email names from datagrams and stores
// them inside a file; using Java 1。02。
import java。*;
import java。io。*;
import java。util。*;
public class NameCollector {
final static int COLLECTOR_PORT = 8080;
final static int BUFFER_SIZE = 1000;
byte'' buf = new byte'BUFFER_SIZE';
DatagramPacket dp =
new DatagramPacket(buf; buf。length);
// Can listen & send on the same socket:
DatagramSocket socket;
Process listmgr;
PrintStream nameList;
DataInputStream addResult;
public NameCollector() {
try {
listmgr =
Runtime。getRuntime()。exec(〃listmgr。exe〃);
nameList = new PrintStream(
554
…………………………………………………………Page 556……………………………………………………………
new BufferedOutputStream(
listmgr。getOutputStream()));
addResult = new DataInputStream(
new BufferedInputStream(
listmgr。getInputStream()));
} catch(IOException e) {
System。err。println(
〃Cannot start listmgr。exe〃);
System。exit(1);
}
try {
socket =
new DatagramSocket(COLLECTOR_PORT);
System。out。println(