Netty权威指南之NIO入门

水深无声 2023-10-16 21:48 89阅读 0赞

本章学习目标:

1、传统的同步阻塞式IO编程

2、基于NIO非阻塞式编程

3、基于NIO2.0异步非阻塞式编程

4、为什么使用NIO编程

5、为什么选择Netty

传统BIO编程

网络编程的基本模式是C/S模式,也就是两个进程之间的相互通信,其中服务端提供位置信息(绑定IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接成功,双方通过网络套接字(Socket)进行通信。

在基于传统同步阻塞开发模式中,ServerSocket负责绑定IP地址,启动监听端口:Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。

BIO通信模型图

Center

BIO演示代码

服务端程序代码:

  1. package com.nio.server;
  2. import java.io.IOException;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import com.nio.thread.ServerThread;
  6. public class TimeServer {
  7. /**
  8. * @param args
  9. */
  10. public static void main(String[] args) throws IOException {
  11. // TODO Auto-generated method stub
  12. //监听端口
  13. int port=1666;
  14. //创建ServerSocket 对象
  15. ServerSocket server=null;
  16. try{
  17. server=new ServerSocket(port);
  18. System.out.println("Serverocket启动对:"+port+"端口监听");
  19. Socket socket=null;
  20. while(true){
  21. socket=server.accept();
  22. new Thread(new ServerThread(socket)).start();
  23. }
  24. }finally{
  25. if(server!=null){
  26. System.out.println("ServerSocket 服务正在关闭");
  27. server.close();
  28. server=null;
  29. }
  30. }
  31. }
  32. }
  33. package com.nio.thread;
  34. import java.io.BufferedReader;
  35. import java.io.IOException;
  36. import java.io.InputStreamReader;
  37. import java.io.PrintWriter;
  38. import java.net.Socket;
  39. public class ServerThread implements Runnable {
  40. // 套接字
  41. private Socket socket;
  42. // 构造函数
  43. public ServerThread(Socket socket) {
  44. super();
  45. this.socket = socket;
  46. }
  47. public void run() {
  48. // TODO Auto-generated method stub
  49. BufferedReader in = null;
  50. PrintWriter out = null;
  51. try {
  52. in = new BufferedReader(new InputStreamReader(
  53. this.socket.getInputStream()));
  54. out = new PrintWriter(this.socket.getOutputStream(), true);
  55. String currentTime = null;
  56. String body = null;
  57. while (true) {
  58. body = in.readLine();
  59. if (body == null) {
  60. break;
  61. }
  62. System.out.println("this ServerSocket receiver message is:"
  63. + body);
  64. currentTime = "time".equalsIgnoreCase(body) ? new java.util.Date(
  65. System.currentTimeMillis()).toString() : "bad time";
  66. out.println("current is:" + currentTime);
  67. }
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. if (in != null) {
  71. try {
  72. in.close();
  73. in = null;
  74. } catch (IOException el) {
  75. el.printStackTrace();
  76. }
  77. }
  78. if (out != null) {
  79. out.close();
  80. out = null;
  81. }
  82. if (this.socket != null) {
  83. try {
  84. this.socket.close();
  85. } catch (IOException el) {
  86. el.printStackTrace();
  87. }
  88. this.socket = null;
  89. }
  90. }
  91. }
  92. }

客户端程序代码:

  1. package com.nio.client;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.PrintWriter;
  6. import java.net.Socket;
  7. public class TimeClient {
  8. /**
  9. * @param args
  10. */
  11. public static void main(String[] args) {
  12. // TODO Auto-generated method stub
  13. int port=1666;
  14. Socket socket=null;
  15. BufferedReader in = null;
  16. PrintWriter out = null;
  17. try{
  18. socket=new Socket("127.0.0.1",port);
  19. in = new BufferedReader(new InputStreamReader(
  20. socket.getInputStream()));
  21. out = new PrintWriter(socket.getOutputStream(), true);
  22. out.println("123");
  23. System.out.println("this client has send message");
  24. String resp=in.readLine();
  25. System.out.println("response message is:"+resp);
  26. }catch(Exception e){
  27. }finally{
  28. if (in != null) {
  29. try {
  30. in.close();
  31. in = null;
  32. } catch (IOException el) {
  33. el.printStackTrace();
  34. }
  35. }
  36. if (out != null) {
  37. out.close();
  38. out = null;
  39. }
  40. if (socket != null) {
  41. try {
  42. socket.close();
  43. } catch (IOException el) {
  44. el.printStackTrace();
  45. }
  46. socket = null;
  47. }
  48. }
  49. }
  50. }

结果展示:

客户端发送信息为:”123”

服务端返回结果为:

Center 1

客户端发送信息为:”time”

服务端返回信息为:

Center 2

第二节:伪异步IO编程

为了解决同步阻塞IO面临的一个链路需要一个线程处理的问题,后来有人对它的线程模式进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发导致线程耗尽。

伪异步IO模型图

Center 3

采用线程池和任务队列可以实现一种叫做伪异步的IO通信框架,它的模型如上图。

当有新的客户端接入的时候,将客户端的Socket封装成一个Task()该任务实现java.lang.Runnable 接口投递到后端的线程池进行处理,JDK维护一个消息队列和N个活跃的线程对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源是可控的,无论多少个客户端并发访问,都不会导致资源耗尽和宕机。

伪异步IO演示代码

服务端代码

  1. package com.nio.server;
  2. import java.io.IOException;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import com.nio.executepool.TimeServerHandlerExcutePool;
  6. import com.nio.thread.ServerThread;
  7. public class TimeServer {
  8. /**
  9. * @param args
  10. */
  11. public static void main(String[] args) throws IOException {
  12. // TODO Auto-generated method stub
  13. //监听端口
  14. int port=1667;
  15. //创建ServerSocket 对象
  16. ServerSocket server=null;
  17. try{
  18. server=new ServerSocket(port);
  19. System.out.println("Serverocket启动对:"+port+"端口监听");
  20. Socket socket=null;
  21. TimeServerHandlerExcutePool pool=new TimeServerHandlerExcutePool(50,1000);
  22. while(true){
  23. socket=server.accept();
  24. pool.execute(new ServerThread(socket));
  25. }
  26. }finally{
  27. if(server!=null){
  28. System.out.println("ServerSocket 服务正在关闭");
  29. server.close();
  30. server=null;
  31. }
  32. }
  33. }
  34. }

线程池

  1. package com.nio.executepool;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.ThreadPoolExecutor;
  5. import java.util.concurrent.TimeUnit;
  6. public class TimeServerHandlerExcutePool {
  7. private ExecutorService executor;
  8. public TimeServerHandlerExcutePool(int maxPoolSize,int queueSize) {
  9. executor=new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L,TimeUnit.SECONDS,new ArrayBlockingQueue<java.lang.Runnable>(queueSize));
  10. }
  11. public void execute(java.lang.Runnable command){
  12. executor.execute(command);
  13. }
  14. }

