java I/O体系总结(四) 使用socket构建HTTP服务器

java I/O体系总结(四) 使用socket构建HTTP服务器

前言

java I/O体系总结(三)中,已使用阻塞和非阻塞的方式分别构建了一个简单的服务器。HTTP作为Web浏览器和Web服务器之间通信的标准协议,在网络编程中有也重要位置。下面就深入HTTP协议,尝试使用NIO构建简单的HTTP服务器。

HTTP协议

我们都知道,HTTP协议是应用层协议,全称超文本传输协议。构建于TCP/IP之上。默认使用80端口通信。一般情况下的HTTP协议是由客户端发起,服务端响应的模式。HTTP协议还是一个无状态协议,同一个客户端的某此请求和其上一次请求无关。

HTTP请求

HTTP格式分为请求和响应。两个形式格式不同。请求一般由客户端发起,表明向服务器请求某项资源。响应由服务器做出,用来响应客户端。

请求格式为 : 请求行+请求首部+空行+请求消息体

必须有的是请求行,首部和消息体不一定有。

示例:

POST /index HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Connection: keep-alive
Content-Length: 308
Content-Type: text/plain;charset=UTF-8

[{"headers":{"component":"enterprise","datatype":"re","version":"v1"},"body":"{\"re\":\"uid=wthfeng&ref=https%3A%2F%2Fblog.csdn.net%2F&pid=blog&mod=popu_4&dsm=get&mtp=2&con=&ck=-&curl=https%3A%2F%2Fblog.csdn.net%2Fwthfeng&session_id=10_1531129353425.349656&x-acl-token=status_js_dkuyqthzbajmncbsb_token\"}"}]

第一行即请求行,必须有。在此例中为POST /index HTTP/1.1。post表明请求方法,/index表示请求地址;HTTP/1.1表示http版本。余下类似User-Agent、Connection等为请求首部。空行之后,就是请求体正文。

HTTP响应

HTTP响应与请求类似,同样有一定格式。主要包括: 状态行+响应首部+空行+响应正文。

如下:

HTTP/1.1 200 OK
Server: openresty
Date: Fri, 14 Sep 2018 04:24:37 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Content-Encoding: gzip
Strict-Transport-Security: max-age= 31536000

<html>
...
/html>

HTTP/1.1 200 OK即是状态行。200为状态码,表明此次请求是成功的。HTTP/1.1为http版本。后续为响应header。空行之后为响应的资源,这里是响应的html页面。

服务器

服务器可说的不多,主要在于怎样使用NIO构建一个http响应。当然这里并不是一个真正的http服务器,只是任何请求都返回了一个百度页面。此案例参考了《java网络编程 第4版》第9章。


   public class NioHttpServer {


    public static void main(String[] args) throws Exception {
        int port = 8080;

        String encoding = "UTF-8";

        AtomicInteger totalNum = new AtomicInteger(0);

        Path path = Paths.get("/Users/wangtonghe/Desktop/baidu.html");
        byte[] data = Files.readAllBytes(path);
        String contentType = "text/html";
        // http的响应header
        String header =
                // 状态行
                "HTTP/1.1 200 OK\n" +
                        "Server: OneFile 2.0\n" +
                        "Content-length: " + data.length + "\r\n" +
                        "Content-type: " + contentType + ",charset=" + encoding
                        // 空行
                        + "\r\n\r\n";
        // 创建 ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 绑定到本地端口
        serverSocketChannel.bind(new InetSocketAddress(port));

        Selector selector = Selector.open();
        // 注册事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        ByteBuffer readBuf = ByteBuffer.allocate(50);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 删除,避免处理多次
                iterator.remove();
                //接收通道已准备好(即服务端有数据过来)
                if (selectionKey.isAcceptable()) {
                    System.out.println("接受请求数:" + totalNum.incrementAndGet());
                    // 获取关联的ServerSocketChannel(因为是服务器channel)
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    // 这里是不阻塞的,获取客户端的
                    SocketChannel socketChannel = ssc.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);

                    // 客户端有可读事件
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    readBuf.clear();
                    StringBuilder strBuilder = new StringBuilder();
                    while (socketChannel.read(readBuf) > 0) {
                        readBuf.flip();
                        while (readBuf.hasRemaining()) {
                            strBuilder.append((char) readBuf.get());
                        }
                        readBuf.clear();
                    }
                    System.out.println(strBuilder.toString());
                } else if (selectionKey.isWritable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer dataBuf = ByteBuffer.wrap(data);
                    ByteBuffer headerBuf = ByteBuffer.wrap(header.getBytes());
                    //
                    dataBuf.rewind();
                    headerBuf.rewind();
                    // 向客户端写http的header
                    socketChannel.write(headerBuf);
                    // 向客户端写http的响应正文
                    socketChannel.write(dataBuf);
                    // 处理完后关闭连接
                    socketChannel.close();
                }

            }
        }
    }
}

总结

从此示例中,算是真正知道了http的响应规则。之前知道http有header头和正文,却不知道是怎样关联和怎样输出。其实也是,各种协议之间也就是通过传输规则和位置确定的。

不过有个问题是,当我用jmeter压测这个服务器时,效果却不很理想。当达到上千TPS时,错误率高的惊人。怀疑写的有问题或哪里不对。还想各位指正哈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值