Java NIO编写简单的WebServer

    在初学Java网络编程的时候,我们通常会想到使用Socket来编写网络相关的程序,比如简单的聊天室,文件传输工具等,Socket来进行网络编程的优势是读写均为阻塞操作,这样的模式输入和输出是确定的步骤,正常情况读取操作必然能读取到数据,否则不会返回。这同时Socket编程也有劣势,即针对单条连接处理时,读写均会阻塞,处理多条连接需要多个线程,这样会导致操作系统频繁切换线程,开销较大。

    说这么多是想引出Java中与Socket IO模型不同的NIO,即Java实现的非阻塞IO API,非阻塞IO可以使用单线程同时处理多个连接,在连接数较多,且每个连接的流量都比较小的时候,性能就非常的显著,在web服务器的场景下就很适合。

    下面用Java的的NIO API写了一个简单的webServer

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.util.Iterator;
import java.util.Set;

/**
 * Created by zhang_ on 2019/5/1.
 */
public class HtmlServer {

    private static final String htmlStr = "HTTP/1.1 200 OK\n\n" +
            "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"><html>\n" +
            "<title>Directory listing for /</title>\n" +
            "<body>\n" +
            "<h2>Welcome to NIO Server</h2>\n" +
            "<hr>\n" +
            "<ul>\n" +
            "<li><a href=\"hi.txt\">hi.txt</a>\n" +
            "</ul>\n" +
            "<hr>\n" +
            "</body>\n" +
            "</html>\n";


    public static void startServer(int port) {

        try {
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(port));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //注册接收连接的事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("服务器启动监听 " + port + " 端口...");
            //持续select事件
            constantSelect(selector);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void constantSelect(Selector selector) {
        while (true) {
            try {
                if (selector.select() > 0) {
                    System.out.println("当前连接数:" + selector.keys().size());
                    System.out.println("检测到活跃的连接数:" + selector.selectedKeys().size());
                    Set<SelectionKey> keys = selector.selectedKeys();
                    Iterator<SelectionKey> it = keys.iterator();
                    while (it.hasNext()) {
                        SelectionKey key = it.next();
                        if (key.isAcceptable()) {
                            //处理接收连接时间
                            boolean succ = dealAccept(selector, key);
                            if (succ)
                                it.remove();
                        }
                        if (key.isReadable()) {
                            //处理读事件
                            boolean succ = dealRead(key);
                            if (succ)
                                it.remove();
                        }

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    private static boolean dealRead(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buf = ByteBuffer.allocate(10);
        //判定读取一个字节,就返回固定页面
        try {
            int len = channel.read(buf);
            if (len <= 0) {
                //处理连接已经断开的情况
                return false;
            }
            ByteBuffer htmlBuf = ByteBuffer.allocate(htmlStr.getBytes().length);
            htmlBuf.put(htmlStr.getBytes());

            htmlBuf.flip();
            int outLen = channel.write(htmlBuf);


        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                channel.shutdownInput();
                channel.shutdownOutput();
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return true;
    }


    private static boolean dealAccept(Selector selector, SelectionKey key) {

        try {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            //接收连接后,注册读事件到selector
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

}

    使用以下主方法代码即可启动

/**
 * Created by zhang_ on 2019/5/1.
 */
public class Main {

    public static void main(String[] args) {
        HtmlServer.startServer(8080);
    }
}

该用例只使用了一个线程,可同时处理客户端的连接请求和数据读取请求,使用浏览器直接访问 

http://127.0.0.1:8080

即可获取到服务器返回的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值