4.1概述【理解】

  • BIO

    Blocking IO,阻塞型IO

  • NIO

    No Blocking IO,非阻塞型IO

  • 阻塞IO的弊端

    在等待的过程中,什么事也做不了

  • 非阻塞IO的好处

    不需要一直等待,当一切就绪了再去做

4.2NIO与BIO的区别【理解】

  • 区别一

    BIO是阻塞的,NIO是非阻塞的

  • 区别二

    BIO是面向流的,NIO是面向缓冲区的

    BIO中数据传输是单向的,NIO中的缓冲区是双向的

4.3NIO三大模块【理解】

  • 缓冲区

    用来存储数据

  • 通道

    用来建立连接和传输数据

  • 选择器

    监视通道状态

4.4NIO创建缓冲区对象【应用】

缓冲区其实就是一个数组

  • 方法介绍

    方法名 说明
    static ByteBuffer allocate(长度) 创建byte类型的缓冲区
    static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区
  • 代码示例

package com.itheima.mynio;

import java.nio.ByteBuffer;

public class CreateByteBufferDemo1 {
public static void main(String[] args) {
//method1();
//method2();
method3();
} public static void method3() {
ByteBuffer byteBuffer3 = ByteBuffer.wrap("aaa".getBytes());
for (int i = 0; i < 3; i++) {
System.out.println(byteBuffer3.get());
}
} public static void method2() {
byte [] bytes={97,98,99};
ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes);
//缓冲区的长度3
//缓冲区的内容就是字节数组的内容
for (int i = 0; i < 3; i++) {
System.out.println(byteBuffer2.get());
}
} public static void method1() {
ByteBuffer byteBuffer1=ByteBuffer.allocate(5);
//get
for (int i = 0; i < 5; i++) {
System.out.println(byteBuffer1.get());
}
System.out.println(byteBuffer1.get());
}
}

4.5NIO缓冲区添加数据【应用】

缓冲区其实就是一个数组

两个功能:

  读/写

问:我要把数据写到缓冲区中          使用缓冲读的功能

我要把缓冲区的数据读出来并打印    使用缓冲写的功能

  • 如何用写?何时用读?
  • 必须站在缓冲区的角度思考问题。
  • capacity:容量(长度)
  • limit:界限(最多能读/写到哪里)
  • posotion:位置(读/写哪个索引)

存三个字节

调用flip方法

读一个字节后

读第一个数组后

调用rewind后

调用clear后

  • 方法介绍

package com.itheima.mynio;

import java.nio.ByteBuffer;

public class ByteBufferDemo2 {
public static void main(String[] args) {
// int position() 当前要操作的索引
// int limit() 最多能操作到哪个索引
// int capacity() 缓冲区的总长度
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity()); // put(byte b) 一次添加一个字节
// byteBuffer.put((byte) 97);
// System.out.println(byteBuffer.position());
// System.out.println(byteBuffer.limit());
// System.out.println(byteBuffer.capacity());
// put(byte[] src) 一次添加一个字节数组
// byteBuffer.put("aaa".getBytes());
// System.out.println(byteBuffer.position());
// System.out.println(byteBuffer.limit());
// System.out.println(byteBuffer.capacity()); // position(int newPosition) 修改position
// byteBuffer.position(1);
// limit(int newLimit) 修改limit
// byteBuffer.limit(5);
// System.out.println(byteBuffer.position());
// System.out.println(byteBuffer.limit());
// System.out.println(byteBuffer.capacity());
// int remaining() 还有多少能操作
byteBuffer.put("0123456789".getBytes());
System.out.println(byteBuffer.remaining());//10
System.out.println(byteBuffer.hasRemaining());//true
// boolean hasRemaining() 是否还有能操作的
} }

4.6NIO缓冲区获取数据【应用】

  • 方法介绍

    方法名 介绍
    flip() 切换读写模式(写à读)
    get() 读一个字节
    get(byte[] dst) 读多个字节
    get(int index) 读指定索引的字节
    rewind() 将position设置为0,可以重复读
    clear() 数据读写完毕(读->写)
    array() 将缓冲区转换成字节数组返回
  • 代码示例

package com.itheima.mynio;

import java.nio.ByteBuffer;

