zoukankan      html  css  js  c++  java
  • 基于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
    从输入流中读取数据的下一个字节。返回 0255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。

    子类必须提供此方法的一个实现。

    返回:
    下一个数据字节;如果到达流的末尾,则返回 -1
    抛出:
    IOException - 如果发生 I/O 错误。

    其实浏览器此时正在等待你的响应,所以我们需要自己界定请求头的范围。请求头的结束标志是两个连续的换行(这个换行是有标准规定的,必须为 而不是只使用 ),即 。于是我们在收到这个字符串后就可以不再读取数据,而开始写入数据了。

    其他就是该服务程序是一次性的,访问之后就不能再访问了,起码应该写成可以多次访问,进一步可以修改成可以同时多次访问,即多线程访问的。

    还有问题就是返回信息没有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("
    
    ");
            if (sc.hasNext())
            {
                String header = sc.next();
                System.out.println(header);
            }
            // HHTP响应头部信息
            pw.print("HTTP/1.0 200 OK
    ");
            pw.print("Content-type:text/html; charset=utf-8
    ");
            pw.print("
    ");
            // 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("
    
    ");
                if (sc.hasNext())
                {
                    String header = sc.next();
                    System.out.println(header);
                }
                // HHTP响应头部信息
                pw.print("HTTP/1.0 200 OK
    ");
                pw.print("Content-type:text/html; charset=utf-8
    ");
                pw.print("
    ");
                // 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("
    
    ");
                if (sc.hasNext())
                {
                    String header = sc.next();
                    System.out.println(header);
                }
                // HTTP响应头部信息
                pw.print("HTTP/1.0 200 OK
    ");
                pw.print("Content-type:text/html; charset=utf-8
    ");
                pw.print("
    ");
                // 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
    ");
                // pw.print("Content-type:text/html; charset=utf-8
    ");
                // pw.print("
    ");
                // // 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();
        }
    }
    

      

  • 相关阅读:
    [译]理解Javascript的异步等待
    [译]为什么我要离开gulp和grunt转投npm脚本的怀抱
    [译]代码审查的重要性
    [译]转译器: 今日大不同
    猴年马月都到了
    关于“我是谁”的思考
    ASP.net MVC基础
    利用Spring.Net技术打造可切换的分布式缓存读写类
    JQuery WEB前段开发
    Javascript——说说js的调试
  • 原文地址:https://www.cnblogs.com/wuxinliulei/p/4853378.html
Copyright © 2011-2022 走看看