线程类

  1. package com.nio.thread;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.PrintWriter;
  6. import java.net.Socket;
  7. public class ServerThread implements Runnable {
  8. // 套接字
  9. private Socket socket;
  10. // 构造函数
  11. public ServerThread(Socket socket) {
  12. super();
  13. this.socket = socket;
  14. }
  15. public void run() {
  16. // TODO Auto-generated method stub
  17. BufferedReader in = null;
  18. PrintWriter out = null;
  19. try {
  20. in = new BufferedReader(new InputStreamReader(
  21. this.socket.getInputStream()));
  22. out = new PrintWriter(this.socket.getOutputStream(), true);
  23. String currentTime = null;
  24. String body = null;
  25. while (true) {
  26. body = in.readLine();
  27. if (body == null) {
  28. break;
  29. }
  30. System.out.println("this ServerSocket receiver message is:"
  31. + body);
  32. currentTime = "time".equalsIgnoreCase(body) ? new java.util.Date(
  33. System.currentTimeMillis()).toString() : "bad time";
  34. out.println("current is:" + currentTime);
  35. }
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. if (in != null) {
  39. try {
  40. in.close();
  41. in = null;
  42. } catch (IOException el) {
  43. el.printStackTrace();
  44. }
  45. }
  46. if (out != null) {
  47. out.close();
  48. out = null;
  49. }
  50. if (this.socket != null) {
  51. try {
  52. this.socket.close();
  53. } catch (IOException el) {
  54. el.printStackTrace();
  55. }
  56. this.socket = null;
  57. }
  58. }
  59. }
  60. }