public class ByteBufferDemo3 {
public static void main(String[] args) {
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
byteBuffer.put("abc".getBytes());
//flip() 切换读写模式(写->读)
byteBuffer.flip();
//get() 读一个字节
// while(byteBuffer.position()!=byteBuffer.limit()){
// System.out.println((char) byteBuffer.get());
// }
for (int i = 0; i < byteBuffer.limit(); i++) {
System.out.println((char) byteBuffer.get());
}
//get(byte[] dst) 读多个字节
// byte []bytes=new byte[byteBuffer.limit()];
// byteBuffer.get(bytes);
// System.out.println(new String(bytes));
//
//
// //get(int index) 读指定索引的字节
// System.out.println((char) byteBuffer.get(0)); //rewind() 将position设置为0,可以重复读
// byteBuffer.rewind();
// for (int i = 0; i < byteBuffer.limit(); i++) {
// System.out.println((char) byteBuffer.get());
// } //clear() 数据读写完毕(读->写)
byteBuffer.clear();
byteBuffer.put("qqq".getBytes()); //array() 将缓冲区转换成字节数组返回
byte[] bytes = byteBuffer.array();
System.out.println(new String(bytes)); } }

4.7小结【理解】

  1. 需求:我要把数据写到缓冲区中。

    数据是从外面进入到缓冲区的,所以缓冲区在做读数据的操作。

  2. 需求:我要把数据从缓冲区中读出来。

    数据是从缓冲区里面到外面的。所以缓冲区在做写数据的操作。

  3. capacity:容量(长度) limit: 界限(最多能读/写到哪里) posotion:位置(读/写哪个索引)

  4. 获取缓冲区里面数据之前,需要调用flip方法

  5. 再次写数据之前,需要调用clear方法,

    但是数据还未消失,等再次写入数据,被覆盖了才会消失。

1.NIO

1.1 NIO通道客户端【应用】

客户端通道----------连接服务端

------------传递缓冲区---------------传递数据

服务端通道-----------建立连接

  • 客户端实现步骤

    1. 打开通道---------------SocketChannel

    2. 指定IP和端口号--------InetSocketAddress

    3. 写出数据-------------------------write

    4. 释放资源----------------------释放资源

  • 示例代码

package com.itheima.mynio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class NIOClient {
public static void main(String[] args) throws IOException {
//1. 打开通道
SocketChannel socketChannel = SocketChannel.open(); //2. 指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); //3. 写出数据
ByteBuffer byteBuffer = ByteBuffer.wrap("hello".getBytes());
socketChannel.write(byteBuffer);
//4. 释放资源
socketChannel.close();
}
}

1.2 NIO通道服务端【应用】

  • NIO通道

    • 服务端通道

      只负责建立建立,不负责传递数据

    • 客户端通道

      建立建立并将数据传递给服务端

    • 缓冲区

      客户端发送的数据都在缓冲区中

    • 服务端通道内部创建出来的客户端通道

      相当于客户端通道的延伸用来传递数据

  • 服务端实现步骤

    1. 打开一个服务端通道

    2. 绑定对应的端口号

    3. 通道默认是阻塞的,需要设置为非阻塞

    4. 此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?

    5. 如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸

    6. 获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中

    7. 服务端创建一个空的缓冲区装数据并输出
    8. 给客户端回写数据

    9. 释放资源

  • 示例代码

package com.itheima.mynio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class NIOClient {
public static void main(String[] args) throws IOException {
//1. 打开通道
SocketChannel socketChannel = SocketChannel.open(); //2. 指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); //3. 写出数据
ByteBuffer byteBuffer = ByteBuffer.wrap("一点汗毛献辞".getBytes());
socketChannel.write(byteBuffer);
//4. 释放资源
socketChannel.close();
}
}

1.3 NIO通道练习【应用】

需求:利用NIO的非阻塞通道与缓冲区完成下面的需求

  客户端发送数据给服务端

  服务端接收后回写一个数据给客户端

  • 客户端

    • 实现步骤

      1. 打开通道

      2. 指定IP和端口号

      3. 写出数据

      4. 读取服务器写回的数据

      5. 释放资源

    • 示例代码

Client

package com.itheima.mynio3;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class Client {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10001)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒".getBytes());
socketChannel.write(byteBuffer1); System.out.println("数据已经写给服务器");
socketChannel.shutdownOutput(); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len=socketChannel.read(byteBuffer2))!=-1){
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
socketChannel.close();
}
}

  Server

