Chapter1 一个简单的Web服务器

本文由码农网 –
小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

Socket 编程
套接字使用TCP提供了两台计算机之间的通信机制。
客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对
Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket
类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
服务器调用 ServerSocket 类的 accept()
方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
服务器正在等待时,一个客户端实例化一个 Socket
对象,指定服务器名称和端口号来请求连接。
Socket
类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个
Socket 对象能够与服务器进行通信。
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket
连接到客户端的 socket。
连接建立后,通过使用 I/O
流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出北京股票配资流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP
是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现
socket。
ServerSocket 类的方法
服务器应用程序通过使用 java.net.ServerSocket
类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
序号 方法描述
1 public ServerSocket(int port) throws IOException
创建绑定到特定端口的服务器套接字。
2 public ServerSocket(int port, int backlog) throws IOException
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket(int port, int backlog, InetAddress address) throws
IOException
使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket() throws IOException
创建非绑定服务器套接字。
创建非绑定服务器套接字。 如果 ServerSocket
构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
序号 方法描述
1 public int getLocalPort()
返回此套接字在其上侦听的端口。
2 public Socket accept() throws IOException
侦听并接受到此套接字的连接。
3 public void setSoTimeout(int timeout)
通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind(SocketAddress host, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
Socket 类的方法
java.net.Socket
类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket
对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept()
方法的返回值。
Socket 类有五个构造方法.
序号 方法描述
1 public Socket(String host, int port) throws UnknownHostException,
IOException.
创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket(InetAddress host, int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket(String host, int port, InetAddress localAddress, int
localPort) throws IOException.
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket(InetAddress host, int port, InetAddress localAddress,
int localPort) throws IOException.
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket()
通过系统默认类型的 SocketImpl 创建未连接套接字
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket
对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket
对象,所以无论客户端还是服务端都能够调用这些方法。
序号 方法描述
澳门新浦京电子游戏 ,1 public void connect(SocketAddress host, int timeout) throws
IOException
将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress()
返回套接字连接的地址。
3 public int getPort()
返回此套接字连接到的远程端口。
4 public int getLocalPort()
返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress()
返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream() throws IOException
返回此套接字的输入流。
7 public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
8 public void close() throws IOException
关闭此套接字。
InetAddress 类的方法
这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:
序号 方法描述
1 static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress(String host, byte[] addr)
根据提供融金汇银的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
5 String getHostName()
获取此 IP 地址的主机名。
6 static InetAddress getLocalHost()
返回本地主机。
7 String toString()
将此 IP 地址转换为 String。
Socket 客户端实例
如下的 GreetingClient 是一个客户端程序,该程序通过 socket
连接到服务器并发送一个请求,然后等待一个响应。
GreetingClient.java 文件代码:
// 文件名 GreetingClient.java

Chapter1 一个简单的Web服务器

介绍

Socket用于进程间通信。进程间通信通常基于客户端—服务端模型。此时,客户端—服务端是可以彼此交互的应用程序。客户端和服务端之间的交互需要连接。Socket编程负责的就是为应用程序之间建立可进行交互的连接。

在本文中,我们将学习如何用PHP创建一个简单的客户端—服务端。我们还将学习如何客户端应用程序如何发送消息到服务端,以及如何从服务端接受消息。

澳门新浦京电子游戏 1

import java.net.;
import java.io.
;

1.1 HTTP

HTTP
RFC 2616 – Hypertext Transfer Protocol —
HTTP/1.1
版本: HTTP/1.1
TCP 连接
基于:“请求—响应”的协议

使用代码

目的:开发一个客户端用于发送string消息到服务端,服务端将相同的信息反转后返回给客户端。

PHP服务器

第1步:设置变量,如“主机”和“端口”

$host = "127.0.0.1";
$port = 5353;
// No Timeout 
set_time_limit(0);

端口号可以是1024 -65535之间的任何正整数。

第2步:创建socket

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");

第3步:绑定socket到端口和主机

创建的socket资源绑定到IP地址和端口号。

$result = socket_bind($socket, $host, $port) or die("Could not bind to socketn");

第4步:启动socket监听

在绑定到IP和端口后,服务端开始等待客户端的连接。在没有连接之前它就一直等下去。

$result = socket_listen($socket, 3) or die("Could not set up socket listenern");

第5步:接受连接

这个函数会接受所建的socket传入的连接请求。在接受来自客户端socket的连接后,该函数返回另一个socket资源,实际上就是负责与相应的客户端socket通信。这里的“$spawn”就是负责与客户端socket通信的socket资源。

$spawn = socket_accept($socket) or die("Could not accept incoming connectionn");

到现在为止,我们已经准备好了服务端socket ,但实际上这个脚本并没有做任何事情。所以为了继续完成上述目标,我们将读取客户端socket消息,然后将接收到的消息反转后发回给客户端socket。

第6步:从客户端socket读取消息

$input = socket_read($spawn, 1024) or die("Could not read inputn");

第7步:反转消息

$output = strrev($input) . "n";

第8步:发送消息给客户端socket

socket_write($spawn, $output, strlen ($output)) or die("Could not write outputn");

关闭socket

socket_close($spawn);
socket_close($socket);

这就完成了服务端。现在,我们学习如何创建PHP客户端。

PHP客户端

前两个步骤与服务端相同。

第1步:设置变量,如“主机”和“端口”

$host = "127.0.0.1";
$port = 5353;
// No Timeout 
set_time_limit(0);

注:这里的端口和主机应该和服务端中的定义是相同的。

第2步:创建socket

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");

第3步:连接到服务端

$result = socket_connect($socket, $host, $port) or die("Could not connect toservern");

此时和服务端不同,客户端socket不绑定端口和主机。相反,它连接到服务端socket,等待接受来自客户端socket的连接。这一步建立了客户端socket到服务端socket的连接。

第4步:写入服务端socket

socket_write($socket, $message, strlen($message)) or die("Could not send data to servern");

在此步骤中,客户端socket的数据被发送到服务端socket。

第5步:阅读来自服务端的响应

$result = socket_read ($socket, 1024) or die("Could not read server responsen");
echo "Reply From Server  :".$result;

第6步:关闭socket

socket_close($socket);

完整的代码

服务端(server.php)

// set some variables
$host = "127.0.0.1";
$port = 25003;
// don't timeout!
set_time_limit(0);
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");
// bind socket to port
$result = socket_bind($socket, $host, $port) or die("Could not bind to socketn");
// start listening for connections
$result = socket_listen($socket, 3) or die("Could not set up socket listenern");

// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket) or die("Could not accept incoming connectionn");
// read client input
$input = socket_read($spawn, 1024) or die("Could not read inputn");
// clean up input string
$input = trim($input);
echo "Client Message : ".$input;
// reverse client input and send back
$output = strrev($input) . "n";
socket_write($spawn, $output, strlen ($output)) or die("Could not write outputn");
// close sockets
socket_close($spawn);
socket_close($socket);

客户端(client.php)

$host    = "127.0.0.1";
$port    = 25003;
$message = "Hello Server";
echo "Message To server :".$message;
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");
// connect to server
$result = socket_connect($socket, $host, $port) or die("Could not connect to servern");  
// send string to server
socket_write($socket, $message, strlen($message)) or die("Could not send data to servern");
// get server response
$result = socket_read ($socket, 1024) or die("Could not read server responsen");
echo "Reply From Server  :".$result;
// close socket
socket_close($socket);

建立上述文件(server.php和client.php)后,执行如下操作:

  1. 复制www目录中的这些文件(假设WAMP),安置于C:wamp。
  2. 打开Web浏览器,在地址栏中键入localhost 。
  3. 先浏览server.php然后client.php。

public class GreetingClient
{
public static void main(String [] args)
{
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try
{
System.out.println(“连接到主机:” + serverName + ” ,端口号:” +
port);
Socket client = new Socket(serverName, port);
System.out.println(“远程主机地址:” +
client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);

1.1.1 HTTP 请求

一个 HTTP 请求包含以下三部分
* 请求方法 —— URI(Uniform Resource Identifier, 统一资源标识符)
* 请求头
* 实体

POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: Localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate

lastName=Franks&firstName=Michael

澳门新浦京电子游戏 2

http .png

HTTP 支持的七种请求方法

  1. GET
  2. POST
  3. HEAD
  4. OPTIONS
  5. PUT
  6. DELET
  7. TRACE

URI(Uniform Resource Identifier, 统一资源标识符) 制定 Internet
资源的完整路径。 URI
通常会被解释为相对于服务器根目录的相对路径,因此,它总是以 “/”
开头的。
URL(Uniform Resource Locator, 统一资源定位符) 实际上是 URI 的一种类型。

版本协议指明了当前请求使用的 HTTP 协议的版本。

许可证

这篇文章,以及任何相关的源代码和文件,是经过The Code Project Open
License (CPOL)许可的。

     out.writeUTF("Hello from " + client.getLocalSocketAddress());
     InputStream inFromServer = client.getInputStream();
     DataInputStream in = new DataInputStream(inFromServer);
     System.bjrongjinhuiyin.com.out.println("服务器响应: " + in.readUTF());
     client.close();
  }catch(IOException e)
  {
     e.printStackTrace();
  }

1.1.2 HTTP 响应

一个 HTTP 响应包括三部分

  • 协议——状态码——描述
  • 响应头
  • 相应实体段

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Data: Mon, 5 Jan 2004 13:13:33 GMT
Content-Length: 112

<html>
    <head>
        <title>HTTP Response Example</title>
    </head>
    <body>
        Welcome to Brainy Software
    </body>
</html>

澳门新浦京电子游戏 3

http-respond.jpg

状态码 200 表示请求成功。
响应实体正文是一段 HTML 代码

}
}
Socket 服务端实例
如下的GreetingServer 程序是一个服务器端应用程序,使用 Socket
来监听一个指定的端口。
GreetingServer.java 文件代码:
// 文件名 GreetingServer.java

1.2 Socket 类

Socket (套接字) 是网络连接的端点。Socket
使应用程序可以从网络中读取数据,可以向网络中写入数据。不同计算机上的两个应用程序可以通过连接发送或接收字节流,以此达到相互通信的目的。为了从一个应用程序向另一个应用程序发送消息,需要知道另一个应用程序中
Socket 的 IP 地址和端口号。

public class Socket
extends Object
implements Closeable
This class implements client sockets (also called just “sockets”). A
socket is an endpoint for communication between two machines.
The actual work of the socket is performed by an instance of the
SocketImpl class. An application, by changing the socket factory
that creates the socket implementation, can configure itself to create
sockets appropriate to the local firewall.

创建一个 Socket :

Socket() Creates an unconnected socket, with the system-default type of SocketImpl.
Socket(String host, int port) Creates a stream socket and connects it to the specified port number on the named host.
Socket(String host, int port, boolean stream) Deprecated. Use DatagramSocket instead for UDP transport.

host – the host name, or null for the loopback address.

port – the port number.

host:远程主机的名称或 IP 地址 (127.0.0.1 表示一个本地主机)
post:连接远程应用程序的端口号

使用 socket 实例发送/接收字节流:
socket.getOutputStream()

发送文本
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)

接收字节流:
socket.getInputStream()

import java.net.;
import java.io.
;

Example: 创建一个Socket,用于与本地 HTTP 服务器进行通信


Socket socket = new Socket(host, portNumber);
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(socket.getOutputStream(), autoflush);
String message = command.getText();
out.println(message);
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
BufferedReader in = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
    while(loop){
    if (in.ready()) {
        int i = 0;
        while (i != -1) {
            i = in.read();
            sb.append((char) i);
        }
        loop = false;
    }
    Thread.currentThread().sleep(50);
}
    response.setText(sb.toString());
    socket.close();

public class GreetingServer extends Thread
{
private ServerSocket serverSocket;

ServerSocket 类

Socket 类:表示一个客户端套接字。
ServerSocket 类:服务器套接字。

public class ServerSocket
extends Object
implements Closeable
This class implements server sockets. A server socket waits for
requests to come in over the network. It performs some operation based
on that request, and then possibly returns a result to the
requester.
The actual work of the server socket is performed by an instance of
the SocketImpl class. An application can change the socket factory
that creates the socket implementation to configure itself to create
sockets appropriate to the local firewall.

服务器套接字要等待来自客户端的连接请求。当服务器套接字接收到了连接请求之后,它会创建一个
Socket 实例来处理与客户端的通信。

ServerSocket 的构造函数

Constructor Description
ServerSocket() Creates an unbound server socket.
ServerSocket(int port) Creates a server socket, bound to the specified port.
ServerSocket(int port, int backlog) Creates a server socket and binds it to the specified local port number, with the specified backlog.
ServerSocket(int port, int backlog,InetAddress bindAddr) Create a server with the specified port, listen backlog, and local IP address to bind to.

port – the port number, or 0 to use a port number that is
automatically allocated.

backlog – requested maximum length of the queue of incoming
connections.

bindAddr – the local InetAddress the server will bind to

backlog:服务端socket处理客户端socket连接是需要一定时间的。ServerSocket有一个队列,存放还没有来得及处理的客户端Socket,这个队列的容量就是backlog的含义。如果队列已经被客户端socket占满了,如果还有新的连接过来,那么ServerSocket会拒绝新的连接。也就是说backlog提供了容量限制功能,避免太多的客户端socket占用太多服务器资源。

bindAddr:必须是 java.net.InetAddress. 类的实例

创建 InetAddress 实例对象的一种简单方法是调用其静态方法
getByName(),传入包含主机名的字符串:

InetAddress.getByName("127.0.0.1");

创建了 ServerSocket
实例后,可以使其等待传入的连接请求,该请求会通过服务器套接字侦听的端口上的绑定地址传入,这些工作可以通过调用
ServerSocket 类的 accept
方法完成。只有收到连接请求后,该方法才会返回。

public Socket accept() throws IOException

A new Socket s is created and, if there is a security manager, the
security manager’s checkAccept method is called with
s.getInetAddress().getHostAddress() and s.getPort() as its
arguments to ensure the operation is allowed. This could result in a
SecurityException.

Returns: the new Socket.

public GreetingServer(int port) throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}

1.3 应用程序

本章的 Web 服务器包括三个类:

  • HttpServer
  • Request
  • Response
  1. 程序的入口点(静态 main()方法)在 HttpServer
    类中。main()方法创建一个 HttpServer 实例,然后调用其 await()
    方法。
  2. await()方法会在指定端口上等待 HTTP
    请求,对其处理,然后发送响应信息回客户端。在接收到关闭命令前,它会保持等待状态。

该应用程序仅发送位于指定目录的静态资源请求,如 HTML
文件和图像文件,它将传入到的 HTTP
请求字节流现实到控制台上。但是它不发送任何 header (头信息)
到浏览器,如日期或 cookies 等。

public void run()
{
while(true)
{
try
{
System.out.println(“等待远程连接,端口号为:” +
serverSocket.getLocalPort() + “…”);
Socket server = serverSocket.accept();
System.out.println(“远程主机地址:” +
server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF(“谢谢连接我:” + server.getLocalSocketAddress() +
“nGoodbye!”);
server.close();
}catch(SocketTimeoutException s)
{
System.out.println(“Socket timed out!”);
break;
}catch(IOException e)
{
e.printStackTrace();
break;
}
}
}
public static void main(String [] args)
{
int port = Integer.parseInt(args[0]);
try
{
Thread t = new GreetingServer(port);
t.run();
}catch(IOException e)
{
e.printStackTrace();
}
}
}

1.3.1 HttpServer

package ch1;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;


// 处理对指定目录中静态资源的请求
public class HttpServer {

    // 指明目录
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

    //目录名为 'webroot'


    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    // 关闭命令——/SHUTDOWN

    // the shutdown command received
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.await();
    }

    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 {
                // accept()
                // Listens for a connection to be made to this socket
                // and accepts it. The method blocks until a connection is made.
                socket = serverSocket.accept();

                // getInputStream()
                // an input stream for reading bytes from this socket.
                input = socket.getInputStream();

                // getOutputStream()
                // an output stream for writing bytes to this socket.
                output = socket.getOutputStream();

                // creat Request object and parse
                Request request = new Request(input);
                request.parse();

                // creat Response object
                Response response = new Response(output);
                response.setRequest(request);
                response.sendStaticResource();

                // close the socket
                socket.close();

                // check if the prevous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
                // 判断一个输入元素与是否与已知元素相同

            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }
}

