自己动手实现HTTP协议

Java中的网络通信是通过Socket实现的,我们可以编写自己的SocketServer服务器接收并处理来自客户端Socket的请求。当客户端请求不是很多的时候,ServerSocket完全可以应付,但是当请求增多的时候,这种ServerSocket的模式就不那么合适了。
从JDK1.4开始,Java增加了新的io模式–nio,它在底层采用了新的处理方式,极大地提高了IO的效率。那么我们可以使用NioSocket来完成服务端,以提高处理效率。
那么,接下来我们在NioSocketServer的基础上,自己做一个简单的实现了HTTP协议的例子。


import java.io.IOException;
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.nio.charset.Charset;
import java.util.Iterator;

/**
 * 采用新IO方式的ServerSocket
 */
public class NIOServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel,监听8080端口
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        //设置为非阻塞模式
        ssc.configureBlocking(false);
        //给ssc注册选择器
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        //创建处理器
        while (true) {
            //等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,传入0或不传参数将一直阻塞
            if (selector.select(3000) == 0) {
                System.out.println("等待请求超时……");
                continue;
            }
            System.out.println("开始处理请求……");
            Iterator keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                //启动新线程处理SelectionKey
                new Thread(new HttpHandler(key)).run();
                //处理完成后,从待处理的SelectionKey迭代器中移除当前所使用的Key
                keyIterator.remove();
            }
        }
    }

    /**
     * 内部类HttpHandler,负责处理监听到的请求
     */
    private static class HttpHandler implements Runnable {
        private int bufferSize = 1024;
        private String localCharset = "utf-8";
        private SelectionKey key;

        private HttpHandler(SelectionKey key) {
            this.key = key;
        }

        private HttpHandler(int bufferSize) {
            this(bufferSize, null);
        }

        private HttpHandler(String localCharset) {
            this(-1, localCharset);
        }

        private HttpHandler(int bufferSize, String localCharset) {
            if (bufferSize > 0) {
                this.bufferSize = bufferSize;
            }
            if (localCharset != null) {
                this.localCharset = localCharset;
            }
        }

        public void handleAccept(SelectionKey key) throws IOException {
            SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
            sc.configureBlocking(false);
            sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        public void handleRead(SelectionKey key) throws IOException {
            //获取channel
            SocketChannel sc = (SocketChannel) key.channel();
            //获取ByteBuffer并重置
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
            //没有读取到内容,关闭信道
            if (sc.read(buffer) == -1) {
                sc.close();
            } else {
                //将buffer转换为读状态
                buffer.flip();
                //将buffer中的值按localCharset格式编码后保存到receivedString
                String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                //控制台打印请求报文头
                String[] requestMessage = receivedString.split("\r\n");
                for (String s : requestMessage) {
                    //遇到空行说明报文头已经打印完
                    if (s.isEmpty()) {
                        break;
                    }
                    System.out.println(s);
                }
                //控制台打印首行信息
                String[] firstLine = requestMessage[0].split(" ");
                System.out.println();
                System.out.println("Method:\t" + firstLine[0]);
                System.out.println("URL:\t" + firstLine[1]);
                System.out.println("HTTP Version:\t" + firstLine[2]);
                System.out.println();
                //返回数据给客户端
                StringBuilder sb = new StringBuilder();
                sb.append("HTTP/1.1 200 OK\r\n");//响应报文首行,表示请求成功
                sb.append("Content-Type:text/html;charset=").append(localCharset).append("\r\n");
                sb.append("\r\n");//报文头结束后需要加一个空行

                sb.append("显示报文")
                        .append("接收到的报文是:
"
); for (String s : requestMessage) { sb.append(s).append("
"
); } sb.append(""); buffer = ByteBuffer.wrap(sb.toString().getBytes(localCharset)); sc.write(buffer); //关闭socket sc.close(); } } @Override public void run() { try { //接收到连接请求时 if (key.isAcceptable()) { handleAccept(key); } //读数据 if (key.isReadable()) { handleRead(key); } } catch (IOException e) { e.printStackTrace(); } } } }

启动这个程序,然后再浏览器输入http://127.0.0.1:8080发起请求,这是控制台和浏览器窗口都会输出这次请求的请求头以及相应的响应信息。

你可能感兴趣的:(Java笔记)