package com.itheima.mynio3;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; public class Server {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(10001)); serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
System.out.println("此时有客户端来连接了"); //获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
//socketChannel.read(byteBuffer1);
int len;
//针对于缓冲区来讲
//如果 从添加数据---->获取数据 flip
//如果 从获取数据---->添加数据 clear
while((len=socketChannel.read(byteBuffer1))!=-1){
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
System.out.println("接收数据完毕,准备开始往客户端回写数据");
ByteBuffer byteBuffer2 = ByteBuffer.wrap("真疼啊".getBytes());
socketChannel.write(byteBuffer2); socketChannel.close();
}
} }
}

1.4 NIO通道练习优化【应用】

  • 存在问题

    服务端内部获取的客户端通道在读取时,如果读取不到结束标记就会一直阻塞

  • 解决方案

  • 1.给一个结束标记
  •         //结束标记
    // socketChannel.shutdownOutput();
  • 2.将服务端内部获取的客户端通道设置为非阻塞的

  • 示例代码

Client

package com.itheima.mynio3;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class Client {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10001)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒".getBytes());
socketChannel.write(byteBuffer1); System.out.println("数据已经写给服务器");
//结束标记
// socketChannel.shutdownOutput(); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len=socketChannel.read(byteBuffer2))!=-1){
System.out.println("客户端接收回写数据");
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
socketChannel.close();
}
}

Server

package com.itheima.mynio3;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; public class Server {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(10001)); serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
System.out.println("此时有客户端来连接了");
//把延伸端的非阻塞关闭 !!!!!!
socketChannel.configureBlocking(false); //获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
//socketChannel.read(byteBuffer1);
int len;
//针对于缓冲区来讲
//如果 从添加数据---->获取数据 flip
//如果 从获取数据---->添加数据 clear
while((len=socketChannel.read(byteBuffer1))>0){
System.out.println("服务端接收发送数据");
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
System.out.println("接收数据完毕,准备开始往客户端回写数据");
ByteBuffer byteBuffer2 = ByteBuffer.wrap("真疼啊".getBytes());
socketChannel.write(byteBuffer2); socketChannel.close();
}
} }
}

1.5NIO选择器【理解】

  • 概述

选择器可以监视通道的状态,多路复用  

  

  

  

  

选择器对象

  • Selector

    选择器对象

  • SelectionKey

    绑定的通道后返回的key

  • SelectableChannel

    能使用选择器的通道

    • SocketChannel

    • ServerSocketChannel

1.6NIO选择器改写服务端【应用】

当有客户端连接时,选择器会看哪个服务端通道准备好了,谁准备好了,就让谁去连

  • 实现步骤

    1. 打开一个服务端通道(open)

    2. 绑定对应的端口号

    3. 通道默认是阻塞的,需要设置为非阻塞

    4. 打开一个选择器(门卫大爷)          以上步骤中,服务端通道和门卫大爷还没有任何关系

    5. 将选择器绑定服务端通道,并监视服务端是否准备好

    6. 如果有客户端来连接了,大爷会遍历所有的服务端通道,谁准备好了,就让谁来连接 连接后,在服务端通道内部,再创建一个客户端延伸通道

    7. 如果客户端把数据传递过来了,大爷会遍历所有的延伸通道,谁准备好了,谁去接收数据

选择器监视客户端通道

选择器监视服务端通道

选择器监视客户端延伸通道

Client

package com.itheima.mynio5;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class Client {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10001)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒".getBytes());
socketChannel.write(byteBuffer1); System.out.println("数据已经写给服务器");
//结束标记
// socketChannel.shutdownOutput(); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len=socketChannel.read(byteBuffer2))!=-1){
System.out.println("客户端接收回写数据");
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
socketChannel.close();
}
}

Server

package com.itheima.mynio5;

import jdk.swing.interop.SwingInterOpUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set; public class Server {
public static void main(String[] args) throws IOException {
//1.打开服务端通道
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2.让这个通道绑定一个端口
serverSocketChannel.bind(new InetSocketAddress(10001));
//3.设置通道为非阻塞状态
serverSocketChannel.configureBlocking(false);
//4.打开一个选择器
//selector ---选择器
//SelectionKey ----绑定通道后返回的令牌
//SelectableChannel ---可以使用选择器的通道
Selector selector = Selector.open();
//5.绑定选择器和服务端通道
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){
//6.选择器会监视客户端通道的状态
//返回值就表示此时有多少个客户端来连接。
int count = selector.select();
if (count != 0) {
System.out.println("有客户端来连接了");
//7.会遍历所有的服务端通道,看谁准备好了,谁准备好了,就让谁去连接
//获取所有服务端通道的令牌,并将它们放到一个集合中,将集合返回
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
//selectionKey 依次表示每一个服务端通道的令牌
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
//可以通过令牌来获取到了一个已经就绪的服务器通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
//客户端的延伸通道
SocketChannel socketChannel = ssc.accept();
//将客户端的延伸通道设置为非阻塞
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
//当客户端来连接的时候,所有的步骤已经全部执行完毕
}else if (selectionKey.isReadable()){
//当前通道已经做好了读取的准备(延伸通道)
SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
int len;
while((len=socketChannel.read(byteBuffer1))>0){
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
//给客户端的回写数据
socketChannel.write(ByteBuffer.wrap("好疼啊!".getBytes()));
socketChannel.close();
}
iterator.remove();
} }
}
}
}