这个 Web
服务器可以处理对指定目录中的静态资源的请求。该目录包括公有静态变量
final WEB_ROOT 指明的目录及其所有子目录。WEB_ROOT 的初始值为:

public static final String WEB_ROOT = 
    System.getProperty("user.dir") + File.separator + "webroot";

若要请求静态资源,可以在浏览器的地址栏或 URL 框中输入如下的 URL

http://machineName:port/staticResource

http://localhost:8080/source

若从另一台计算机上向该应用程序发出请求,则 machineName
是应用程序所在计算机的名称或 IP
地址;若在同一台机器上发出的请求,则可以将 machineName 替换为
localhost

此连接请求使用的端口为 8080。

staticResourse 是请求的文件的名字,该文件必须位于 WEB_ROOT
指向的目录下。

若要关闭服务器,可以通过 Web 浏览器的地址栏或 URI 框,在 URI 的
host:port 部分后面输入预先定义好的字符串,从 WEB
浏览器发送一条关闭命令,这样服务器就会收到关闭命令了。

例中的关闭命令定义在 HttpServer 类的 SHUTDOWN 静态 final 变量中:

private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

若要关闭服务器,输入

http://localhost:8080/SHUTDOWN

await() 方法而不是 wait() 方法,是因为 wait() 方法是
java.lang.Object 类中与使用线程相关的重要方法。

  1. await() 方法先创建一个 ServerSocket 实例,然后进入一个 while
    循环,从 8080 端口接收到 Http 请求后, await() 方法会从
    accept() 方法返回的 Socket 实例中,获取 java.io.InputStream
    java.io.OutputStream 对象:

    input = socket.getInputStream();
    output = socket.getOutputStream();
    
  2. 之后,await() 方法创建一个 Request 对象,并调用其 paser()
    方法来解析 HTTP 请求的原始数据:

