BIO

BIO:blocking IO,分别写一个服务端和客户端交互的C/S实例。
服务器端:
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; /**
* Created by atai on 2019/3/19.
*/
public class BIOServer { private String host; private int port; private static Charset charset = Charset.forName("UTF-8"); public static void main(String[] args) {
int port = 9010;
try (ServerSocket ss = new ServerSocket(port)) {
while (true) {
Socket s = ss.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream(), charset)); String mess = null;
while ((mess = reader.readLine()) != null) {
System.out.println(mess);
}
s.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

客户端:

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Scanner; /**
* Created by atai on 2019/3/19.
*/
public class BIOClient implements Runnable { private String host; private int port; private Charset charset = Charset.forName("UTF-8"); public BIOClient(String host, int port) {
super();
this.host = host;
this.port = port;
} @Override
public void run() {
try (Socket s = new Socket(host, port); OutputStream out = s.getOutputStream();) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
String mess = scanner.nextLine();
out.write(mess.getBytes(charset));
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
BIOClient client = new BIOClient("localhost", 9010);
client.run();
}
}

启动时,记得先启动服务器代码,才能正常启动客户端代码,不然客户端会报连接异常(不存在可用端口号)。

上面的服务器端代码每次只能同时受理一个客户端请求,其他客户端此时只能等待,为了让服务端支持处理多个客户端请求,可以改造成多线程形式:

public class BIOServerV2 {

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