2.HTTP协议

非阻塞的HTTP服务器

  非阻塞的服务器:就是刚刚写的NIO服务器

  HTTP:互联网传输数据的一种规则

2.1概述【理解】

超文本传输协议(关于超文本的概念JavaWeb在进行学习),是建立在TCP/IP协议基础上,是网络应用层的协议。

由请求和响应构成,是一个标准的客户端和服务器模型

是互联网传输数据的一种规则

是基于TCP/IP协议

由请求和响应组成

2.2URL【理解】

  

2.3抓包工具的使用【应用】

  • 使用步骤

    1. 在谷歌浏览器网页中按F12 或者网页空白处右键,点击检查,可以调出工具

    2. 点击network,进入到查看网络相关信息界面

    3. 这时在浏览器中发起请求,进行访问,工具中就会显示出请求和响应相关的信息

Request Headers  请求数据

Response Headers  响应数据

2.4请求信息【理解】

Request Headers  请求

  • 组成

    • 请求行

    • 请求头

    • 请求空行

    • 请求体

  • 请求行

    • 格式

 

GET /index.html HTTP/1.1

使用GET方式请求数据是没有请求体的

  • 请求方式

    GET,POST,HEAD,PUT,DELETE,CONNECT,OPTIONS,TRACE,PATCH

    其中用的比较多的是GET和POST

  • URI

    请求资源路径,统一资源标识符

  • 协议版本

    • HTTP1.0: 每次请求和响应都需要建立一个单独的连接

    • HTTP1.1:支持长连接

请求头

  • 格式

请求头名称

  • Host: 用来指定请求的服务端地址

  • Connection: 取值为keep-alive表示需要持久连接

  • User-Agent: 客户端的信息

  • Accept: 指定客户端能够接收的内容类型

  • Accept-Encoding: 指定浏览器可以支持的服务器返回内容压缩编码类型

  • Accept-Language: 浏览器可接受的语言

小结

2.5响应信息【理解】

  • 组成

    • 响应行

    • 响应头

    • 响应空行

    • 响应体

  • 响应行

    • 格式

Http/1.1 200 OK

    • 协议版本

      • HTTP1.0: 每次请求和响应都需要建立一个单独的连接

      • HTTP1.1: 支持长连接

    • 响应状态码

      • 1xx: 指示信息(表示请求已接收,继续处理)

      • 2xx: 成功(表示请求已被成功接收、理解、接受)

      • 3xx: 请求重定向(要完成请求必须进行更进一步的操作)

      • 4xx: 客户端错误(请求有语法错误或请求无法实现)

      • 5xx: 服务器端错误(服务器未能实现合法的请求)

    • 状态信息

      • 200 ok

      • 404 Not Found

      • 500 Internal Server Error

  • 响应头

  • 响应头名称   冒号   空格    响应头值   回车符   换行符
    • 响应头名称

      • Content-Type: 告诉客户端实际返回内容的网络媒体类型(互联网媒体类型,也叫做MIME类型)

    • 响应头值

      • text/html ----> 文本类型

      • image/png ----> png格式文件

      • image/jpeg ----> jpg格式文件

小结

3.HTTP服务器

3.1需求【理解】

  • 编写服务器端代码,实现可以解析浏览器的请求,给浏览器响应数据

3.2环境搭建【理解】

如何才能让这个服务器支持HTTP协议呢

  可以解析出HTTP请求协议的内容

  可以按照HTTP响应协议格式,给浏览器响应数据

  • 实现步骤

    • 编写HttpServer类,实现可以接收浏览器发出的请求

    • 其中获取连接的代码可以单独抽取到一个类中

  • 代码实现

服务端代码HttpServer