//create Request object and parse
Request request = new Request(input);
request.parse(); 
  1. 然后,await() 方法会创建一个 Response 对象,并分别调用其
    setRequest() 方法和 sendStaticResource() 方法:

    // creat Response object
    Response response = new Response(output);
    response.setRequest(request);
    response.sendStaticResource();
    
  2. 最后,await() 方法关闭套接字,调用 Request 类的 getUri()
    方法来测试 HTTP 请求的 URI 是否是关闭命令,若是,则将变量
    shutdown 设置为 true,程序退出 while 循环。

    // close the socket
    socket.close();
    
    // check if the prevous URI is a shutdown command
    shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
    

1.3.2 Request

Request 类表示一个 HTTP 请求,可以传递 InputStream
对象(从通过处理与客户端通信的 Socket 对象中获取的),来创建 Request
对象。可以调用 InputStream 对象中的 read() 方法来读取 HTTP
请求的原始数据。

package ch1;

import java.io.IOException;
import java.io.InputStream;

public class Request {
    private InputStream input;
    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    // 解析 HTTP 请求的原始数据
    public void parse() {
        // read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);

        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }

        for (int j = 0; j < i; j++) {
            request.append((char) buffer[j]);
        }

        System.out.print(request.toString());
        uri = parseUri(request.toString());
    }

    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri() {
        return uri;
    }

}

