ServerSocket 类
Socket 类描述的是“客户端” socket,当你需要创建与远程服务程序连接时需要用到它。如果你想实现一个服务程序,如 HTTP 服务器或者 FTP 服务器,则需要另外不同的方法。这是因为你的服务器必须随时服务,它不知道什么时候会有一个客户端程序需要连接它。
因为这个目的,你需要用到 java.net.ServerSocket 这个类,它是服务器端 socket 的一个实现。服务器端 socket 等待来自客户端的连接请求。一旦它收到一个连接请求,它创建一个 socket 实例来与客户端进行通信。
要创建服务器端 socket ,需要用到 ServerSocket 类提供的四个构建方法中的一个。你需要指定服务器端 socket 侦听的 IP 地址与端口号。比较典型地,这个 IP 地址可以是 127.0.0.1 ,意思是该服务器端 socket 侦听的是本地机器。服务器端 socket 侦听的 IP 地址指的是绑定地址。服务器端 socket 另一个重要的属性是队列长度,即它拒绝请求前所接受的最大请求排队长度。
ServerSocket 类的构建方法之一如下:
public ServerSocket(int port,int backLog,InetAddress bindingAddress);
对于这个构建方法,绑定地址必须是 java.net.InetAddress 类的实例。创建一个 InetAddress 类的对象的简单方法是呼叫其静态方法 getByName ,传递一个包含主机名的字符串。
InetAddress.getByName("127.0.0.1");
以下行的代码创建了一个服务器端 socket ,它侦听本地机器的 8080 端口,限制队列长度为 1 。
new ServerSocket(8080,1,InetAddress.getByName("127.0.0.1"));
一旦有了一个 ServerSocket 实例,就可以通过呼叫其 accept 方法来让它等待进来的链接请求。这个方法只有当接收到请求时才返回,它返回的是 Socket 类的实例。这个 Socket 对象就可以用来从客户端应用程序发送与接收字节流,正如上节据说的那样。实际上,accept 方法是本文例子中用到的唯一方法。
应用实例
我们的 web 服务器程序是 ex01.pyrmont 包的一部分,它包含三个类:HttpServer;Request;Response。
整个程序的入口(静态 main 方法)是 HttpServer 类。它创建一个 HttpServer 的实例,并呼叫其 await 方法。正如名字表达的,await 在一个特定的端口等待 HTTP 请求,处理它们,并返回响应给客户端。它保持等待状态,直到收到停止命令。(用方法名 await 代替 wait ,是因为 System 中有一个重要的与线程相关的方法)
这个程序只从一个特定的目录发送静态资源,如 HTML 与图像文件。它只支持没有文件头(如日期与 cookie)的情况。现在我们将在如下的几节中看一下这三个类。
HttpServer 类
HttpServer 实现了一个 web 服务器,它可以提供(serve)特定目录及其子目录下的静态资源。这个特定的目录由 public static final WEB_ROOT 指定。
WEB_ROOT 初始化如下:
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
代码列表中包含了一具叫做 webroot 的目录,里面有一些静态的资源,你可以用来测试本应用。你也可以看到一个 servlet ,在我的下一篇文章将会被用到:“Servlets 容器是怎样工作的”。
为了请求一个静态的资源,在浏览器的地址栏输入如是地址:http://machinename:port/staticResources
如果你从不同的机器上发送请求到运行本应用的机器,则 machinename 是运行应用机器的机器名或 IP 地址,port 是 8080 ,staticResources 是被请求的文件名称,它必须包含在 WEB_ROOT 目录内。
例如,如果你用同一台电脑来测试这个应用,你想要 HttpServer 发送 index.html 这个文件,用以下的地址:http://localhost:8080/index.html
要停止服务,只需要从浏览器发送一个停止(shutdown)命令,即在浏览器的地址栏输入 host:port 字段后,加上预先定义好的字符串。在我们的 HttpServer 类中,停止命令被定义为 SHUTDOWN ,一个 static final 变量。
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
因此,要停止服务,你可以这样:http://localhost:8080/SHUTDOWN
现在,让我们看一下列表 1.1 中给出的 await 方法。代码列表后面将对这段代码做一些解释。
Listing 1.1. The HttpServer class' await method
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
await 方法以创建一个 ServerSocket 实例开始,然后进入一个 while 的循环。
serverSocket = new ServerSocket(
port, 1, InetAddress.getByName("127.0.0.1"));
...
// Loop waiting for a request
while (!shutdown) {
...
}
在 while 循环中的代码,运行到 ServerSocket 的 accept 方法即停止。这个方法只有在 8080 端口接收到 HTTP 请求才返回:
socket = serverSocket.accept();
收到请求后,await 方法从 accept 方法返回的 Socket 实例中等到 java.io.InputStream 与 java.io.OutputStream:
input = socket.getInputStream();
output = socket.getOutputStream();
然后 await 方法创建一个 Request 对象,呼叫它的 parse 方法来解析这个原始的 HTTP 请求:
// create Request object and parse
Request request = new Request(input);
request.parse();
下一步,await 方法创建一个 Response 对象并把 Request 对象设置给它,呼叫它的 sendStaticResource 方法:
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
最后,await 方法关闭 Socket ,呼叫 Request 的 getUri 方法来检查 HTTP 请求的地址是否是一个停止命令。如果是,则 shutdown 变量被设置为 true ,程序退出 while 循环:
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);