package com.itheima.mynio6;

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; public class HttpServer {
public static void main(String[] args) throws IOException {
//1.打开服务端通道
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2.让这个通道绑定一个端口
serverSocketChannel.bind(new InetSocketAddress(10001));
//3.设置通道为非阻塞状态
serverSocketChannel.configureBlocking(false);
//4.打开一个选择器
//selector ---选择器
//SelectionKey ----绑定通道后返回的令牌
//SelectableChannel ---可以使用选择器的通道
Selector selector = Selector.open();
//5.绑定选择器和服务端通道
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){
//6.选择器会监视客户端通道的状态
//返回值就表示此时有多少个客户端来连接。
int count = selector.select();
if (count != 0) {
System.out.println("有客户端来连接了");
//7.会遍历所有的服务端通道,看谁准备好了,谁准备好了,就让谁去连接
//获取所有服务端通道的令牌,并将它们放到一个集合中,将集合返回
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
//selectionKey 依次表示每一个服务端通道的令牌
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
//获取连接
AcceptHandler acceptHandler=new AcceptHandler();
acceptHandler.connSocketChannel(selectionKey);
//当客户端来连接的时候,所有的步骤已经全部执行完毕
}else if (selectionKey.isReadable()){
//当前通道已经做好了读取的准备(延伸通道)
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
StringBuilder sb=new StringBuilder();
//创建缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
int len;
//循环读取
while((len=socketChannel.read(byteBuffer1))>0){
byteBuffer1.flip();
sb.append(new String(byteBuffer1.array(),0,len));
//System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
System.out.println(sb);
//给客户端的回写数据
socketChannel.write(ByteBuffer.wrap("好疼啊!".getBytes()));
socketChannel.close();
}
iterator.remove();
} }
}
}
}

// 将获取连接的代码抽取到这个类中

