Netty权威指南之NIO入门

来源:互联网 发布:js游戏脚本 编辑:程序博客网 时间:2024/06/05 15:38

本章学习目标:

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

2、基于NIO非阻塞式编程

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

4、为什么使用NIO编程

5、为什么选择Netty


传统BIO编程

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

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


BIO通信模型图


BIO演示代码

服务端程序代码:

package com.nio.server;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import com.nio.thread.ServerThread;public class TimeServer {/** * @param args */public static void main(String[] args) throws IOException {// TODO Auto-generated method stub//监听端口int port=1666;//创建ServerSocket 对象ServerSocket server=null;try{server=new ServerSocket(port);System.out.println("Serverocket启动对:"+port+"端口监听");Socket socket=null;while(true){socket=server.accept();new Thread(new ServerThread(socket)).start();}}finally{if(server!=null){System.out.println("ServerSocket 服务正在关闭");server.close();server=null;}}}}
package com.nio.thread;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class ServerThread implements Runnable {// 套接字private Socket socket;// 构造函数public ServerThread(Socket socket) {super();this.socket = socket;}public void run() {// TODO Auto-generated method stubBufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String currentTime = null;String body = null;while (true) {body = in.readLine();if (body == null) {break;}System.out.println("this ServerSocket receiver message is:"+ body);currentTime = "time".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "bad time";out.println("current is:" + currentTime);}} catch (Exception e) {e.printStackTrace();if (in != null) {try {in.close();in = null;} catch (IOException el) {el.printStackTrace();}}if (out != null) {out.close();out = null;}if (this.socket != null) {try {this.socket.close();} catch (IOException el) {el.printStackTrace();}this.socket = null;}}}}



客户端程序代码:

package com.nio.client;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class TimeClient {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubint port=1666;Socket socket=null;BufferedReader in = null;PrintWriter out = null;try{socket=new Socket("127.0.0.1",port);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println("123");System.out.println("this client has send message");String resp=in.readLine();System.out.println("response message is:"+resp);}catch(Exception e){}finally{if (in != null) {try {in.close();in = null;} catch (IOException el) {el.printStackTrace();}}if (out != null) {out.close();out = null;}if (socket != null) {try {socket.close();} catch (IOException el) {el.printStackTrace();}socket = null;}}}}

结果展示:

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

服务端返回结果为:



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

服务端返回信息为:



第二节:伪异步IO编程

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


伪异步IO模型图


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

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


伪异步IO演示代码

服务端代码

package com.nio.server;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import com.nio.executepool.TimeServerHandlerExcutePool;import com.nio.thread.ServerThread;public class TimeServer {/** * @param args */public static void main(String[] args) throws IOException {// TODO Auto-generated method stub//监听端口int port=1667;//创建ServerSocket 对象ServerSocket server=null;try{server=new ServerSocket(port);System.out.println("Serverocket启动对:"+port+"端口监听");Socket socket=null;TimeServerHandlerExcutePool pool=new TimeServerHandlerExcutePool(50,1000);while(true){socket=server.accept();pool.execute(new ServerThread(socket));}}finally{if(server!=null){System.out.println("ServerSocket 服务正在关闭");server.close();server=null;}}}}

线程池

package com.nio.executepool;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class TimeServerHandlerExcutePool {private ExecutorService executor;public TimeServerHandlerExcutePool(int maxPoolSize,int queueSize) {executor=new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L,TimeUnit.SECONDS,new ArrayBlockingQueue<java.lang.Runnable>(queueSize));}public void execute(java.lang.Runnable command){executor.execute(command);}}
线程类

package com.nio.thread;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class ServerThread implements Runnable {// 套接字private Socket socket;// 构造函数public ServerThread(Socket socket) {super();this.socket = socket;}public void run() {// TODO Auto-generated method stubBufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String currentTime = null;String body = null;while (true) {body = in.readLine();if (body == null) {break;}System.out.println("this ServerSocket receiver message is:"+ body);currentTime = "time".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "bad time";out.println("current is:" + currentTime);}} catch (Exception e) {e.printStackTrace();if (in != null) {try {in.close();in = null;} catch (IOException el) {el.printStackTrace();}}if (out != null) {out.close();out = null;}if (this.socket != null) {try {this.socket.close();} catch (IOException el) {el.printStackTrace();}this.socket = null;}}}}



客户端代码

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


第三节 NIO编程

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

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


NIO演示代码

服务端代码

package com.nio.server;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;/** * NIO服务端 * @author 小路 */public class NIOServer {//通道管理器private Selector selector;/** * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 * @param port  绑定的端口号 * @throws IOException */public void initServer(int port) throws IOException {// 获得一个ServerSocket通道ServerSocketChannel serverChannel = ServerSocketChannel.open();// 设置通道为非阻塞serverChannel.configureBlocking(false);// 将该通道对应的ServerSocket绑定到port端口serverChannel.socket().bind(new InetSocketAddress(port));// 获得一个通道管理器this.selector = Selector.open();//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,//当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。serverChannel.register(selector, SelectionKey.OP_ACCEPT);}/** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * @throws IOException */@SuppressWarnings("unchecked")public void listen() throws IOException {System.out.println("服务端启动成功!");// 轮询访问selectorwhile (true) {//当注册的事件到达时,方法返回;否则,该方法会一直阻塞selector.select();// 获得selector中选中的项的迭代器,选中的项为注册的事件Iterator ite = this.selector.selectedKeys().iterator();while (ite.hasNext()) {SelectionKey key = (SelectionKey) ite.next();// 删除已选的key,以防重复处理ite.remove();// 客户端请求连接事件if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();// 获得和客户端连接的通道SocketChannel channel = server.accept();// 设置成非阻塞channel.configureBlocking(false);//在这里可以给客户端发送信息哦channel.write(ByteBuffer.wrap(new String("server").getBytes()));//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。channel.register(this.selector, SelectionKey.OP_READ);// 获得了可读的事件} else if (key.isReadable()) {read(key);}}}}/** * 处理读取客户端发来的信息 的事件 * @param key * @throws IOException  */public void read(SelectionKey key) throws IOException{// 服务器可读取消息:得到事件发生的Socket通道SocketChannel channel = (SocketChannel) key.channel();// 创建读取的缓冲区ByteBuffer buffer = ByteBuffer.allocate(10);channel.read(buffer);byte[] data = buffer.array();String msg = new String(data).trim();System.out.println("服务端收到信息:"+msg);ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());channel.write(outBuffer);// 将消息回送给客户端}/** * 启动服务端测试 * @throws IOException  */public static void main(String[] args) throws IOException {NIOServer server = new NIOServer();server.initServer(8000);server.listen();}}


客户端代码

package com.nio.client;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Iterator;/** * NIO客户端 * @author 小路 */public class NIOClient {//通道管理器private Selector selector;/** * 获得一个Socket通道,并对该通道做一些初始化的工作 * @param ip 连接的服务器的ip * @param port  连接的服务器的端口号          * @throws IOException */public void initClient(String ip,int port) throws IOException {// 获得一个Socket通道SocketChannel channel = SocketChannel.open();// 设置通道为非阻塞channel.configureBlocking(false);// 获得一个通道管理器this.selector = Selector.open();// 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调//用channel.finishConnect();才能完成连接channel.connect(new InetSocketAddress(ip,port));//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。channel.register(selector, SelectionKey.OP_CONNECT);}/** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * @throws IOException */@SuppressWarnings("unchecked")public void listen() throws IOException {// 轮询访问selectorwhile (true) {selector.select();// 获得selector中选中的项的迭代器Iterator ite = this.selector.selectedKeys().iterator();while (ite.hasNext()) {SelectionKey key = (SelectionKey) ite.next();// 删除已选的key,以防重复处理ite.remove();// 连接事件发生if (key.isConnectable()) {SocketChannel channel = (SocketChannel) key.channel();// 如果正在连接,则完成连接if(channel.isConnectionPending()){channel.finishConnect();}// 设置成非阻塞channel.configureBlocking(false);//在这里可以给服务端发送信息哦channel.write(ByteBuffer.wrap(new String("client").getBytes()));//在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。channel.register(this.selector, SelectionKey.OP_READ);// 获得了可读的事件} else if (key.isReadable()) {read(key);}}}}/** * 处理读取服务端发来的信息 的事件 * @param key * @throws IOException  */public void read(SelectionKey key) throws IOException{// 客户端可读取消息:得到事件发生的Socket通道SocketChannel channel = (SocketChannel) key.channel();// 创建读取的缓冲区ByteBuffer buffer = ByteBuffer.allocate(10);channel.read(buffer);byte[] data = buffer.array();String msg = new String(data).trim();System.out.println("客户端收到信息:"+msg);ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());channel.write(outBuffer);// 将消息回送给服务器}/** * 启动客户端测试 * @throws IOException  */public static void main(String[] args) throws IOException {NIOClient client = new NIOClient();client.initClient("localhost",8000);client.listen();}}






0 0