基于IO、NIO、Netty的Java网络程序(实现服务端与客户端通信)

Github源文件下载:https://github.com/longl118/IO-NIO-Netty

一、IO Java网络聊天程序

1.1 IO 介绍

1.1.1 简介

  • IOBIO Blocking IO)阻塞IO网络模型:服务器启动后会进入阻塞状态,等待client连接,每一个client端连接上服务器后,服务器会为每一个客户端起一个线程来处理客户端的需求。服务器的accept()方法、服务器新起的thread中,Socket的read()和write()方法都是阻塞的。

1.1.2 传统IO特点

  • 1.服务端阻塞点
    server.accept();获取套接字的时候
    inputStream.read(bytes);输入流读取数据的时候
  • 2.传统socket是短连接,可以做短连接服务器,他无法做长连接,属于一问一答的模式,比如老的tomcat底层用的就是socket,用完就会关掉线程,因此不会出现线程一直被占用的情况,支持处理多个客户端连接
    (1)单线程情况下只能有一个客户端(一个线程维护一个连接,也就是一个socket客户连接)线程一直被占用。
    (2)用线程池可以有多个客户端连接,但是非常消耗性能(用此案城池,就是老tomcat原理,只不过是用完后就释放)

1.1.3 原理图

在这里插入图片描述

1.2 IDEA新建Java项目

  • 新建

在这里插入图片描述

在这里插入图片描述

  • 然后一路next
  • 这里改下名就可以

在这里插入图片描述

  • 新建 client.java 文件

在这里插入图片描述

方法同上新建项目IOjava2,新建Java文件 server.java。

  • 最终如下

在这里插入图片描述

1.3 代码

  • server.java
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class server {
    public static void main(String[] args) throws IOException {
        //创建客户端的Socket对象(SevereSocket)
        //ServerSocket (int port)创建绑定到指定端口的服务器套接字
        ServerSocket ss=new ServerSocket(50000);

        //Socket accept()侦听要连接到此套接字并接受他
        Socket s=ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is=s.getInputStream();
        byte[] bys=new byte[1024];
        int len=is.read(bys);
        String data=new String(bys,0,len);
        System.out.println("数据是:"+data);

        //释放资源
        s.close();
        ss.close();
    }

}

  • client.java
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class client {
    public static void main(String[] args) throws IOException{
        //创建客户端的Socket对象
        //Socket (InetAddress adress,int port)创建流套接字并将其连接到指定IP地址的指定端口号
//		Socket s=new Socket(InetAddress.getByName("192.168.224.1"), 10000);
        //Socket (String host,int port)创建流套接字并将其连接到指定主机的指定端口号
        Socket s=new Socket("127.0.0.1", 50000);

        //获取输出流,写数据
        //OutputStream getOutputStream();返回此套接字的输出流
        OutputStream os=s.getOutputStream();
        os.write("helloWorld!".getBytes());

        //释放资源
        s.close();

    }

}

1.4 运行结果

在这里插入图片描述

二、NIO Java网络聊天程序

2.1 NIO 介绍

2.1.1 简介

  • NIO(Non-Blocking IO)
    NIO-Single Thread模型
    NIO单线程模型:采用selector管理的轮询查询模式,selector每隔一段时间都去看一下client端有没有产生需要处理的消息(客户端连接请求、客户端发送数据请求、客户端下载数据请求),也就是说selector会同时管理client端的连接和通道内client端的读、写请求。

2.1.2 NIO的特点

  • 主要API介绍:
    ServerSocketChannel对应传统IO中的ServerSocket。
    SocketChannel对应传统IO中的Socket。
    Selector 是NIO核心 ,负载监听 ServerSocketChannel与SocketChannel ,支持单线程连多个客户端;类似通道管理器而且底层是c实现的;线程拥有一个selector就可以支持多个客户端。
    SelectionKey 相当于map中的key 相当于记录根据不同动作做不同事情,一个key一个事件。

2.1.3 NIO的通信步骤

①创建ServerSocketChannel,为其配置非阻塞模式。
②绑定监听,配置TCP参数,录入backlog大小等。
③创建一个独立的IO线程,用于轮询多路复用器Selector。
④创建Selector,将之前创建的ServerSocketChannel注册到Selector上,并设置监听标识位SelectionKey.OP_ACCEPT。
⑤启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的通道。
⑥当轮询到处于就绪状态的通道时,需要进行操作位判断,如果是ACCEPT状态,说明是新的客户端接入,则调用accept方法接收新的客户端。
⑦设置新接入客户端的一些参数,如非阻塞,并将其继续注册到Selector上,设置监听标识位等。
⑧如果轮询的通道标识位是READ,则进行读取,构造Buffer对象等。
⑨更细节的问题还有数据没发送完成继续发送的问题…

2.1.4 原理图

在这里插入图片描述

NIO模型中线程数量大大降低,线程切换效率因此也大幅度提高。

2.1.5 IO与NIO的主要区别