客户端代码

客户端代码未做任何修改。

第三节 NIO编程

在介绍NIO编程之前,我们首先澄清一个概念:NIO到底是什么的简称?有人称之为New IO,因为它是相对之前的IO库新增的,所以称之为New IO。这是它的官方叫法。但是,由于之前的IO类库是阻塞IO,New IO类库的目标就是让IO支持非阻塞,所以,人们更喜欢称之为非阻塞IO,由于非阻塞IO更能体现NIO的特点。

与Socket和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式正好相反。一般来说低负载、低并发的应用程序可以选择阻塞式IO来降低开发的编程复杂度,但是对于高负载、高并发的网络应用,需要使用NIO非阻塞模式进行开发。

NIO演示代码

服务端代码

  1. package com.nio.server;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.ServerSocketChannel;
  8. import java.nio.channels.SocketChannel;
  9. import java.util.Iterator;
  10. /**
  11. * NIO服务端
  12. * @author 小路
  13. */
  14. public class NIOServer {
  15. //通道管理器
  16. private Selector selector;
  17. /**
  18. * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
  19. * @param port 绑定的端口号
  20. * @throws IOException
  21. */
  22. public void initServer(int port) throws IOException {
  23. // 获得一个ServerSocket通道
  24. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  25. // 设置通道为非阻塞
  26. serverChannel.configureBlocking(false);
  27. // 将该通道对应的ServerSocket绑定到port端口
  28. serverChannel.socket().bind(new InetSocketAddress(port));
  29. // 获得一个通道管理器
  30. this.selector = Selector.open();
  31. //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
  32. //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
  33. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  34. }
  35. /**
  36. * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
  37. * @throws IOException
  38. */
  39. @SuppressWarnings("unchecked")
  40. public void listen() throws IOException {
  41. System.out.println("服务端启动成功!");
  42. // 轮询访问selector
  43. while (true) {
  44. //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
  45. selector.select();
  46. // 获得selector中选中的项的迭代器,选中的项为注册的事件
  47. Iterator ite = this.selector.selectedKeys().iterator();
  48. while (ite.hasNext()) {
  49. SelectionKey key = (SelectionKey) ite.next();
  50. // 删除已选的key,以防重复处理
  51. ite.remove();
  52. // 客户端请求连接事件
  53. if (key.isAcceptable()) {
  54. ServerSocketChannel server = (ServerSocketChannel) key
  55. .channel();
  56. // 获得和客户端连接的通道
  57. SocketChannel channel = server.accept();
  58. // 设置成非阻塞
  59. channel.configureBlocking(false);
  60. //在这里可以给客户端发送信息哦
  61. channel.write(ByteBuffer.wrap(new String("server").getBytes()));
  62. //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
  63. channel.register(this.selector, SelectionKey.OP_READ);
  64. // 获得了可读的事件
  65. } else if (key.isReadable()) {
  66. read(key);
  67. }
  68. }
  69. }
  70. }
  71. /**
  72. * 处理读取客户端发来的信息 的事件
  73. * @param key
  74. * @throws IOException
  75. */
  76. public void read(SelectionKey key) throws IOException{
  77. // 服务器可读取消息:得到事件发生的Socket通道
  78. SocketChannel channel = (SocketChannel) key.channel();
  79. // 创建读取的缓冲区
  80. ByteBuffer buffer = ByteBuffer.allocate(10);
  81. channel.read(buffer);
  82. byte[] data = buffer.array();
  83. String msg = new String(data).trim();
  84. System.out.println("服务端收到信息:"+msg);
  85. ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
  86. channel.write(outBuffer);// 将消息回送给客户端
  87. }
  88. /**
  89. * 启动服务端测试
  90. * @throws IOException
  91. */
  92. public static void main(String[] args) throws IOException {
  93. NIOServer server = new NIOServer();
  94. server.initServer(8000);
  95. server.listen();
  96. }
  97. }