Request

parse():解析 HTTP 请求中的原始数据。通过调用 private 方法
parseUri() 来解析 HTTP 请求的 URI

parseUri(String requestString):将 URI 存储在变量 uri 中。

getUri():返回 HTTP 请求的 URI

  1. parse() 方法从传入到 Request 对象中的套接字的 InputStream
    对象中读取整个字节流,并将字节数据存储在缓冲区中。然后使用缓冲区字节数组中的数组填充
    StringBuffer 对象 request ,并将 StringBufferString表示
    传递给 parseUri() 方法。

    public void parse() {
            // read a set of characters from the socket
            StringBuffer request = new StringBuffer(2048);
            int i;
            byte[] buffer = new byte[2048];
            try {
                i = input.read(buffer);
    
            } catch (IOException e) {
                e.printStackTrace();
                i = -1;
            }
    
            for (int j = 0; j < i; j++) {
                request.append((char) buffer[j]);
            }
    
            System.out.print(request.toString());
            uri = parseUri(request.toString());
        }
    
  2. parseURI() 方法从请求行中获取 URI

    private String parseUri(String requestString) {
            int index1, index2;
            index1 = requestString.indexOf(' ');
            if (index1 != -1) {
                index2 = requestString.indexOf(' ', index1 + 1);
                if (index2 > index1)
                    return requestString.substring(index1 + 1, index2);
            }
            return null;
        }
    