IONIO
面向流面向缓冲
阻塞IO非阻塞IO
选择器

2.2 新建项目

  • 新建项目方法同 1.2
  • 最终项目如下

在这里插入图片描述

2.3 代码

  • server
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;
import java.util.Set;

public class server {
    //网络通信IO操作,TCP协议,针对面向流的监听套接字的可选择通道(一般用于服务端)
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    /*
     *开启服务端
     */
    public void start(Integer port) throws Exception {
        serverSocketChannel = ServerSocketChannel.open();
        selector = Selector.open();
        //绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //注册到Selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        startListener();
    }
    private void startListener() throws Exception {
        while (true) {
            // 如果客户端有请求select的方法返回值将不为零
            if (selector.select(1000) == 0) {
                System.out.println("当前没有任务!!!");
                continue;
            }
            // 如果有事件集合中就存在对应通道的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历所有的key找到其中事件类型为Accept的key
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable())
                    handleConnection();
                if (key.isReadable())
                    handleMsg(key);
                iterator.remove();
            }
        }
    }
    /**
     * 处理建立连接
     */
    private void handleConnection() throws Exception {
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    }
    /*
     * 接收信息
     */
    private void handleMsg(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer attachment = (ByteBuffer) key.attachment();
        channel.read(attachment);
        System.out.println("当前信息: " + new String(attachment.array()));
    }

    public static void main(String[] args) throws Exception {
        server myServer = new server();
        myServer.start(8881);
    }
}
  • client
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class client {
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        // 连接服务器
        if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 8881))) {
            while (!socketChannel.finishConnect()) {
                System.out.println("connecting...");
            }
        }
        //发送数据
        String str = "Hello~,要加油呀!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }

}

2.4 运行结果

注意:要先运行server,再运行client。

在这里插入图片描述

三、Netty Java网络程序

3.1 Netty 介绍

3.1.1 简介

  • Netty 是一个基于NIO的客户、服务器端编程框架。
    Netty封装了JDK的NIO,Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

3.1.2 特点

  • 1.使用Netty 可以确保你快速和简单的开发出一个网络应用。
    2.Netty 是一个吸收了多种协议的实现经验,包括FTP,SMTP,HTTP,各种二进制,文本协议,并经过相当精心设计的项目。
    3.Netty在保证程序易于开发的同时,还保证了应用的高性能,稳定性和伸缩性。

3.1.3 Netty通信的步骤

  • ①创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信的读写。
    ②创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。
    ③创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式以及实际处理数据的接口。
    ④绑定端口,执行同步阻塞方法等待服务器端启动即可。

3.2 环境配置及构建项目

3.2.1 新建项目

  • 同上新建两个项目
  • 最终两个项目目录如下:

在这里插入图片描述

3.2.2 环境配置

  • FIle->Project Structure

在这里插入图片描述

  • Moudles→Dependencies→右边的加号→Library->From Maven…

在这里插入图片描述

  • 在弹出的搜索框输入 io.netty:netty-all,点击搜索符号,等待几分钟
  • 选择 io.netty:netty-all:5.0.0.Alpha2(下载Netty的jar包)

在这里插入图片描述

在这里插入图片描述

  • 这里直接点OK

在这里插入图片描述

  • 先点击Apply,再点击OK

在这里插入图片描述

  • 在另一个项目里,也要加上,如下

在这里插入图片描述

  • 在弹出的文件选择里找到前面存放jar的目录

在这里插入图片描述

  • 还是先Apply再OK

在这里插入图片描述

3.3 代码

  • server.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.net.InetSocketAddress;

/**
 *
 */
public class server {

    private int port;

    public static void main(String[] args){
        new server(12345).start();
    }

    public server(int port) {
        this.port = port;
    }

    public void start() {
        /**
         * 创建两个EventLoopGroup,即两个线程池,boss线程池用于接收客户端的连接,
         * 一个线程监听一个端口,一般只会监听一个端口所以只需一个线程
         * work池用于处理网络连接数据读写或者后续的业务处理(可指定另外的线程处理业务,
         * work完成数据读写)
         */
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup work = new NioEventLoopGroup();
        try {
            /**
             * 实例化一个服务端启动类,
             * group()指定线程组
             * channel()指定用于接收客户端连接的类,对应java.nio.ServerSocketChannel
             * childHandler()设置编码解码及处理连接的类
             */
            ServerBootstrap server = new ServerBootstrap()
                    .group(boss, work).channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new HelloWorldServerHandler());
                        }
                    });
            //绑定端口
            ChannelFuture future = server.bind().sync();
            System.out.println("server started and listen " + port);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    public static class HelloWorldServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("HelloWorldServerHandler active");
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("server channelRead..");
            System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString());
            ctx.write("server write"+msg);
            ctx.flush();
        }
    }
}

  • client.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 *
 */
public class client {
    private static final String HOST = "localhost";
    private static final int PORT= 12345;

