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. JS模态框 简单案例

    演示: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8 ...

  2. Spark数据分析-记录关联问题

    1. 问题描述 记录关联问题(Record Linkage):有大量从一个或多个源系统来的记录,其中有些记录可能代表了相同的基础实体. 每个实体有若干个属性,比如姓名.地址.生日.我们需要根据这些属性 ...

  3. 安卓MVP框架

    一.理解MVP 原文地址 我的Demo 效果图: 项目结构: 实现 一.Model层 首先定义一个实体类User package app.qcu.pmit.cn.mvpdemo.model; /** ...

  4. Android之Fragment(碎片)方方面面

    Fragment简介碎片(Fragment)是一种可以嵌入到活动当中的UI片段,它能让程序更加合理和充分的利用大屏幕的空间. Fragment的生命周期 它与Activity生命周期的关系: 可以看到 ...

  5. 『流畅的Python』第15章:上下文管理器和else块

  6. 修改PL/ORACLE字符编码集

    一.pl/sql developer 中文字段显示乱码 原因:因为数据库的编号格式和pl /sql developer的编码格式不统一造成的. 二.查看和修改oracle数据库字符集: select ...

  7. zzw原_环境变量导致date命令显示处理一例

    1.显示时间看不懂 [root@localhost ~]# date201790:02:06 CST 2.注释掉环境变量bash_profile的两行配置 [root@localhost ~]# vi ...

  8. netty源码分析之一:server的启动

    nio server启动的第一步,都是要创建一个serverSocketChannel,我截取一段启动代码,一步步分析: public void afterPropertiesSet() throws ...

  9. elinks快捷方式

    突然有兴趣看看Linux下的字符模式的浏览器,搜了下有好几个,在Ubuntu里“添加/删除”里找到一个,叫Elinks,安装,然后在终端运行elinks,试用了一下,不错,使用方法也挺简单的,支持多标 ...

  10. webpack配置接口路径

    比如在webpack.config.js中的plugins中加入 new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(' ...