BIO就是我们传统意义上的Inputstream,Outputstream。这个东西是同步阻塞的,效率不高,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO 随着分布式的如火如荼,为了提高访问效率,引进了NIO,同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
同时这个东西读取或者写出数据的时候不是直接操作channel的,他是操作buffer内存的。这个东西写起来很麻烦,从而诞生了Netty来封装NIO,像一些市面上牛逼的中间件用的都是这东西。比如zk.dubbo等等。。。。
好了说了这么多上代码吧,这东西到底是咋玩的呢?
////////BIO代码服务端和客户端
public class BIOClient {
public static void main(String[] args) throws UnknownHostException, IOException {
//要和谁进行通信,服务器IP、服务器的端口
//一台机器的端口号是有限
Socket client = new Socket("localhost", 8080);
//输出 O write();
//不管是客户端还是服务端,都有可能write和read
OutputStream os = client.getOutputStream();
//生成一个随机的ID
String name = UUID.randomUUID().toString();
System.out.println("客户端发送数据:" + name);
//传说中的101011010
os.write(name.getBytes());
os.close();
client.close();
}
public class BIOServer {
//服务端网络IO模型的封装对象
ServerSocket server;
//服务器
public BIOServer(int port){
try {
//Tomcat 默认端口8080
//只要是Java写的都这么玩,3306
//Redis 6379
//Zookeeper 2181
server = new ServerSocket(port);
System.out.println("BIO服务已启动,监听端口是:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 开始监听,并处理逻辑
* @throws IOException
*/
public void listen() throws IOException{
//循环监听
while(true){
//等待客户端连接,阻塞方法
//Socket数据发送者在服务端的引用
Socket client = server.accept();
System.out.println(client.getPort());
//对方法数据给我了,读 Input
InputStream is = client.getInputStream();
//网络客户端把数据发送到网卡,机器所得到的数据读到了JVM内中
byte [] buff = new byte[1024];
int len = is.read(buff);
if(len > 0){
String msg = new String(buff,0,len);
System.out.println("收到" + msg);
}
}
}
public static void main(String[] args) throws IOException {
new BIOServer(8080).listen();
}
////////////////////////////////////NIO代码演示 客户端可以上BIO的客户端拉起来直接跑
public class NIOServerDemo {
private int port = 8080;
//准备两个东西
//轮询器 Selector
private Selector selector;
//缓冲区 Buffer
private ByteBuffer buffer = ByteBuffer.allocate(1024);
//初始化完毕
public NIOServerDemo(int port){
//初始化Selector
try {
this.port = port;
ServerSocketChannel server = ServerSocketChannel.open();
//我得告诉地址
//IP/Port
server.bind(new InetSocketAddress(this.port));
//BIO 升级版本 NIO,为了兼容BIO,NIO模型默认是采用阻塞式
server.configureBlocking(false);
//Selector准备干活
selector = Selector.open();
//注册连接事件到selector
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
public void listen(){
System.out.println("listen on " + this.port + ".");
try {
//轮询主线程
while (true){
selector.select();
//每次都拿到所有的号子
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
//不断地迭代,就叫轮询
//同步体现在这里,因为每次只能拿一个key,每次只能处理一种状态
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
//每一个key代表一种状态
//没一个号对应一个业务
//数据就绪、数据可读、数据可写 等等等等
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//每一次轮询就是调用一次process方法,而每一次调用,只能干一件事
//在同一时间点,只能干一件事
private void process(SelectionKey key) throws IOException {
//针对于每一种状态给一个反应
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//这个方法体现非阻塞,不管你数据有没有准备好
//你给我一个状态和反馈
SocketChannel channel = server.accept();
//一定一定要记得设置为非阻塞
channel.configureBlocking(false);
//当数据准备就绪的时候,将状态改为可读
key = channel.register(selector,SelectionKey.OP_READ);
}
else if(key.isReadable()){
//key.channel 从多路复用器中拿到客户端的引用
SocketChannel channel = (SocketChannel)key.channel();
int len = channel.read(buffer);
if(len > 0){
buffer.flip();
String content = new String(buffer.array(),0,len);
key = channel.register(selector,SelectionKey.OP_WRITE);
//在key上携带一个附件,一会再写出去
key.attach(content);
System.out.println("读取内容:" + content);
}
}
else if(key.isWritable()){
SocketChannel channel = (SocketChannel)key.channel();
String content = (String)key.attachment();
channel.write(ByteBuffer.wrap(("输出:" + content).getBytes()));
channel.close();
}
}
public static void main(String[] args) {
new NIOServerDemo(8080).listen();
}
来源:CSDN
作者:菜鸟张工
链接:https://blog.csdn.net/weixin_41758726/article/details/104595793