    public static void main(String[] args) {
int port = 9010;
try (ServerSocket ss = new ServerSocket(port)) {
while (true) {
new Thread(new SocketProcess(ss.accept())).start();
}
} catch (IOException e) {
e.printStackTrace();
}
} static class SocketProcess implements Runnable { Socket s; public SocketProcess(Socket s) {
super();
this.s = s;
} @Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream(), charset))) {
String mess = null;
while ((mess = reader.readLine()) != null) {
System.out.println(mess);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

1、理解什么是阻塞
2、思考:阻塞对服务端有什么影响?
3、阻塞时,服务端什么也干不了,不能处理其他客户端的连接,如何改进?
4、多线程
5、如果并发请求量很大,比如一万、十万,会有什么问题?
6、32位系统1个线程对象默认最大需要320KB内存,64位系统默认最大需要1M内存,业务对象也需要内存,内存会不足。过多的线程需要OS频繁切换,也会大大影响性能。
7、怎么办?
8、线程池

既然使用线程池可以避免频繁创建、销毁、切换线程,那就写一个使用线程池的服务端实现:

public class BIOServerV3 {

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

    public static void main(String[] args) {
int port = 9010;
int threads = 100;
ExecutorService tpool = Executors.newFixedThreadPool(threads); try (ServerSocket ss = new ServerSocket(port)) {
while (true) {
Socket s = ss.accept();
// 丢到线程池中执行
tpool.execute(new SocketProcess(s));
}
} catch (Exception e) {
e.printStackTrace();
}
tpool.shutdown();
} static class SocketProcess implements Runnable { Socket s; public SocketProcess(Socket s) {
super();
this.s = s;
} @Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream(), charset))) {
String mess = null;
while ((mess = reader.readLine()) != null) {
System.out.println(mess);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

9、阻塞对线程池的方式有什么影响?
10、阻塞等待接收客户端的数据时,这段时间占着线程,而池中线程数是有限的,并发量大时,将导致没有线程处理请求,请求的响应时间长,甚至拒绝服务。
11、如果能不阻塞,在没有数据时,就去干点别的事情,有数据了才处理数据那该多好。
这个时候,终于等到NIO闪亮登场。

NIO

NIO:new IO,java1.4开始推出的可非阻塞IO,在java.io包中。特点如下:
1、可解决BIO阻塞的不足;
2、但比BIO学习、使用复杂;
3、可以以阻塞、非阻塞两种方式工作;
4、在非阻塞模式下,可以用少量(甚至一个)线程处理大量的IO连接;
5、Java7推出了NIO.2(又称AIO,即异步IO)

Select选择器:非阻塞模式下,一个选择器可检测多个SelectableChannel,获得为读写等操作准备好的通道,就不需要我们用循环去判断了。

Selector的用法:
1、创建Selector

Selector selector = new Selector.open();

2、将要交给Selector检测的SelectableChannel注册进来

channel.configureBlocking(false); // 注意:一定要设为非阻塞模式
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

channel.register方法的第二个参数指定要selector帮忙监听的就绪操作:

SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

3、通过Selector来选择就绪的Channel,有三个select方法

int select()             // 阻塞直到有就绪的Channel
int select(long timeout) // 阻塞最长多久
int selectNow() // 不阻塞这

这三个方法返回值:就绪的Channel数量

int n = selector.select();

注意:select()方法返回当前的就绪数量。

4、获得就绪的SelectionKey集合(当有就绪的Channel时)

Set<SelectionKey> selectedKey = selector.selectedKeys();

5、处理selectedKeys set(详见后面的服务端代码)

Channel通道:数据的来源或去向目标

1、Channel的实现

  FileChannel(只能用于BIO)
  DatagramChannel
       SocketChannel
       SocketChannel
       ServerSocketChannel

2、各Channel的API方法

open():创建通道
       read(Buffer):从通道中读取数据放入到buffer
       write(Buffer):将buffer中的数据写给通道

Buffer缓冲区,数据的临时存放区

ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer等

Buffer的基本使用步骤:
1、调用xxxBuffer.allocate(int)创建Buffer

2、调用put方法往Buffer中写数据

3、调用buffer.flip()将buffer转为读模式

4、读取buffer中的数据

5、清理数据buffer.clear(),整理数据buffer.compact()

Buffer的三个重要属性capacity、position、limit

以下是NIO代码的具体实例。

服务器端:

public class NioServer {

    private static Charset charset = Charset.forName("UTF-8");
private static CharsetDecoder decoder = charset.newDecoder(); public static void main(String[] args) throws IOException {
// 创建一个selector
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
int port = 9200;
ssc.bind(new InetSocketAddress(port)); // 2注册到selector
// 设置非阻塞
ssc.configureBlocking(false);
// ssc向selector注册,监听连接到来
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 连接的计数
int connectionCount = 0;
// 极少量线程
int threads = 3;
ExecutorService tpool = Executors.newFixedThreadPool(threads); while (true) {
// 阻塞等待就绪的事件
int readyChannelCount = selector.select();
if (readyChannelCount == 0) {
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
ServerSocketChannel ssssc = (ServerSocketChannel) key.channel();
// 接收连接
SocketChannel cc = ssssc.accept(); // 请selectoror帮忙监测数据到了没
cc.configureBlocking(false);
// 向selector注册
cc.register(selector , SelectionKey.OP_READ, ++connectionCount);
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
// 交给线程池去处理数据读
tpool.execute(new SocketProcess(key));
// 取消Selector注册,防止线程池处理不及时,重复选择
key.cancel();
} else if (key.isWritable()) {
// a channel is ready for writing
}
// 处理后,一定要从selectedKey集合中移除
keyIterator.remove();
}
}
} static class SocketProcess implements Runnable { SelectionKey key; public SocketProcess(SelectionKey key) {
super();
this.key = key;
} @Override
public void run() {
try {
System.out.println("连接" + key.attachment() + " 发来了:" + readFromChannel());
key.channel().close();
} catch (Exception e) {
e.printStackTrace();
}
} private String readFromChannel() throws IOException {
SocketChannel sc = (SocketChannel) key.channel(); int bfsize = 1024;
ByteBuffer rbf = ByteBuffer.allocateDirect(bfsize); // 定义一个更大的buffer
ByteBuffer bigBf = null; // 读的次数
int count = 0;
while ((sc.read(rbf) != -1)) {
count++;
ByteBuffer temp = ByteBuffer.allocateDirect(bfsize * (count + 1));
if (bigBf != null) {
// 将buffer由写转为读模式
bigBf.flip();
temp.put(bigBf);
}
bigBf = temp;
// 将这次读到的数据放入大buffer
rbf.flip();
bigBf.put(rbf);
// 为了下次读,清理Buffer
rbf.clear();
} if (bigBf != null) {
bigBf.flip();
try {
// 将字节转为字符,返回接收到的字符串
return decoder.decode(bigBf).toString();
} catch (CharacterCodingException e) {
e.printStackTrace();
}
} return null;
}
}
}

客户端:

public class NioClient {

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

    public static void main(String[] args) {
try (SocketChannel sc = SocketChannel.open()) {
boolean connected = sc.connect(new InetSocketAddress("localhost", 9200));
System.out.println("connected=" + connected);
// 写
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
String mess = scanner.nextLine();
ByteBuffer bf = ByteBuffer.wrap(mess.getBytes(charset)); while (bf.hasRemaining()) {
int writedCount = sc.write(bf);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

BIO、NIO实战的更多相关文章

  1. IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)

    有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...

  2. (转)也谈BIO | NIO | AIO (Java版)

    原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...

  3. Tomcat Connector三种运行模式(BIO, NIO, APR)的比较和优化

    Tomcat Connector的三种不同的运行模式性能相差很大,有人测试过的结果如下: 这三种模式的不同之处如下: BIO: 一个线程处理一个请求.缺点:并发量高时,线程数较多,浪费资源. Tomc ...

  4. 拿搬东西来解释udp tcpip bio nio aio aio异步

     [群主]雷欧纳德简单理解 tcpip是有通信确认的面对面通信   有打招呼的过程  有建立通道的过程 有保持通道的确认    有具体传输udp是看到对面的人好像在对面等你 就往对面扔东西[群主]雷欧 ...

  5. 也谈BIO | NIO | AIO (Java版--转)

    关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  6. tomcat bio nio apr 模式性能测试

    转自:tomcat bio nio apr 模式性能测试与个人看法 11.11活动当天,服务器负载过大,导致部分页面出现了不可访问的状态.那后来主管就要求调优了,下面是tomcat bio.nio.a ...

  7. Netty5序章之BIO NIO AIO演变

    Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...

  8. I/O模型系列之三:IO通信模型BIO NIO AIO

    一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...

  9. BIO,NIO与AIO的区别

    Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理.Java AIO(NIO.2 ...

  10. 【netty】(1)---BIO NIO AIO演变

    BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...

随机推荐

  1. CSS实现输入框宽度随内容自适应效果

    有时候我们会遇到如下需求:输入框的宽度随内容长度自适应,当输入框宽度增大到一定值时,里边的内容自动隐藏. 面对这种需求,我们首先想到的是使用input元素标签,但是发现input标签的宽度默认设定的是 ...

  2. 利用python操作excel

    https://zhuanlan.zhihu.com/p/51292549 打开程序:https://segmentfault.com/q/1010000002441500

  3. 无头浏览器phantomJS

    selenium: 有头浏览器的代表(selenium+python也可实现静默运行 引入python的一个包,包叫:虚拟屏幕pyvirtualdisplay) PhantomJS : 无头浏览器的代 ...

  4. 雷林鹏分享:jQuery EasyUI 数据网格 - 合并单元格

    jQuery EasyUI 数据网格 - 合并单元格 数据网格(datagrid)经常需要合并一些单元格.本教程将向您展示如何在数据网格(datagrid)中合并单元格. 为了合并数据网格(datag ...

  5. 文献导读 | A Pan-Cancer Analysis of Enhancer Expression in Nearly 9000 Patient Samples

    Chen, H., Li, C., Peng, X., Zhou, Z., Weinstein, J.N., Liang, H. and Cancer Genome Atlas Research Ne ...

  6. 『PyTorch』第十五弹_torch.nn.Module的属性设置&查询

    一.背景知识 python中两个属相相关方法 result = obj.name 会调用builtin函数getattr(obj,'name')查找对应属性,如果没有name属性则调用obj.__ge ...

  7. Container&injection

    容器(Container)就是组件和底层服务细节之间的接口.在web组件.企业级Bean等能够执行之前,它必须被装配为一个JavaEE模块,并部署在容器上. 在JavaEE5时代通过注解的方式注入(i ...

  8. MQTT简介

    MQTT简介 MQTT是IBM开发的一个即时通讯协议,该协议支持所有的平台,几乎可以把所有联网的物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议 MQTT的特 ...

  9. vue2.0 实现富文本编辑器功能

    前端富文本编译器使用总结: UEditor:百度前端的开源项目,功能强大,基于 jQuery,但已经没有再维护,而且限定了后端代码,修改起来比较费劲 bootstrap-wysiwyg:微型,易用,小 ...

  10. decltype typename

    decltype((variable))总是引用类型,但是decltype(variable)只有当variable是引用类型时才是引用类型. #include <iostream> #i ...