《Spring MVC源码分析与实战》笔记(二)

Socket 与NioSocket

Socket分为ServerSocket和Socket两大类,ServerSocket用于服务端,可以通过accept()方法监听请求,监听到请求后返回Socket,Socket用于具体的数据传输,客户端可以直接使用Socket向服务端发起请求

accept()是阻塞方法,每次服务端监听到一个请求后,会新开一个线程负责与当前Socket通信,假如客户端网络有延迟,服务端对应客户端Socket的线程就会一直等待,对于服务器而言这是一种非常消耗资源的事情,于是从JDK1.4开始,就有了新的IO方式:NioSocket

举个通俗的例子来说,用去餐馆吃饭来打比方,用顾客比喻客户端,传统的Socket相当于每个顾客配有一个专属厨师(比喻ServerSocket新线程),在点菜(比喻客户端准备数据)的时候,顾客考虑多久,这个厨师就等待多久,直到顾客想好要吃什么,厨师才开始做菜(比喻客户端准备数据);而NIO模式下,还是用顾客比喻客户端,但这个时候整个餐厅只需要一个厨师外加一个服务员就够了,顾客想好要吃什么,会通知服务员(也就是事件驱动),然后服务员再将这些请求收集起来一并交给厨师处理,这样一来,本身需要多线程解决的事情现在单线程也解决了

NioSocket中有三个重要概念:

  • Buffer:服务端与客户端之间交换的数据
  • Channel:分为ServerSocketChannel和SocketChannel,在上文的例子中,对应厨师和顾客
  • Selector:在上文的例子中,对应服务员,Selector管理Channel,并注册相应的事件,它采取事件驱动的方式,当注册的事件就绪后,调用相应的线程处理该事件,而不必像传统Socket那样用线程去维持一个长连接,从而减少了线程的开销(注意:Selector与Channel没有谁属于谁的关系,他们类似于数据库中“多对多”的关系,一个顾客可以喊不同的服务员给自己加菜,一个服务员也可以服务不同的顾客)

NioSocket实现简单HTTP协议的例子:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;

public class HttpServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080)); // 创建channel 并监听8080端口
        ssc.configureBlocking(false); // 设置为非阻塞模式,只有这样才能注册选择器
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT); // 为channel注册选择器
        // 创建处理器
        while (true) {
            // 等待请求,每次等待三秒,超过三秒后程序继续向下执行,如果传入参数0或不传参数将一直阻塞
            if (selector.select(3000) == 0) {
                continue;
            }
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                // 启动新线程处理SelectionKey
                new Thread(new HttpHandler(key)).run();
                // 处理完后,从待处理的集合中移除当前key
                keyIterator.remove();
            }
        }
    }

    private static class HttpHandler implements Runnable {
        private int bufferSize = 1024;
        private String localCharset = "UTF-8";
        private SelectionKey key;

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

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

        public void handleRead() throws IOException {
            // 获取channel
            SocketChannel sc = (SocketChannel)key.channel();
            // 获取buffer并重置
            ByteBuffer buffer = (ByteBuffer)key.attachment();
            buffer.clear();
            // 没有读到内容则关闭
            if (sc.read(buffer) == -1) {
                sc.close();
            } else {
                // 接收请求数据
                buffer.flip();
                String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                // 控制台打印请求报文头
                String[] requestMessage = receivedString.split("\r\n");
                for (String s : requestMessage) {
                    System.out.println(s);
                    // 遇到空行说明报文头已经打印完
                    if (s.isEmpty()) {
                        break;
                    }
                }

                // 控制台打印首行信息
                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 sendString = new StringBuilder();
                sendString.append("HTTP/1.1 200 OK\r\n"); //响应报文首行,200表示处理成功
                sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");
                sendString.append("\r\n"); // 报文头结束后加一个空行

                sendString.append("<html><head><title>显示报文</title></head><body>");
                sendString.append("接收到的请求报文是:<br/>");
                for (String s : requestMessage) {
                    sendString.append(s + "<br/>");
                }
                sendString.append("</body></html>");
                buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
                sc.write(buffer);
                sc.close();
            }
        }

        @Override
        public void run() {
            try {
                // 接收到连接请求
                if (key.isAcceptable()) {
                    handleAccept();
                }
                // 读数据
                if (key.isReadable()) {
                    handleRead();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/yaoomoon/p/11716144.html