netty基础01_netty是什么
netty是一个网络编程框架;
如果java应用程序需要与其它程序或设备通信时,需要用到网络编程;
比如:通过java程序远程控制设备开关、接收并处理传感器传递的数据等;
1.利用阻塞流的socket
服务端:
public class BioSocket_server { public static void main(String[] args) throws Exception{ ServerSocket serverSocket = new ServerSocket(8090); //创建服务端socket new Thread(() -> { while(true){ try { Socket socket = serverSocket.accept(); //接收请求 new Thread(()->{ try { byte[] buf = new byte[1024]; InputStream is = socket.getInputStream(); //获取字节流 while (true){ int len; while((len = is.read(buf)) != -1){ System.out.println(new String(buf, 0, len)); //输出客户端通过字节流传过来的数据 } } } catch (Exception e) { e.printStackTrace(); } }).start(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
服务端每接收到一个请求,将给该请求创建一个线程;
该线程用字节流读取客户端发送过来的数据,并在控制台输出;
客户端:
public class BioSocket_client { public static void main(String[] args) { new Thread(()->{ try { Socket socket = new Socket("127.0.0.1", 8090); //请求服务端 while(true){ socket.getOutputStream().write((new Date() + " hello ").getBytes()); //发送数据 socket.getOutputStream().flush(); Thread.sleep(2000); } } catch (Exception e) { e.printStackTrace(); } }).start(); } }
客户端每2秒给服务端发送一个字符串;
结果:
Tue Apr 28 16:20:35 CST 2020hello Tue Apr 28 16:20:37 CST 2020hello Tue Apr 28 16:20:39 CST 2020hello Tue Apr 28 16:20:41 CST 2020hello Tue Apr 28 16:20:43 CST 2020hello Tue Apr 28 16:20:45 CST 2020hello Tue Apr 28 16:20:47 CST 2020hello Tue Apr 28 16:20:49 CST 2020hello Tue Apr 28 16:20:51 CST 2020hello
这种IO编程模型在客户端较少的情况下运行良好;
但是对于客户端比较多的业务来说,IO模型可能就不太合适了;
比如在上面的例子中:
每个连接创建成功之后都需要一个线程来维护,
每个线程包含一个while死循环,
那么1w个连接对应1w个线程,继而1w个while死循环
bio的缺点:
线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
线程切换效率低下:单机cpu核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。
除了以上两个问题,IO编程中,我们看到数据读写是以字节流为单位,效率不高。
2.NIO编程
java1.4引入了非阻塞式流NIO;
与传统的bio( Blocking I/O)相比,nio( Nonblocking I/O)可以一个线程处理多个请求;
优点:更少的内存和上下文切换开销;
nio服务端:
public class NIOServer { public static void main(String[] args) throws IOException { Selector serverSelector = Selector.open(); Selector clientSelector = Selector.open(); new Thread(() -> { try { // 对应IO编程中服务端启动 ServerSocketChannel listenerChannel = ServerSocketChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(8000)); listenerChannel.configureBlocking(false); listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT); while (true) { // 监测是否有新的连接,这里的1指的是阻塞的时间为1ms if (serverSelector.select(1) > 0) { Set<SelectionKey> set = serverSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { try { // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); clientChannel.register(clientSelector, SelectionKey.OP_READ); } finally { keyIterator.remove(); } } } } } } catch (IOException ignored) { } }).start(); new Thread(() -> { try { while (true) { // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为1ms if (clientSelector.select(1) > 0) { Set<SelectionKey> set = clientSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // (3) 读取数据以块为单位批量读取 clientChannel.read(byteBuffer); byteBuffer.flip(); System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer) .toString()); } finally { keyIterator.remove(); key.interestOps(SelectionKey.OP_READ); } } } } } } catch (IOException ignored) { } }).start(); }}
nio的api太过复杂,并且使用不当可能有安全问题;
为了方便使用,需要将其进行封装;
netty就是基于nio开发的网络通信框架;
3.netty
需要引入jar包
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.6.Final</version> </dependency>
服务端:
public class NettyServer { public static void main(String[] args) { ServerBootstrap serverBootstrap = new ServerBootstrap(); NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap .group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(msg); } }); } }) .bind(8000); } }
服务端需要2个消息循环组:boss和worker;
boss用来创建新连接;worker用来读取发送过来的信息,并在控制台输出;
客户端:
public class NettyClient { public static void main(String[] args) throws InterruptedException { Bootstrap bootstrap = new Bootstrap(); NioEventLoopGroup group = new NioEventLoopGroup(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(new StringEncoder()); } }); Channel channel = bootstrap.connect("127.0.0.1", 8000).channel(); while (true) { channel.writeAndFlush(new Date() + ": hello world!"); Thread.sleep(2000); } } }
客户端只需要一个消息循环组
每隔2秒给服务端发送字符串
结果:
Tue Apr 28 17:03:38 CST 2020: hello world! Tue Apr 28 17:03:40 CST 2020: hello world! Tue Apr 28 17:03:42 CST 2020: hello world! Tue Apr 28 17:03:44 CST 2020: hello world! Tue Apr 28 17:03:46 CST 2020: hello world! Tue Apr 28 17:03:48 CST 2020: hello world! Tue Apr 28 17:03:50 CST 2020: hello world! Tue Apr 28 17:03:52 CST 2020: hello world! Tue Apr 28 17:03:54 CST 2020: hello world! Tue Apr 28 17:03:56 CST 2020: hello world!