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. guxh的python笔记二:函数基础

    1,函数的参数 1.1,查看函数的参数类型 def run(a, *args, b, **kwargs): return a + b 可以通过如下方式查看参数类型: import inspect k ...

  2. Python自学:第三章 使用列表中的各个值

    bicycles = ['trek','cannondale','redline','specialized'] message = "My first bicycle was a &quo ...

  3. 【分布式搜索引擎】Elasticsearch中的基本概念

    一.Elasticsearch中的基本概念 以下概念基于这个例子:存储员工数据,每个文档代表一个员工 1)索引(index)  在Elasticsearch中存储数据的行为就叫做索引(indexing ...

  4. linux软件管理之概述

    软件包管理 ====================================================================================安装/查询/卸载 一 ...

  5. JWT ajax java spingmvc 简洁教程

    1.添加依赖 <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</ ...

  6. 填写数独 洛谷P1784

    题目链接:https://www.luogu.org/problemnew/show/P1784 因为要求行列以及每9个数字组成的中格子都得有1-9这9个数,我们不妨建三个二维数组 第一维代表是第几个 ...

  7. 有关两个jar包中包含完全相同的包名和类名的加载问题

    首先从表现层介绍,后续后深入原理. 1,先简单介绍maven如何生成jar文件方便测试 <plugin> <artifactId>maven-assembly-plugin&l ...

  8. linux系统下如何挂载NTFS移动硬盘

    前言 数据迁移是我们经常会遇到的,有时候做大数据量迁移时,为了快速迁移大数据,有可能在Linux服务器上临时挂载NTFS格式的移动硬盘, 一般情况下,Linux是识别不了NTFS格式移动硬盘的(需要重 ...

  9. 解决import caffe 时no module named protobuf的报错

    ProtoBuf是Google开发的可以实现内存与非易失存储介质(硬盘文件等等)交换时的协议接口.Caffe源码中大量使用了ProtoBuf作为权值和模型参数的载体. 在Anaconda下打开Anac ...

  10. nltk中的三元词组,二元词组

    在做英文文本处理时,常常会遇到这样的情况,需要我们提取出里面的词组进行主题抽取,尤其是具有行业特色的,比如金融年报等.其中主要进行的是进行双连词和三连词的抽取,那如何进行双连词和三连词的抽取呢?这是本 ...