    public static void main(String[] args){
        new client().start(HOST, PORT);
    }

    public void start(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap client = new Bootstrap().group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new HelloWorldClientHandler());
                        }
                    });
            ChannelFuture future = client.connect(host, port).sync();
            future.channel().writeAndFlush("Hello Netty Server ,I am a netty client");
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();

        }

    }

    public static class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("HelloWorldClientHandler Active");
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("HelloWorldClientHandler read Message:"+msg);
        }
    }
}

3.4 实验结果

  • 先运行server,再运行client

在这里插入图片描述

小小的总结

  1. 通过IO实现的服务端与客户端之间的通讯非常简单,但是存在的问题也非常多:
    线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起。
    线程切换效率低下:单机 CPU 核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。
    数据读写是以字节流为单位。
  2. 不建议直接基于JDK原生NIO来进行网络开发。使用 JDK 自带的NIO需要了解太多的概念,编程复杂,一不小心 bug 横飞。
  3. Netty 底层 IO 模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从 NIO 模型变身为 IO 模型;
    Netty 自带的拆包解包,异常检测等机制让你从NIO的繁重细节中脱离出来,让你只需要关心业务逻辑;
    Netty 解决了 JDK 的很多包括空轮询在内的 Bug;
    Netty 底层对线程,selector 做了很多细小的优化,精心设计的 reactor 线程模型做到非常高效的并发处理;
    自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手;
    Netty 社区活跃,遇到问题随时邮件列表或者 issue;
    Netty 已经历各大 RPC 框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大。

以上取自大佬的博客,总结得很清晰,链接放在参考文献最后了。

参考文献

  1. 基于IO、NIO、Netty的TCP聊天程序
  2. 分别基于IO、NIO、Netty的Java网络程序
  3. 基于IO/NIO/Netty的Client/Server的java程序实现
  4. IO、NIO、Netty分别实现服务端与客户端通信-(笔主认为写得很简洁明了又好懂的一篇文章)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的Netty实现WebSocket协议的服务端客户端案例: 服务端代码: ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; public class WebSocketServer { private final int port; public WebSocketServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerProtocolHandler("/websocket")); pipeline.addLast(new WebSocketServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(port).sync(); System.out.println("WebSocket Server started on port " + port); future.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new WebSocketServer(8080).start(); } } ``` 客户端代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import java.net.URI; public class WebSocketClient { private final URI uri; private final WebSocketClientHandler handler = new WebSocketClientHandler(); private Channel channel; public WebSocketClient(URI uri) { this.uri = uri; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(8192)); pipeline.addLast(new WebSocketClientProtocolHandler(uri, WebSocketVersion.V13, null, true, HttpHeaders.EMPTY_HEADERS, 65536)); pipeline.addLast(handler); } }); channel = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel(); handler.handshakeFuture().sync(); System.out.println("WebSocket Client connected to " + uri); } catch (Exception e) { group.shutdownGracefully(); } } public void stop() { channel.writeAndFlush(new CloseWebSocketFrame()); try { channel.closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } public void sendMessage(String message) { channel.writeAndFlush(new TextWebSocketFrame(message)); } public static void main(String[] args) throws Exception { WebSocketClient client = new WebSocketClient(new URI("ws://localhost:8080/websocket")); client.start(); client.sendMessage("Hello, Netty WebSocket!"); Thread.sleep(1000); client.stop(); } private static class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> { private final WebSocketClientHandshaker handshaker; private ChannelPromise handshakeFuture; public WebSocketClientHandler() { URI uri = URI.create("ws://localhost:8080/websocket"); handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, HttpHeaders.EMPTY_HEADERS, 65536); } public ChannelFuture handshakeFuture() { return handshakeFuture; } @Override public void handlerAdded(ChannelHandlerContext ctx) { handshakeFuture = ctx.newPromise(); } @Override public void channelActive(ChannelHandlerContext ctx) { handshaker.handshake(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) { System.out.println("WebSocket Client disconnected!"); } @Override public void channelRead0(ChannelHandlerContext ctx, Object msg) { Channel ch = ctx.channel(); if (!handshaker.isHandshakeComplete()) { handshaker.finishHandshake(ch, (FullHttpResponse) msg); System.out.println("WebSocket Client connected!"); handshakeFuture.setSuccess(); return; } if (msg instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) msg; throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; System.out.println("WebSocket Client received message: " + textFrame.text()); } else if (frame instanceof PongWebSocketFrame) { System.out.println("WebSocket Client received pong"); } else if (frame instanceof CloseWebSocketFrame) { System.out.println("WebSocket Client received closing"); ch.close(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); if (!handshakeFuture.isDone()) { handshakeFuture.setFailure(cause); } ctx.close(); } } } ``` 使用时,先启动服务端,再启动客户端客户端启动后,会向服务端发送一条消息,并在接收到服务端的响应后关闭连接。在控制台上可以看到客户端服务端的交互过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值