基于Java的Http服务器几种模式演进
首先抛出问题:
程序1---错误版本
import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class HttpSimpleServer { public void startServer() throws IOException { ServerSocket ss = new ServerSocket(10021); Socket so = ss.accept(); InputStream in = so.getInputStream(); PrintWriter pw = new PrintWriter(so.getOutputStream(),true); byte[] bytes = new byte[1024]; int num = 0; while((num = in.read(bytes))!=-1) { String str = new String(bytes,0,num); if(str.trim().length() <= 0) { break; } System.out.print(str); } pw.println("<font color='red' size='7'> 今天天气真好</font>"); so.close(); ss.close(); } public static void main(String[] args) throws IOException { new HttpSimpleServer().startServer(); } }
上面的代码是一个基于Java的简单的HTTP服务器,输入访问地址:http://localhost:10021/ 进行访问
服务端输出:
GET /favicon.ico HTTP/1.1
Host: localhost:10021
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
而浏览器一直卡着,看不到输出的信息。
当浏览器访问连接:http://localhost:10021/的时候,服务端将一直阻塞,浏览器收不到响应。
出现无响应的原因在于:
浏览器和你的http服务器建立连接后,先发送请求头信息,然后并不会断开连接,所以read方法就会一直阻塞,等待服务器关闭连接(只有关闭后才会返回-1)。我么知道TCP断开连接使用的是四次分手原则,之所以使用四次分手原则,是因为TCP是个全双工的管道,每一端既可以发送也可以接受,当一端调用socket.close()之后,会发送fin关闭连接的报文(表明自己不再发送数据了,但是可以接收数据),对方收到之后,发送ACK报文,确认自己已经收到所有信息,这样一端关闭了发送,一端关闭了接收,当被动关闭方发送完毕之后,发送fin给主动关闭方,从而全部关闭.
于是Http的TCP连接的主动关闭方一般都是服务端,是在服务端的Response通过TCP发送出去之后再调用socket.close()方法的。read方法会阻塞(流没结束并且没有断开信号),这是主要原因。
public abstract int read() throws IOException
- 从输入流中读取数据的下一个字节。返回
0
到255
范围内的int
字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1
。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。子类必须提供此方法的一个实现。
- 返回:
- 下一个数据字节;如果到达流的末尾,则返回
-1
。 - 抛出:
IOException
- 如果发生 I/O 错误。
其实浏览器此时正在等待你的响应,所以我们需要自己界定请求头的范围。请求头的结束标志是两个连续的换行(这个换行是有标准规定的,必须为\r\n而不是只使用\n),即\r\n\r\n。于是我们在收到这个字符串后就可以不再读取数据,而开始写入数据了。
其他就是该服务程序是一次性的,访问之后就不能再访问了,起码应该写成可以多次访问,进一步可以修改成可以同时多次访问,即多线程访问的。
还有问题就是返回信息没有HTTP响应头部,可能会出现乱码或者浏览器无法识别等问题
程序2--修正版本1:
import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; /** * @author 作者 E-mail: * @version 创建时间:2015-8-27 下午09:02:16 类说明 */ public class HttpSimpleServer2 { public void startServer() throws IOException { // 建立ServerSocket ServerSocket serso = new ServerSocket(10021); // 获取客户端对象 Socket so = serso.accept(); // 获取相关流对象 InputStream in = so.getInputStream(); PrintWriter pw = new PrintWriter(so.getOutputStream(), true); Scanner sc = new Scanner(in); sc.useDelimiter("\r\n\r\n"); if (sc.hasNext()) { String header = sc.next(); System.out.println(header); } // HHTP响应头部信息 pw.print("HTTP/1.0 200 OK\r\n"); pw.print("Content-type:text/html; charset=utf-8\r\n"); pw.print("\r\n"); // HTTP响应内容 pw.println("<font color='red' size='7'>good</font>"); sc.close(); so.close(); serso.close(); } public static void main(String[] args) throws IOException { new HttpSimpleServer2().startServer(); } }
服务端输出:
GET / HTTP/1.1
Host: localhost:10021
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
缺点:①不能多线程访问 ②只能访问一次,程序就运行结束了
程序3---修正版本2:
import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.TimeUnit; /** * 现在这个请求仍然是阻塞的,单线程的,同一时刻只能有一个程序进行访问 * @author 作者 E-mail: * @version 创建时间:2015-8-27 下午09:08:26 类说明 */ public class HttpSimpleServer3 { public void startServer() throws IOException, InterruptedException { // 建立ServerSocket 这里默认的backlog 是50 可以有50个请求在排队等待 ServerSocket serso = new ServerSocket(10021); while (true) { // 获取客户端对象 Socket so = serso.accept(); // 获取相关流对象 InputStream in = so.getInputStream(); PrintWriter pw = new PrintWriter(so.getOutputStream(), true); Scanner sc = new Scanner(in); sc.useDelimiter("\r\n\r\n"); if (sc.hasNext()) { String header = sc.next(); System.out.println(header); } // HHTP响应头部信息 pw.print("HTTP/1.0 200 OK\r\n"); pw.print("Content-type:text/html; charset=utf-8\r\n"); pw.print("\r\n"); // HTTP响应内容 pw.println("<font color='red' size='7'>good</font>"); pw.flush(); Thread.sleep(100000); //单线程程序在多个同时访问的时候就会受限 sc.close(); //在sc关闭之前是写不出去的,因为没有flush so.close(); } } public static void main(String[] args) throws IOException, InterruptedException { new HttpSimpleServer3().startServer(); } }
使用while循环持续监听client连接
缺点:仍旧不能多线程访问
程序4:----修正版本3
import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; /** * @author 作者 E-mail: * @version 创建时间:2015-8-27 下午09:16:50 类说明 */ class Runner implements Runnable { private final Socket socket; public Runner(Socket socket) { this.socket = socket; } @Override public void run() { // 获取相关流对象 try { InputStream in = socket.getInputStream(); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); Scanner sc = new Scanner(in); sc.useDelimiter("\r\n\r\n"); if (sc.hasNext()) { String header = sc.next(); System.out.println(header); } // HTTP响应头部信息 pw.print("HTTP/1.0 200 OK\r\n"); pw.print("Content-type:text/html; charset=utf-8\r\n"); pw.print("\r\n"); // HTTP响应内容 pw.println("<font color='red' size='7'>good</font>"); pw.flush(); try { Thread.sleep(100000); } catch(InterruptedException e) { e.printStackTrace(); } sc.close(); //服务器端的关闭请求。 socket.close(); } catch(IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } //实现了多线程的访问,但是效率明显有点低哈哈哈哈 public class HttpSimpleServer4 { public void startServer() throws IOException { // 建立ServerSocket 这里默认的backlog 是50 可以有50个请求在排队等待 ServerSocket serso = new ServerSocket(10021); while (true) { // 获取客户端对象 Socket so = serso.accept(); Runnable runner = new Runner(so); Thread thread = new Thread(runner); // run就相当于在本线程当中调用,start才是启动的新线程 thread.start(); } } public static void main(String[] args) throws IOException, InterruptedException { new HttpSimpleServer4().startServer(); } }
实现了多线程访问,
缺点:是阻塞式的,每个访问都要启用一个线程,没有数据输入线程就会阻塞,线程重复创建和销毁
程序5---修正版本4
草稿
import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Scanner; import java.util.Set; /** * @author 作者 E-mail: * @version 创建时间:2015-8-27 下午09:37:25 类说明 */ interface Handler { void doHandle(SelectionKey key); } class AcceptHandler implements Handler { @Override public void doHandle(SelectionKey key) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = null; // 在非阻塞模式下,serverSocketChannel.accept()有可能返回null // 判断socketChannel是否为null,可以使程序更加健壮,避免NullPointException try { socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); if (socketChannel == null) return; System.out.println("接收到客户链接,来自:" + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort()); RequestHandler requestHandler = new RequestHandler(socketChannel); socketChannel.register(key.selector(), SelectionKey.OP_READ, requestHandler); } catch(IOException ex) { ex.printStackTrace(); } } } class RequestHandler implements Handler { private SocketChannel socketChannel = null; public RequestHandler(SocketChannel socketChannel) { this.socketChannel = socketChannel; } @Override public void doHandle(SelectionKey key) { try { ByteBuffer buffer = ByteBuffer.allocate(1024); socketChannel.read(buffer); if(buffer.position()!=0) { System.out.println(new String(buffer.array())); } else { // 这里可能出现问题 System.out.println(buffer.toString()); } buffer.flip(); // System.out.println(buffer) // buffer.wrap(array) // // HTTP响应头部信息 // S // pw.print("HTTP/1.0 200 OK\r\n"); // pw.print("Content-type:text/html; charset=utf-8\r\n"); // pw.print("\r\n"); // // HTTP响应内容 // pw.println("<font color='red' size='7'>good</font>"); // pw.flush(); } catch(IOException ex) { ex.printStackTrace(); } } } public class HttpSimpleServer5 { private Selector selector = null; private ServerSocketChannel serverSocketChannel = null; private int port = 10021; public HttpSimpleServer5() throws IOException { // 创建一个Selector对象 selector = Selector.open(); // 创建一个ServerSocketChannel对象 serverSocketChannel = ServerSocketChannel.open(); // 使ServerSocketChannel工作处于非阻塞模式 serverSocketChannel.configureBlocking(false); // 使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时 // 可以顺利的绑定到相同的端口 serverSocketChannel.socket().setReuseAddress(true); // 把服务器进程与一个本地端口绑定 serverSocketChannel.socket().bind(new InetSocketAddress(10021)); } void startServer() throws IOException { serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new AcceptHandler()); while (selector.select() > 0) { // 获得Selector的selector-keys集合 Set<SelectionKey> readKeys = selector.selectedKeys(); Iterator<SelectionKey> it = readKeys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); // 由Handler处理连接就绪事件 final Handler handler = (Handler) key.attachment(); handler.doHandle(key); } } } public static void main(String[] args) throws IOException { new HttpSimpleServer5().startServer(); } }