package com.itheima.mynio6;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; /*
* 接受连接的任务处理类
*
* */
public class AcceptHandler {
public SocketChannel connSocketChannel(SelectionKey selectionKey){
try {
//可以通过令牌来获取到了一个已经就绪的服务器通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
//客户端的延伸通道
SocketChannel socketChannel = ssc.accept();
//将客户端的延伸通道设置为非阻塞
socketChannel.configureBlocking(false);
//把socketChannel注册到选择器上
socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
return socketChannel;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

3.3获取请求信息并解析【理解】

  • 实现步骤

    • 将请求信息封装到HttpRequest类中

    • 在类中定义方法,实现获取请求信息并解析

  • 代码实现

HttpRequest

package com.itheima.mynio6;

import javax.naming.ldap.PagedResultsControl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Hashtable; /*
* 用来封装请求数据的类
* */
public class HttpRequest {
private String method;//请求方式
private String requestURI;//请求的URI
private String version;//http的协议版本 private HashMap<String,String> hm=new HashMap<>();//所有的请求头 //parse ----获取请求数据,并解析
public void parse(SelectionKey selectionKey){
try {
//当前通道已经做好了读取的准备(延伸通道)
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
StringBuilder sb=new StringBuilder();
//创建缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
int len;
//循环读取
while((len=socketChannel.read(byteBuffer1))>0){
byteBuffer1.flip();
sb.append(new String(byteBuffer1.array(),0,len));
//System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
//System.out.println(sb);
parseHttpRequest(sb);
} catch (IOException e) {
e.printStackTrace();
} }
//解析http请求协议中的数据
private void parseHttpRequest(StringBuilder sb) {
//1.需要把StringBuilder先变成一个字符串
String httpRequeststr=sb.toString();
//2.获取每一行数据
String[] split = httpRequeststr.split("\r\n");
//3.获取请求行
String httpRequestLine = split[0];//GET / HTTP/1.1
//4.再按照空格进行切割,得到请求行中的三部分
String[] httpRequestInfo = httpRequestLine.split(" ");
this.method=httpRequestInfo[0];
this.requestURI=httpRequestInfo[1];
this.version=httpRequestInfo[2];
//5.操作每一个请求头
for (int i = 1; i < split.length; i++) {
String httpRequestHeaderInfo = split[i];
String[] httpRequestHeaderInfoArr = httpRequestHeaderInfo.split(": ");
hm.put(httpRequestHeaderInfoArr[0],httpRequestHeaderInfoArr[1]);
}
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} public String getRequestURI() {
return requestURI;
} public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
} public String getVersion() {
return version;
} public void setVersion(String version) {
this.version = version;
} public HashMap<String, String> getHm() {
return hm;
} public void setHm(HashMap<String, String> hm) {
this.hm = hm;
} @Override
public String toString() {
return "HttpRequest{" +
"method='" + method + '\'' +
", requestURI='" + requestURI + '\'' +
", version='" + version + '\'' +
", hm=" + hm +
'}';
}
}

3.4给浏览器响应数据【理解】

  • 实现步骤

    • 将响应信息封装HttpResponse类中

    • 定义方法,封装响应信息,给浏览器响应数据

  • 代码实现

package com.itheima.mynio6;

import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; public class HttpResponse {
private String version;//协议版本
private String status;//响应状态码
private String desc;//状态码的描述信息 //响应头数据
private HashMap<String,String> hm=new HashMap<>();
private HttpRequest httpRequest;//我们后面要根据请求的信息,来进行一些判断 //给浏览器响应数据的方法
public void sendstaticResource(SelectionKey selectionKey){
//1.给相应行赋值
this.version="HTTP/1.1";
this.status="200";
this.desc="ok";
//2.将响应行拼接成一个单独的字符串 //HTTP/1.1 200 ok
String responseLine=this.version+" "+this.status+" "+this.desc+"\r\n";
//3.给响应头赋值
hm.put("Content-Type","text/html;charset=UTF-8");
//4.将所有的响应头拼接成一个单独的字符串
StringBuilder sb=new StringBuilder();
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> entry : entries) {
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n"); }
//5.响应空行
String emptyLine="\r\n";
//6.相应行,响应头,相应空行拼接成一个大的字符串
String reponseLineStr=responseLine+sb.toString()+emptyLine;
try {
//7.将上面三个写给浏览器
SocketChannel socketchannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer1 = ByteBuffer.wrap(reponseLineStr.getBytes());
socketchannel.write(byteBuffer1);
//8.单独操作响应体
//因为在以后的响应体不一定是一个字符串
//有可能是一个文件,所以单独操作
String s="哎呀妈呀";
ByteBuffer byteBuffer2=ByteBuffer.wrap(s.getBytes());
socketchannel.write(byteBuffer2);
//9.释放资源
socketchannel.close();
} catch (IOException e) {
e.printStackTrace();
}
} public String getVersion() {
return version;
} public void setVersion(String version) {
this.version = version;
} public String getStatus() {
return status;
} public void setStatus(String status) {
this.status = status;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} public HashMap<String, String> getHm() {
return hm;
} public void setHm(HashMap<String, String> hm) {
this.hm = hm;
} public HttpRequest getHttpRequest() {
return httpRequest;
} public void setHttpRequest(HttpRequest httpRequest) {
this.httpRequest = httpRequest;
} @Override
public String toString() {
return "HttpResponse{" +
"version='" + version + '\'' +
", status='" + status + '\'' +
", desc='" + desc + '\'' +
", hm=" + hm +
", httpRequest=" + httpRequest +
'}';
}
}

3.5代码优化【理解】

  • 实现步骤

    • 根据请求资源路径不同,响应不同的数据

    • 服务端健壮性处理

    • 访问不存在的资源处理

  • 代码实现

  

/*
* 接受连接的任务处理类
*

 

package com.itheima.mynio9;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; /*
* 接受连接的任务处理类
*
* */
public class AcceptHandler {
public SocketChannel connSocketChannel(SelectionKey selectionKey){
try {
//可以通过令牌来获取到了一个已经就绪的服务器通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
//客户端的延伸通道
SocketChannel socketChannel = ssc.accept();
//将客户端的延伸通道设置为非阻塞
socketChannel.configureBlocking(false);
//把socketChannel注册到选择器上
socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
return socketChannel;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

 HttpResponse

package com.itheima.mynio9;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; public class HttpResponse {
private String version; //协议版本
private String status; //响应状态码
private String desc; //状态码的描述信息 //响应头数据
private HashMap<String, String> hm = new HashMap<>(); private HttpRequest httpRequest; //我们后面要根据请求的数据,来进行一些判断 //给浏览器响应数据的方法
public void sendStaticResource(SelectionKey selectionKey) {
//1.给响应行赋值
this.version = "HTTP/1.1";
this.status = "200";
this.desc = "ok"; //3.给响应头赋值
//先获取浏览器请求的URI
String requestURI = this.getHttpRequest().getRequestURI();
if(requestURI != null){ File file = new File(WEB_APP_PATH + requestURI);
//判断这个路径是否存在
if(!file.exists()){
this.status = "404";
this.desc = "NOT FOUNG";
} if("200".equals(this.status)){
if("/".equals(requestURI)){
hm.put("Content-Type", "text/html;charset=UTF-8");
}else if("/favicon.ico".equals(requestURI)){
hm.put("Content-Type", "image/x-icon");
}else if("/a.txt".equals(requestURI)){
hm.put("Content-Type", "text/html;charset=UTF-8");
}else if("/1.jpg".equals(requestURI)){
hm.put("Content-Type", "image/jpeg");
}else if("/1.png".equals(requestURI)){
hm.put("Content-Type", "image/png");
}
}else{
hm.put("Content-Type", "text/html;charset=UTF-8");
} } //2.将响应行拼接成一个单独的字符串 // HTTP/1.1 200 ok
String responseLine = this.version + " " + this.status + " " + this.desc + "\r\n"; //4.将所有的响应头拼接成一个单独的字符串
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> entry : entries) {
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
} //5.响应空行
String emptyLine = "\r\n"; //6.响应行,响应头,响应空行拼接成一个大字符串
String responseLineStr = responseLine + sb.toString() + emptyLine; try {
//7.将上面三个写给浏览器
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer1 = ByteBuffer.wrap(responseLineStr.getBytes());
socketChannel.write(byteBuffer1); //8.单独操作响应体
//因为在以后响应体不一定是一个字符串
//有可能是一个文件,所以单独操作
// String s = "哎哟,妈呀,终于写完了.";
byte [] bytes = getContent();
ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes);
socketChannel.write(byteBuffer2); //9.释放资源
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
} public static final String WEB_APP_PATH = "internetmodel\\webapp";
private byte[] getContent() {
try {
//1.获取浏览器请求的URI
String requestURI = this.getHttpRequest().getRequestURI();
if(requestURI != null){ if("200".equals(this.status)){
//2.判断一下请求的URI,根据不同的URI来响应不同的东西
if("/".equals(requestURI)){
String s = "哎哟,妈呀,终于写完了.";
return s.getBytes();
}else/* if("/favicon.ico".equals(requestURI))*/{
//获取一个ico文件
FileInputStream fis = new FileInputStream(WEB_APP_PATH + requestURI);
//把ico文件变成一个字节数组返回
return IOUtils.toByteArray(fis);
}
}else{
return "访问的资源不存在".getBytes();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
} public String getVersion() {
return version;
} public void setVersion(String version) {
this.version = version;
} public String getStatus() {
return status;
} public void setStatus(String status) {
this.status = status;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} public HashMap<String, String> getHm() {
return hm;
} public void setHm(HashMap<String, String> hm) {
this.hm = hm;
} public HttpRequest getHttpRequest() {
return httpRequest;
} public void setHttpRequest(HttpRequest httpRequest) {
this.httpRequest = httpRequest;
} @Override
public String toString() {
return "HttpResponse{" +
"version='" + version + '\'' +
", status='" + status + '\'' +
", desc='" + desc + '\'' +
", hm=" + hm +
", httpRequest=" + httpRequest +
'}';
}
}
HttpServer

package com.itheima.mynio9;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set; public class HttpServer {
public static void main(String[] args) throws IOException {
//1.打开服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.让这个通道绑定一个端口
serverSocketChannel.bind(new InetSocketAddress(10000));
//3.设置通道为非阻塞
serverSocketChannel.configureBlocking(false);
//4.打开一个选择器
Selector selector = Selector.open();
//5.绑定选择器和服务端通道
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); while(true){
//6.选择器会监视通道的状态.
int count = selector.select();
if(count != 0){
//7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
//获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
//selectionKey 依次表示每一个服务端通道的令牌
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
//获取连接
AcceptHandler acceptHandler = new AcceptHandler();
acceptHandler.connSocketChannel(selectionKey); }else if(selectionKey.isReadable()){
//读取数据
HttpRequest httpRequest = new HttpRequest();
httpRequest.parse(selectionKey);
System.out.println("http请求的数据为 ---->" + httpRequest); if(httpRequest.getRequestURI() == null || "".equals(httpRequest.getRequestURI())){
selectionKey.channel();
continue;
}
System.out.println("...数据解析完毕,准备响应数据...."); //响应数据
HttpResponse httpResponse = new HttpResponse();
httpResponse.setHttpRequest(httpRequest);
httpResponse.sendStaticResource(selectionKey);
}
//任务处理完毕以后,将SelectionKey从集合中移除
iterator.remove();
}
}
}
}
}

  

  

 

 

  

  

 

  

  

  

------------恢复内容结束------------

33.2.NIO的更多相关文章

  1. JAVA NIO系列(二) Channel解读

    Channel就是一个通道,用于传输数据,两端分别是缓冲区和实体(文件或者套接字),通道的特点(也是NIO的特点):通道中的数据总是要先读到一个缓冲区,或者总是要从一个缓冲区中读入. Channel的 ...

  2. nio加强服务端并发

    究了一下Android推送,方式很多,比如用框架或者用第三方服务,在此并不讨论个中优劣.抱着学习的态度,本人不太喜欢用一些现成的东西,所以自己动手实现了一套简单的推送机制.使用TCP长连接,完成服务器 ...

  3. Java NIO 之缓冲区

    缓冲区基础 所有的缓冲区都具有四个属性来 供关于其所包含的数据元素的信息. capacity(容量):缓冲区能够容纳数据的最大值,创建缓冲区后不能改变. limit(上界):缓冲区的第一个不能被读或写 ...

  4. java编解码技术,netty nio

    对于java提供的对象输入输出流ObjectInputStream与ObjectOutputStream,可以直接把java对象作为可存储 的字节数组写入文件,也可以传输到网络上去.对与java开放人 ...

  5. Java NIO 网络编程基础

    Java NIO提供了一套网络api,可以用来处理连接数很多的情况.他的基本思想就是用一个线程来处理多个channel. 123456789101112131415161718192021222324 ...

  6. Java NIO、NIO.2学习笔记

    相关学习资料 http://www.molotang.com/articles/903.html http://www.ibm.com/developerworks/cn/education/java ...

  7. java的nio之:java的nio的服务器实现模型

    [nio服务端序列图]

  8. socket的NIO操作

    一.前言 Java中直接使用socket进行通信的场景应该不是很多,在公司的一个项目中有这种需求,所以根据自己的理解和相关资料的参考,基于NIO 实现了一组工具类库,具体的协议还未定义,后续再整理 二 ...

  9. Getting started with new I/O (NIO)--reference

    The new input/output (NIO) library, introduced with JDK 1.4, provides high-speed, block-oriented I/O ...

随机推荐

  1. C# 8.0 宝藏好物 Async streams

    之前写<.NET gRPC 核心功能初体验>,利用gRPC双向流做了一个打乒乓的Demo,存储消息的对象是IAsyncEnumerable<T>,这个异步可枚举泛型接口支撑了g ...

  2. 获取执行计划之Autotrace

    Autotrace 简介 AUTOTRACE是一项SQL*Plus功能,自动跟踪为SQL语句生成一个执行计划并且提供与该语句的处理有关的统计. AUTOTRACE的好处是您不必设置跟踪文件的格式,并且 ...

  3. 分享一次排查CLOSE_WAIT过多的经验

    关键词:TCP.CLOSE_WAIT 问题背景 某日下午有测试人员急匆匆的跑来跟我反馈:"有客户反馈供应商附件预览不了,流程阻塞,需要紧急处理",我立马精神起来,毕竟都是付费客户( ...

  4. JS复制文本到粘贴板,前端H5移动端点击按钮复制文本

    <span id="codeNum">FTYHDSDW</span> <span class=" code-btn" id=&qu ...

  5. 2020-BUAA OO-面向对象设计与构造-第三单元总结

    Part-1 JML总结 Section-1 理论基础 The Java Modeling Language (JML) is a behavioral interface specification ...

  6. Ducci Sequence UVA - 1594

      A Ducci sequence is a sequence of n-tuples of integers. Given an n-tuple of integers (a1,a2,···,an ...

  7. JDBC_04_使用Properties集合保存JDBC所需配置信息

    使用Properties集合保存JDBC所需配置信息 将JDBC连接所需的配置信息保存在一个配置文件中,然后使用Properties将该信息存储起来,动态的完成JDBC的配置连接 代码: import ...

  8. Jenkins 自动触发执行的配置

    1. 两种触发方式 2. jenkins 和 github 同步配置 ngrok 安装 webhook 配置 1. 两种触发条件 Jenkins 中建立的任务是可以设置自动触发,更进一步的实现自动化. ...

  9. 网页解析:Xpath 与 BeautifulSoup

    1. Xpath 1.1 Xpath 简介 1.2 Xpath 使用案例 2. BeautifulSoup 2.1 BeautifulSoup 简介 2.2 BeautifulSoup 使用案例 1) ...

  10. 痞子衡嵌入式:利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache. 关于i.MXRT1xxx系列内部通用数据协处 ...