- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
1.阻塞式 与 非阻塞式
2. 客户端 从键盘重复输入
while (true){
String write = new Scanner(System.in).next();
socket.getOutputStream().write(write.getBytes());
}
public class ClientSocket {
public static void main(String[] args) {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",8080));
while (true){
String write = new Scanner(System.in).next();
socket.getOutputStream().write(write.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 消耗资源
4.引入nio操作
- java 1.4推出io 对原来bio(阻塞式)进行优化
- nio 英文: no blocking io 非阻塞
- 核心:面向缓存区 基于通道实现非阻塞式io 多路io复用实现(靠选择器实现)
5.bio与nio区别:
bio: 阻塞式io 面向流传输 根据字节传输 效率很低
nio: 阻塞式io 面向缓存区 最大亮点:io多路复用机制
6. io多路复用
public class NioServerTcp {
static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
// 存放多个不同的tcp连接
private static List<SocketChannel> socketChannels = new ArrayList<>();
//多路(多个不同的tcp连接),io复用:只要一个线程去维护多个不同的io操作 最大的好处是:保证线程安全问题、减少cpu调度资源
//选择的io模型 非阻塞式
public static void main(String[] args) {
try {
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口号码
serverSocketChannel.bind(new InetSocketAddress(8080));
//设置为非阻塞式
serverSocketChannel.configureBlocking(false);
while (true) {
// 监听我们的消息 socketChannel为空没有人给我消息
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 已经有人帮我发送消息
socketChannel.configureBlocking(false);
socketChannels.add(socketChannel);
}
// 直接遍历我们的选择器所有的SocketChannel
for (SocketChannel scl : socketChannels) {
// 遍历读取
int j = scl.read(byteBuffer);
if (j > 0) {
byteBuffer.flip();
byte[] bytes = Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
System.out.println("获取到数据" + new String(bytes));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 为什么for在while里面
}
7.NIO的核心组件
1.通道(Channel)
通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。
2.缓冲区(Buffer)
Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。
3.选择器(Selector)
Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;
public class NIOServer {
/**
* 创建一个选择器
*/
private Selector selector;
public void initServer(int port) throws IOException {
// 获得一个ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置通道为非阻塞
serverSocketChannel.configureBlocking(false);
// 将该通道对应的ServerSocket绑定到port端口
serverSocketChannel.bind(new InetSocketAddress(port));
// 获得一个通道管理器
this.selector = Selector.open();
// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
// 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
System.out.println("服务端启动成功!");
// 轮询访问selector
while (true) {
// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
int select = selector.select(10);
if (select == 0) {
continue;
}
// 消息类型有很多种 、发送消息连接
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理
ite.remove();
// 发出一个消息 蚂蚁课堂牛逼
if (key.isAcceptable()) {// 客户端请求连接事件
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 获得和客户端连接的通道
SocketChannel channel = server.accept();
// 设置成非阻塞
channel.configureBlocking(false);
// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
channel.register(this.selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {// 获得了可读的事件
read(key);
}
}
}
}
public void read(SelectionKey key) throws IOException {
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(512);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));
channel.write(outBuffer);// 将消息回送给客户端
}
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8080);
server.listen();
}
8.Redis是如何单线程能够非常好支持并发
Redis官方没有windows版本redis,只有linux版本的reids
Redis的底层是采用nio 多路io复用机制实现对多个不同的连接(tcp)实现io的复用;能够非常好的支持高并发,同时能够先天性支持线程安全的问题。
io的复用::使用一个线程维护多个不同的io操作 原理使用nio的选择器,将多个不同的Channel统一交给我们的selector(选择器管理)
但是nio的实现在不同的操作系统上存在差别:在我们windows操作系统上使用select实现轮训机制、在linux操作系统使用epoll
备注:windows操作系统是没有epoll
在windows操作系统中使用select实现轮训机制时间复杂度是为 o(n),而且这种情况也会存在空轮训的情况,效率非常低、其次默认对我们的轮训有一定限制,所以这样的话很难支持上万tcp连接。
所以在这时候linux操作就出现epoll实现事件驱动回调形式通知,不会存在空轮训的情况,只
是对活跃的socket
实现主动回调
,这样的性能有很大的提升 所以时间复杂度为是o(1)
注意:windows操作系统没有epoll、只有linux操作系统有。
所以为什么Nginx、redis能够支持非常高的并发 最终都是靠的linux版本的 io多路复用机制epoll