15.5 一个Web 应用
15.5 一个Web 应用
现在让我们想想如何创建一个应用,令其在真实的
若采用传统方式来解决这个问题,我们要创建一个包含了文本字段及一个“提交”(Submit)按钮的
作为
正如大家不久就会体验到的那样,尽管看起来非常简单,但实际上有一些意想不到的问题使局面显得稍微有些复杂。用
现在讨论一下服务器应用(程序)的问题,我把它叫作
如果我们换用数据报,就不必使用多线程了。用单个数据报即可“侦听”进入的所有数据报。一旦监视到有进入的消息,程序就会进行适当的处理,并将答复数据作为一个数据报传回原先发出请求的那名接收者。若数据报半路上丢失了,则用户会注意到没有答复数据传回,所以可以重新提交请求。
服务器应用收到一个数据报,并对它进行解读的时候,必须提取出其中的电子函件地址,并检查本机保存的数据文件,看看里面是否已经包含了那个地址(如果没有,则添加之
C 程序
这个非
//: Listmgr.c
// Used by NameCollector.java to manage
// the email list file on the server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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, "%s\n", buf);
fflush(list);
printf("%s added to list", buf);
fflush(stdout);
}
}
} ///:~
该程序假设
文件中的第一个函数检查我们作为第二个参数(指向一个
1,指出该名字已经在文件里了(注意这个函数找到相符内容后会立即返回,不会把时间浪费在检查列表剩余内容的上面
在
如果文件成功打开,程序就会进入一个无限循环。调用
如果名字不在列表中,就用
记住一般不能先在自己的计算机上编译此程序,再把编译好的内容上载到
Java 程序
这个程序先启动上述的
//: NameCollector.java
// Extracts email names from datagrams and stores
// them inside a file, using Java 1.02.
import java.net.*;
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(
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(
"NameCollector Server started");
while(true) {
// Block until a datagram appears:
socket.receive(dp);
String rcvd = new String(dp.getData(),
0, 0, dp.getLength());
// Send to listmgr.exe standard input:
nameList.println(rcvd.trim());
nameList.flush();
byte[] resultBuf = new byte[BUFFER_SIZE];
int byteCount =
addResult.read(resultBuf);
if(byteCount != -1) {
String result =
new String(resultBuf, 0).trim();
// Extract the address and port from
// the received datagram to find out
// where to send the reply:
InetAddress senderAddress =
dp.getAddress();
int senderPort = dp.getPort();
byte[] echoBuf = new byte[BUFFER_SIZE];
result.getBytes(
0, byteCount, echoBuf, 0);
DatagramPacket echo =
new DatagramPacket(
echoBuf, echoBuf.length,
senderAddress, senderPort);
socket.send(echo);
}
else
System.out.println(
"Unexpected lack of result from " +
"listmgr.exe");
}
} catch(SocketException e) {
System.err.println("Can't open socket");
System.exit(1);
} catch(IOException e) {
System.err.println("Communication error");
e.printStackTrace();
}
}
public static void main(String[] args) {
new NameCollector();
}
} ///:~
这个程序的所有工作都是在构造器内进行的。为启动
和往常一样,我们将
nameList.println(rcvd.trim());
之所以能这样编码,是因为
从
记住尽管
正如早先指出的那样,程序片必须用
//: NameSender.java
// An applet that sends an email address
// as a datagram, using Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
public class NameSender extends Applet
implements Runnable {
private Thread pl = null;
private Button send = new Button(
"Add email address to mailing list");
private TextField t = new TextField(
"type your email address here", 40);
private String str = new String();
private Label
l = new Label(), l2 = new Label();
private DatagramSocket s;
private InetAddress hostAddress;
private byte[] buf =
new byte[NameCollector.BUFFER_SIZE];
private DatagramPacket dp =
new DatagramPacket(buf, buf.length);
private int vcount = 0;
public void init() {
setLayout(new BorderLayout());
Panel p = new Panel();
p.setLayout(new GridLayout(2, 1));
p.add(t);
p.add(send);
add("North", p);
Panel labels = new Panel();
labels.setLayout(new GridLayout(2, 1));
labels.add(l);
labels.add(l2);
add("Center", labels);
try {
// Auto-assign port number:
s = new DatagramSocket();
hostAddress = InetAddress.getByName(
getCodeBase().getHost());
} catch(UnknownHostException e) {
l.setText("Cannot find host");
} catch(SocketException e) {
l.setText("Can't open socket");
}
l.setText("Ready to send your email address");
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(send)) {
if(pl != null) {
// pl.stop(); Deprecated in Java 1.2
Thread remove = pl;
pl = null;
remove.interrupt();
}
l2.setText("");
// Check for errors in email name:
str = t.getText().toLowerCase().trim();
if(str.indexOf(' ') != -1) {
l.setText("Spaces not allowed in name");
return true;
}
if(str.indexOf(',') != -1) {
l.setText("Commas not allowed in name");
return true;
}
if(str.indexOf('@') == -1) {
l.setText("Name must include '@'");
l2.setText("");
return true;
}
if(str.indexOf('@') == 0) {
l.setText("Name must preceed '@'");
l2.setText("");
return true;
}
String end =
str.substring(str.indexOf('@'));
if(end.indexOf('.') == -1) {
l.setText("Portion after '@' must " +
"have an extension, such as '.com'");
l2.setText("");
return true;
}
// Everything's OK, so send the name. Get a
// fresh buffer, so it's zeroed. For some
// reason you must use a fixed size rather
// than calculating the size dynamically:
byte[] sbuf =
new byte[NameCollector.BUFFER_SIZE];
str.getBytes(0, str.length(), sbuf, 0);
DatagramPacket toSend =
new DatagramPacket(
sbuf, 100, hostAddress,
NameCollector.COLLECTOR_PORT);
try {
s.send(toSend);
} catch(Exception e) {
l.setText("Couldn't send datagram");
return true;
}
l.setText("Sent: " + str);
send.setLabel("Re-send");
pl = new Thread(this);
pl.start();
l2.setText(
"Waiting for verification " + ++vcount);
}
else return super.action(evt, arg);
return true;
}
// The thread portion of the applet watches for
// the reply to come back from the server:
public void run() {
try {
s.receive(dp);
} catch(Exception e) {
l2.setText("Couldn't receive datagram");
return;
}
l2.setText(new String(dp.getData(),
0, 0, dp.getLength()));
}
} ///:~
程序片的
到现在为止,大家已能判断出
无论这是否按钮被第一次按下,
下一组语句将检查
名字校验通过以后,它会打包到一个数据报里,然后采用与前面那个数据报示例一样的方式发到主机地址和端口编号。第一个标签会发生变化,指出已成功发送出去。而且按钮上的文字也会改变,变成“重发”(resend
线程的
Web 页
当然,程序片必须放到一个
<HTML>
<HEAD>
<META CONTENT="text/html">
<TITLE>
Add Yourself to Bruce Eckel's Java Mailing List
</TITLE>
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080" BGCOLOR="#ffffff">
<FONT SIZE=6><P>
Add Yourself to Bruce Eckel's Java Mailing List
</P></FONT>
The applet on this page will automatically add your email address to the mailing list, so you will receive update information about changes to the online version of "Thinking in Java," notification when the book is in print, information about upcoming Java seminars, and notification about the “Hands-on Java Seminar” Multimedia CD. Type in your email address and press the button to automatically add yourself to this mailing list. <HR>
<applet code=NameSender width=400 height=100>
</applet>
<HR>
If after several tries, you do not get verification it means that the Java application on the server is having problems. In this case, you can add yourself to the list by sending email to
<A HREF="mailto:Bruce@EckelObjects.com">
Bruce@EckelObjects.com</A>
</BODY>
</HTML>
程序片标记(