1.3.3 Response

Response 类表示 HTTP 响应。

package ch1;

import java.io.*;

public class Response {
    private static final int BUFFER_SIZE = 2048;
    Request request;
    OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws IOException {

        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;

        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            if (file.exists()){
                fis = new FileInputStream(file);
                int ch = fis.read(bytes, 0, BUFFER_SIZE);

                String msg = "HTTP/1.1 404 File Not Foundrn" +
                        "Content-Type: text/htmlrn" +
                        "rn";
                output.write(msg.getBytes());

                while (ch != -1){
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            }
            else {
                // find not found
                String errorMessage = "HTTP/1.1 404 File Not Foundrn" +
                        "Content-Type: text/htmlrn" +
                        "Content-Length: 23rn" +
                        "rn" +
                        "<h1>File Not Found</h1>";
                output.write(errorMessage.getBytes());
            }

        }catch (Exception e){
            // thrown if cannot instantiate a File object
            System.out.println(e.toString());
        }
        finally {
            if (fis != null ){
                fis.close();
            }
        }

    }
}
  1. Response 类的构造函数接受一个 java.io.OutputStream 对象,:

        public Response(OutputStream output) {
            this.output = output;
        }
    
  2. Response 对象在 HttpServer 类的 await()
    方法中,通过传入从套接字中获取的 OutputStream 来创建。

  3. Response 内有两个 public 方法:setRequest()
    sendStaticResourse()setRequest() 方法会接受一个 Request
    对象作为参数。

  4. sendStaticResource() 方法用于发丝路过一个静态资源到浏览器,如
    HTML 文件。

    1. 它首先会通过传入父路径和子路径到 File 类的构造函数中来实例化
      java.io.File 类:

      File file = new File(HttpServer.WEB_ROOT, request.getUri());
      
    2. 然后,它检查该文件是否存在。

    3. 若存在,sedStaticResource() 方法会使用 File 对象创建
      java.io.InputStream 。然后它调用 FileInputStream 类的
      read() 方法,并将字节数据写入到 OutputStream
      输出中。(这种情况下,静态资源的内容作为原始数据发送到浏览器的):

      if (file.exists()){
          fis = new FileInputStream(file);
          int ch = fis.read(bytes, 0, BUFFER_SIZE);
      
          // mac 防止出错
          String msg = "HTTP/1.1 404 File Not Foundrn" +
                  "Content-Type: text/htmlrn" +
                  "rn";
          output.write(msg.getBytes());
      
          while (ch != -1){
              output.write(bytes, 0, ch);
              ch = fis.read(bytes, 0, BUFFER_SIZE);
          }
      }
      
    4. 若不存在,staticResource() 会发送错误信息到浏览器:

       else {
          // find not found
          String errorMessage = "HTTP/1.1 404 File Not Foundrn" +
                  "Content-Type: text/htmlrn" +
                  "Content-Length: 23rn" +
                  "rn" +
                  "<h1>File Not Found</h1>";
          output.write(errorMessage.getBytes());
      }
      

1.3.5 运行应用程序

  1. 执行 ch1 包

  2. 在地址栏或 URL 框输入:

    http://localhost:8080/source.html

Result

GET /source.html HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Cookie: __utma=111872281.722673633.1521287843.1521287843.1521287843.1; __utmz=111872281.1521287843.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Idea-7e6930cd=8b83b26b-bd8b-480c-832b-3c0b6e5d4c39
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: keep-alive

GET /favicon.ico HTTP/1.1
Host: localhost:8080
Accept: */*
Connection: keep-alive
Cookie: __utma=111872281.722673633.1521287843.1521287843.1521287843.1; __utmz=111872281.1521287843.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Idea-7e6930cd=8b83b26b-bd8b-480c-832b-3c0b6e5d4c39
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Accept-Language: zh-cn
Referer: http://localhost:8080/source.html
Accept-Encoding: gzip, deflate

澳门新浦京电子游戏 4

ch1_result.png

发表评论

电子邮件地址不会被公开。 必填项已用*标注