nio 服务器设计需求文档,NIO的设计思路

NIO的由来

为啥要有NIO呢,因为以前的IO用起来不爽呗。在JDK1.4之前的版本用的都是BIO,没错,就是阻塞的IO,阻塞的原因在于Java的InputStream,OutputStream本身就是阻塞的,为了解决这种缺陷,从JDK1.4开始引入Buffer解决了阻塞问题,多个输入请求还要通过Selector来注册管理,通过Channel来将请求数据写入到Buffer,有了这三样东西,NIO的雏形就形成了。

BIO的实现

BIO的实现是一个同步阻塞模型,一个客户端连接对应一个处理线程。

c3205d53f323

image.png

为了说明BIO的问题,我们先看他的一个基本实现。

BIO客户端实现:

package com.david.bio;

import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.nio.charset.Charset;

import java.util.Scanner;

public class BIOClient implements Runnable {

private int port;

private Charset charset = Charset.forName("UTF-8");

private String host;

public BIOClient(String host, int port) {

super();

this.host = host;

this.port = port;

}

@Override

public void run() {

//建立Socket连接,获取输出流,完成后自动释放

try (Socket socket = new Socket(host, port);

OutputStream out = socket.getOutputStream();){

//从控制台获取输入的消息

Scanner scanner = new Scanner(System.in);

System.out.println("Please Input Message");

String strMsg = scanner.nextLine();

out.write(strMsg.getBytes(charset));

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

BIOClient client = new BIOClient("localhost", 8090);

client.run();

}

}

BIO服务器端实现:

package com.david.bio;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.net.ServerSocket;

import java.net.Socket;

import java.nio.charset.Charset;

public class BIOServer {

private static Charset charset = Charset.forName("UTF-8");

private static final int port = 8090;

public static void main(String[] args) {

//建立一个port上的Sokcet连接,结束后自动释放ServerSocket

try (ServerSocket serverSocket = new ServerSocket(port);) {

while (true) {

Socket socket = serverSocket.accept(); //监听socket连接,这个方法是阻塞的,直到连接建立

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), charset));

//这里也是阻塞的,没有读到客户端输入就一直阻塞着

String receiveMsg = null;

while ((receiveMsg = bufferedReader.readLine()) != null) {

System.out.println(receiveMsg);

}

socket.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

我们看到了BIO服务端里面accept方法和read方法都是阻塞的,就是如果没有连接请求,accept就一直阻塞着,如果没有数据可读取,read方法阻塞。

我们启动下服务器端,再启动客户端,结果看到:

c3205d53f323

image.png

这个时候说明客户端和服务器端的连接已经建立了。

这里有一个问题,多个Client启动的时候,后面启动的一定要等前面的Client输入完成Server读取到数据后才能处理下一个Client的输入,像下面这种效果。

c3205d53f323

image.png

c3205d53f323

image.png

c3205d53f323

image.png

这个问题好解决,我们多起来几个线程接受Socket连接就可以了,这样我们的服务器代码改造成这样了:

package com.david.bio;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.net.ServerSocket;

import java.net.Socket;

import java.nio.charset.Charset;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class BIOServerMultiThread {

private static Charset charset = Charset.forName("UTF-8");

private static ExecutorService executorService = Executors.newFixedThreadPool(3);

public static void main(String[] args) {

int port = 8090;

try (ServerSocket serverSocket = new ServerSocket(port);) {

while (true) {

Socket s = serverSocket.accept();

executorService.submit(new SocketProcess(s));

//new Thread(new SocketProcess(s));

}

} catch (IOException e) {

e.printStackTrace();

}

}

static class SocketProcess implements Runnable {

Socket s;

public SocketProcess(Socket socket) {

this.s = socket;

}

@Override

public void run() {

try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(s.getInputStream(), charset));)

{

//这里也是阻塞的,没有读到客户端输入就一直阻塞着

String receiveMsg = null;

while ((receiveMsg = bufferedReader.readLine()) != null) {

System.out.println(receiveMsg);

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

这个时候我们再分别启动服务器和客户端,输入后,结果是这样:

c3205d53f323

image.png

c3205d53f323

image.png

c3205d53f323

image.png

c3205d53f323

image.png

到这里,阻塞的问题似乎解决了。可是如果来的输入特别多的话,仍然会有资源竞争,这个时候会出现排队太久的问题,于是我们的NIO就诞生了。

NIO的实现

NIO和上面的实现最大的改进在于去掉了InputStream的阻塞,它的设计大致是这样的:

c3205d53f323

image.png

我们可以看到NIO主要由三大组件组成:

Buffer:用于存储数据,底层基于数组实现,针对8种基本类型提供了缓冲区。

Channel:用于进行数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从

Selector:选择器,主要是用来注册Channel和记录(监听)Channel状态的。当某个Channel上面发生了读或者写事件,Channel就处于就绪状态,会被Selector监听到,然后通过SelectionKeys就可以获取就绪状态的Channel集合,进行后续的操作了。

NIO的服务器端实现:

package com.david.nio;

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.nio.charset.Charset;

import java.nio.charset.CharsetDecoder;

import java.util.Iterator;

import java.util.Set;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class NIOServer {

private static Charset charset = Charset.forName("UTF-8");

private static CharsetDecoder cd = charset.newDecoder();

private static final int port = 8090;

private static ExecutorService executorService = Executors.newFixedThreadPool(3);

public static void main(String[] args) throws IOException{

//创建一个slector

Selector selector = Selector.open();

ServerSocketChannel channel = ServerSocketChannel.open();

channel.bind(new InetSocketAddress(port));

//需要设置成非阻塞

channel.configureBlocking(false);

channel.register(selector, SelectionKey.OP_ACCEPT);

int connectCount = 0;

//少量的线程

int threads = 3;

while (true) {

int readyChannelCount = selector.select();

if(readyChannelCount == 0) continue;

Set selectionKeys = selector.selectedKeys();

Iterator keyInterator = selectionKeys.iterator();

while (keyInterator.hasNext()) {

SelectionKey key = keyInterator.next();

if (key.isAcceptable()) {

ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();

SocketChannel sc = serverSocketChannel.accept();

sc.configureBlocking(false);

sc.register(selector, SelectionKey.OP_READ);

} else if (key.isReadable()) {

executorService.execute(new SocketProcess(key));

key.channel();

} else if (key.isWritable()) {

} else if (key.isConnectable()) {

}

keyInterator.remove();

}

}

}

static class SocketProcess implements Runnable {

SelectionKey key;

public SocketProcess(SelectionKey key) {

this.key = key;

}

@Override

public void run() {

try

{

System.out.println("connet" + key.attachment() + "send msg:" + readFromChannel());

key.channel().close(); //不需要的时候要关闭channel

} catch (IOException e) {

e.printStackTrace();

}

}

private String readFromChannel() throws IOException {

SocketChannel sc = (SocketChannel)key.channel();

int bufferSize = 1024;

ByteBuffer bf = ByteBuffer.allocateDirect(bufferSize);

//定义一个更大的buffer

ByteBuffer biggerBf = null;

int count = 0; //读的次数统计

while ((sc.read(bf)) != -1) {

count++;

ByteBuffer temp = ByteBuffer.allocateDirect(bufferSize * (count + 1));

//这里将Buffer由写转为读模式

if (biggerBf != null) {

biggerBf.flip();

temp.put(biggerBf);

}

biggerBf = temp;

//将这次独到的放入大的buffer里面

bf.flip();

biggerBf.put(bf);

bf.clear(); //为了下次读需要清理buffer

}

if(biggerBf != null) {

biggerBf.flip();

try {

return cd.decode(biggerBf).toString();

} catch (Exception e) {

e.printStackTrace();

}

}

return null;

}

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值