客户端代码

  1. package com.nio.client;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.SocketChannel;
  8. import java.util.Iterator;
  9. /**
  10. * NIO客户端
  11. * @author 小路
  12. */
  13. public class NIOClient {
  14. //通道管理器
  15. private Selector selector;
  16. /**
  17. * 获得一个Socket通道,并对该通道做一些初始化的工作
  18. * @param ip 连接的服务器的ip
  19. * @param port 连接的服务器的端口号
  20. * @throws IOException
  21. */
  22. public void initClient(String ip,int port) throws IOException {
  23. // 获得一个Socket通道
  24. SocketChannel channel = SocketChannel.open();
  25. // 设置通道为非阻塞
  26. channel.configureBlocking(false);
  27. // 获得一个通道管理器
  28. this.selector = Selector.open();
  29. // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
  30. //用channel.finishConnect();才能完成连接
  31. channel.connect(new InetSocketAddress(ip,port));
  32. //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
  33. channel.register(selector, SelectionKey.OP_CONNECT);
  34. }
  35. /**
  36. * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
  37. * @throws IOException
  38. */
  39. @SuppressWarnings("unchecked")
  40. public void listen() throws IOException {
  41. // 轮询访问selector
  42. while (true) {
  43. selector.select();
  44. // 获得selector中选中的项的迭代器
  45. Iterator ite = this.selector.selectedKeys().iterator();
  46. while (ite.hasNext()) {
  47. SelectionKey key = (SelectionKey) ite.next();
  48. // 删除已选的key,以防重复处理
  49. ite.remove();
  50. // 连接事件发生
  51. if (key.isConnectable()) {
  52. SocketChannel channel = (SocketChannel) key
  53. .channel();
  54. // 如果正在连接,则完成连接
  55. if(channel.isConnectionPending()){
  56. channel.finishConnect();
  57. }
  58. // 设置成非阻塞
  59. channel.configureBlocking(false);
  60. //在这里可以给服务端发送信息哦
  61. channel.write(ByteBuffer.wrap(new String("client").getBytes()));
  62. //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
  63. channel.register(this.selector, SelectionKey.OP_READ);
  64. // 获得了可读的事件
  65. } else if (key.isReadable()) {
  66. read(key);
  67. }
  68. }
  69. }
  70. }
  71. /**
  72. * 处理读取服务端发来的信息 的事件
  73. * @param key
  74. * @throws IOException
  75. */
  76. public void read(SelectionKey key) throws IOException{
  77. // 客户端可读取消息:得到事件发生的Socket通道
  78. SocketChannel channel = (SocketChannel) key.channel();
  79. // 创建读取的缓冲区
  80. ByteBuffer buffer = ByteBuffer.allocate(10);
  81. channel.read(buffer);
  82. byte[] data = buffer.array();
  83. String msg = new String(data).trim();
  84. System.out.println("客户端收到信息:"+msg);
  85. ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
  86. channel.write(outBuffer);// 将消息回送给服务器
  87. }
  88. /**
  89. * 启动客户端测试
  90. * @throws IOException
  91. */
  92. public static void main(String[] args) throws IOException {
  93. NIOClient client = new NIOClient();
  94. client.initClient("localhost",8000);
  95. client.listen();
  96. }
  97. }

发表评论

表情:
评论列表 (有 0 条评论,89人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Netty权威指南文件传输

    本章相关知识点: 文件是最常见的数据源之一,在程序经常需要将数据存储到文件中,比如:图片文件、声音文件等数据文件。在实际使用中,文件都包含一个特定的格式,这个格式需要程序员根

    相关 Netty权威指南AIO编程

    由JDK1.7提供的NIO2.0新增了异步的套接字通道,它是真正的异步I/O,在异步I/O操作的时候可以传递信号变量,当操作完成后会回调相关的方法,异步I/o也被称为AIO,对