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(); } } }