【IDEA】基于IO、NIO、Netty的TCP网络聊天程序

本文详细介绍了如何使用IO、NIO和Netty三种方式实现TCP聊天程序,对比了它们的特点和差异。IO基于阻塞IO模型,适合短连接,而NIO引入了选择器,实现单线程处理多个连接。Netty作为高性能的异步网络通信框架,提供了更高级别的抽象。通过实例代码展示了每种方式的实现过程,并进行了测试验证。

一、IO实现TCP聊天程序

1. IO简介

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

2. IO实现网络程序

  • 新建两个java项目(一个Server端,一个client端)
    在这里插入图片描述
  • Server端代码编写
package com.company;

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

public class Main {

    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端代码编写
package com.company;

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

public class Main {

    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("hello,tcp".getBytes());

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

在这里插入图片描述

3. 运行测试

在这里插入图片描述
测试成功。

二、NIO实现TCP聊天程序

1. NIO简介

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

  2. NIO与IO的主要区别
    在这里插入图片描述

2. NIO实现网络程序

  • 新建两个java项目(一个Server端,一个client端)
    在这里插入图片描述
  • Server端代码编写
package com.company;

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 Main {

    //网络通信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("current not exists task");
                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("current msg: " + new String(attachment.array()));
    }

    public static void main(String[] args) throws Exception {
        Main myServer = new Main();
        myServer.start(8888);
    }
}

在这里插入图片描述

  • Client端代码编写
package com.company;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Main {

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

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

在这里插入图片描述

3. 运行测试

在这里插入图片描述
测试成功。

三、Netty实现TCP聊天程序

1. Netty简介

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

2. Netty实现网络程序

  • 新建两个java项目(一个Server端,一个client端),并导入jar
    在这里插入图片描述
    • Server端代码编写
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(18080).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端代码编写
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= 18080;

    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. 运行测试

在这里插入图片描述
测试成功!

四、总结

  本文对IO、NIO、Netty的进行了简单介绍,并分别基于IO、NIO、Netty编写了TCP网络聊天程序。

五、参考

https://blog.youkuaiyun.com/junseven164/article/details/121714791?spm=1001.2014.3001.5501

@Test @Timeout(value = 10, unit = TimeUnit.SECONDS) void testResponseTimeout() throws InterruptedException { // 定义一个不响应的服务器(覆盖createChannelInitializer) TcpServer nonResponsiveServer = new TcpServer() { @Override protected ChannelInitializer createChannelInitializer() { return new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { // 仅保留解码器,移除业务处理器和编码器(不返回响应) ch.pipeline().addLast( new LengthFieldFrameDecoder(), new JsonRequestDecoder() ); } }; } }; // 启动服务器线程(处理InterruptedException) Thread nonResponsiveServerThread = new Thread(() -> { try { nonResponsiveServer.start(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 LOG.error("服务器启动被中断", e); } }); nonResponsiveServerThread.start(); // 等待服务器启动(根据实际情况调整等待时间) Thread.sleep(3000); // 客户端连接并发送请求(验证超时) TcpClient client = new TcpClient("127.0.0.1", 8080); client.connect(); GreetRequest request = new GreetRequest(); request.setName("TimeoutUser"); GreetResponse response = client.sendRequest(request); assertThat(response).isNull(); // 预期超时返回null // 清理资源 client.shutdown(); nonResponsiveServer.shutdown(); nonResponsiveServerThread.join(3000); } @Test @Timeout(value = 10, unit = TimeUnit.SECONDS) // 防止测试卡住 void testNormalCommunication() throws InterruptedException { // 初始化客户端 TcpClient client = new TcpClient("127.0.0.1", TEST_PORT); client.connect(); // 构造请求 GreetRequest request = new GreetRequest(); request.setName("TestUser"); // 发送请求并等待响应 GreetResponse response = client.sendRequest(request); // 验证响应 assertThat(response).isNotNull(); assertThat(response.getMessage()).isEqualTo("Hello, TestUser!"); // 关闭客户端 client.shutdown(); }报错Exception in thread "Thread-3" java.net.BindException: Address already in use: bind at sun.nio.ch.Net.bind0(Native Method) at sun.nio.ch.Net.bind(Net.java:461) at sun.nio.ch.Net.bind(Net.java:453) at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:222) at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:148) at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:434) at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1353) at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:492) at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:471) at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:994) at io.netty.channel.Channel.bind(Channel.java:302) at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:380) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at --- Async.Stack.Trace --- (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.bootstrap.AbstractBootstrap.doBind0(AbstractBootstrap.java:376) at io.netty.bootstrap.AbstractBootstrap.access$000(AbstractBootstrap.java:54) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:315) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:302) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118) at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:861) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:385) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:374) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:110) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:78) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:73) at io.netty.channel.nio.AbstractNioChannel.lambda$doRegister$0(AbstractNioChannel.java:465) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:198) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:36) at io.netty.channel.nio.AbstractNioChannel.doRegister(AbstractNioChannel.java:462) at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:408) at io.netty.channel.AbstractChannel$AbstractUnsafe.access$300(AbstractChannel.java:288) at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:352) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at --- Async.Stack.Trace --- (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:349) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:119) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:113) at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86) at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:339) at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:288) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:284) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:262) at server.TcpServer.start(TcpServer.java:75) at TcpServerClientTest.lambda$startServer$0(TcpServerClientTest.java:45) at java.lang.Thread.run(Thread.java:750) Suppressed: java.util.concurrent.CompletionException: Rethrowing promise failure cause at io.netty.util.concurrent.DefaultPromise.rethrowIfFailed(DefaultPromise.java:685) at io.netty.util.concurrent.DefaultPromise.sync(DefaultPromise.java:419) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:119) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:30) ... 3 more Exception in thread “Thread-4” java.net.BindException: Address already in use: bind at sun.nio.ch.Net.bind0(Native Method) at sun.nio.ch.Net.bind(Net.java:461) at sun.nio.ch.Net.bind(Net.java:453) at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:222) at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:148) at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:434) at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1353) at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:492) at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:471) at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:994) at io.netty.channel.Channel.bind(Channel.java:302) at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:380) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at — Async.Stack.Trace — (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.bootstrap.AbstractBootstrap.doBind0(AbstractBootstrap.java:376) at io.netty.bootstrap.AbstractBootstrap.access$000(AbstractBootstrap.java:54) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:315) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:302) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118) at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:861) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:385) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:374) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:110) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:78) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:73) at io.netty.channel.nio.AbstractNioChannel.lambda$doRegister$0(AbstractNioChannel.java:465) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:198) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:36) at io.netty.channel.nio.AbstractNioChannel.doRegister(AbstractNioChannel.java:462) at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:408) at io.netty.channel.AbstractChannel$AbstractUnsafe.access$300(AbstractChannel.java:288) at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:352) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at — Async.Stack.Trace — (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:349) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:119) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:113) at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86) at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:339) at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:288) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:284) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:262) at server.TcpServer.start(TcpServer.java:75) at TcpServerClientTest.lambda$testResponseTimeout$1(TcpServerClientTest.java:119) at java.lang.Thread.run(Thread.java:750) Suppressed: java.util.concurrent.CompletionException: Rethrowing promise failure cause at io.netty.util.concurrent.DefaultPromise.rethrowIfFailed(DefaultPromise.java:685) at io.netty.util.concurrent.DefaultPromise.sync(DefaultPromise.java:419) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:119) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:30) … 3 more [main] INFO client.TcpClient - 客户端连接成功,目标地址:127.0.0.1:8080 Duplicate field name “$hits$” with signature “[I” in class file io/netty/buffer/AllocateChunkEvent JfrClassAdapter: unable to create InstanceKlass Duplicate field name “$hits$” with signature “[I” in class file io/netty/buffer/AllocateBufferEvent JfrClassAdapter: unable to create InstanceKlass Duplicate field name “$hits$” with signature “[I” in class file io/netty/buffer/FreeBufferEvent JfrClassAdapter: unable to create InstanceKlass [main] INFO server.TcpServer - 服务器已关闭 org.opentest4j.AssertionFailedError: expected: null but was: model.GreetResponse@e7529ac 预期:null 实际:model.GreetResponse@e7529ac;Exception in thread “Thread-5” java.net.BindException: Address already in use: bind at sun.nio.ch.Net.bind0(Native Method) at sun.nio.ch.Net.bind(Net.java:461) at sun.nio.ch.Net.bind(Net.java:453) at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:222) at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:148) at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:434) at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1353) at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:492) at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:471) at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:994) at io.netty.channel.Channel.bind(Channel.java:302) at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:380) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at — Async.Stack.Trace — (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.bootstrap.AbstractBootstrap.doBind0(AbstractBootstrap.java:376) at io.netty.bootstrap.AbstractBootstrap.access$000(AbstractBootstrap.java:54) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:315) at io.netty.bootstrap.AbstractBootstrap$1.operationComplete(AbstractBootstrap.java:302) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118) at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:861) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:385) at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:374) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) at io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:110) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:78) at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:73) at io.netty.channel.nio.AbstractNioChannel.lambda$doRegister$0(AbstractNioChannel.java:465) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:198) at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:36) at io.netty.channel.nio.AbstractNioChannel.doRegister(AbstractNioChannel.java:462) at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:408) at io.netty.channel.AbstractChannel$AbstractUnsafe.access$300(AbstractChannel.java:288) at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:352) at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) at — Async.Stack.Trace — (captured by IntelliJ IDEA debugger) at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:907) at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:873) at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:863) at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:349) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:119) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:113) at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86) at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:339) at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:288) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:284) at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:262) at server.TcpServer.start(TcpServer.java:75) at TcpServerClientTest.lambda$startServer$0(TcpServerClientTest.java:45) at java.lang.Thread.run(Thread.java:750) Suppressed: java.util.concurrent.CompletionException: Rethrowing promise failure cause at io.netty.util.concurrent.DefaultPromise.rethrowIfFailed(DefaultPromise.java:685) at io.netty.util.concurrent.DefaultPromise.sync(DefaultPromise.java:419) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:119) at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:30) … 3 more [main] INFO server.TcpServer - 服务器已关闭 [main] INFO client.TcpClient - 客户端连接成功,目标地址:127.0.0.1:8080 [main] INFO client.TcpClient - 客户端已关闭 [main] INFO server.TcpServer - 服务器已关闭 org.opentest4j.AssertionFailedError: Expecting value to be true but was false 预期:true 实际:false 请分析原因并尝试解决
最新发布
08-23
<think>我们遇到了多个问题,包括端口绑定冲突(BindException: Address already in in use)、Netty内部日志警告(Duplicate field name "$hits$")以及测试断言失败(预期为null但实际收到响应,以及预期为true但实际为false)。下面逐一分析: 1. **端口绑定冲突(BindException)**: 错误信息显示多个线程尝试绑定同一个端口(如8080)时失败,因为端口已被占用。在测试中,我们启动了多个服务器(例如在`testResponseTimeout`和`testNormalCommunication`中),它们可能尝试绑定到同一个端口。另外,由于测试是并行运行或前一个测试没有正确释放端口,也会导致此问题。 解决方案: - 确保每个测试使用不同的端口。 - 确保在测试结束后正确关闭服务器并释放端口。在测试方法中,我们使用了`shutdown`方法,但需要确保服务器关闭操作完成后再进行下一个测试(例如使用`nonResponsiveServerThread.join(3000)`等待线程结束,但可能不够,因为关闭操作是异步的)。 - 在测试中,我们可以使用随机端口(例如端口0,由系统分配)或者为每个测试指定不同的固定端口(如8080, 8081等)。但随机端口更可靠。 2. **Netty内部日志警告(Duplicate field name "$hits$")**: 这些警告来自Netty内部,可能是由于Netty的某些类被多个类加载器加载,或者某些工具(如JFR)尝试重新定义类导致的。这通常不会影响功能,但可以尝试升级Netty版本或忽略这些警告。 3. **测试断言失败**: - 在`testResponseTimeout`中,我们预期服务器不响应,因此`response`应该为null。但实际收到了一个非空的`GreetResponse`对象。这可能是因为服务器没有按照预期配置(移除业务处理器和编码器)?或者测试代码中实际启动的服务器并不是我们覆盖的那个?另外,端口冲突也可能导致测试环境混乱。 - 另一个测试中预期为true但实际为false,具体原因需要看代码,但同样可能是环境问题。 具体解决步骤: 针对端口冲突: - 在测试中,我们可以让服务器绑定到随机端口(端口0),然后获取实际绑定的端口,再让客户端连接这个端口。 - 或者,在每个测试方法中,使用不同的固定端口(例如8080,8081,8082等),并确保测试是顺序执行的(使用`@Order`注解或调整测试顺序)。 由于我们无法在测试中直接使用端口0(因为需要告知客户端端口号),我们可以这样做: 1. 在服务器启动前,创建一个`ServerSocket`并绑定到端口0,然后立即关闭它,这样系统会分配一个空闲端口,我们获取这个端口号,然后让服务器和客户端都使用这个端口。 2. 或者,使用JUnit的`@BeforeEach`和`@AfterEach`来管理服务器和端口,确保每个测试使用独立端口。 但是,在Netty中,我们可以直接绑定到端口0,然后获取绑定的实际端口。例如: ```java ServerBootstrap b = new ServerBootstrap(); // ... 配置b ChannelFuture future = b.bind(0).sync(); int port = ((InetSocketAddress) future.channel().localAddress()).getPort(); ``` 然后,将这个端口传递给客户端。 由于测试代码结构,我们需要调整服务器启动方式,使其能够返回实际绑定的端口。然后,在客户端连接时使用这个端口。 另外,注意在测试结束后正确关闭服务器,并等待关闭完成(使用`sync()`或`await()`)。 针对`testResponseTimeout`测试失败(预期null但实际有响应): 我们创建了一个不响应服务器(移除了业务处理器和编码器),但为什么客户端还是收到了响应?可能的原因有: - 服务器配置错误:我们覆盖了`createChannelInitializer`,但可能这个覆盖并没有被实际使用?需要检查服务器启动时是否使用了这个初始器。 - 由于端口冲突,测试实际连接到了之前未关闭的服务器(正常响应的那个)?所以确保端口不冲突和正确关闭服务器很重要。 针对另一个测试(预期true但实际false): 需要具体看是哪个测试,但同样可能是环境问题。 建议的修改: 1. 重构TcpServer,使其可以绑定到指定端口(包括0)并返回实际绑定的端口。 2. 在测试中,每个服务器都绑定到0端口,然后获取实际端口,并确保客户端连接这个端口。 3. 确保每个测试都正确关闭服务器,并等待关闭完成(使用`ChannelFuture`的`sync`或`await`)。 由于代码较长,这里只给出关键修改思路: 在TcpServer类中: - 修改`start`方法,允许传入端口(可以传入0),然后绑定到这个端口。 - 在绑定后,获取实际绑定的端口并返回。 ```java public int start(int port) throws InterruptedException { // 配置ServerBootstrap... ChannelFuture future = bootstrap.bind(port).sync(); // 获取实际绑定的端口 this.port = ((InetSocketAddress) future.channel().localAddress()).getPort(); // 记录日志 return this.port; } ``` 在测试中: - 每个测试启动自己的服务器,使用端口0,并获取实际端口。 - 客户端连接这个实际端口。 另外,在测试中,要确保服务器关闭后端口被释放。在`shutdown`方法中,我们应确保关闭操作完成: ```java public void shutdown() throws InterruptedException { if (bossGroup != null) { bossGroup.shutdownGracefully().sync(); } if (workerGroup != null) { workerGroup.shutdownGracefully().sync(); } } ``` 使用`sync()`确保关闭完成。 同时,在测试代码中,要确保在测试方法结束时关闭服务器并等待关闭完成。 由于时间关系,这里不展开所有代码修改。但按照上述思路,应该可以解决端口冲突问题。 针对不响应服务器的测试,需要再次检查覆盖的`ChannelInitializer`是否正确移除了业务处理器和编码器。另外,确保客户端连接的是这个不响应服务器,而不是其他服务器。 最后,关于Netty的警告,可以尝试升级Netty到最新版本,或者忽略这些警告(因为它们可能只是警告,不影响功能)。 总结修改步骤: 1. 修改TcpServer,使其在绑定端口0时能返回实际端口,并确保关闭方法等待关闭完成。 2. 在测试中,每个测试方法都使用独立的服务器实例,并绑定到端口0,然后使用返回的实际端口创建客户端。 3. 确保每个测试方法在结束时关闭服务器并等待关闭完成(使用`shutdown`方法,并在其中调用`sync`)。 4. 对于不响应服务器的测试,确保其配置正确,并且客户端连接到了正确的服务器。 由于原始代码较长,这里无法提供完整的修改代码,但按照上述思路修改,应该可以解决大部分问题。 如果问题仍然存在,请检查测试代码中是否有多个服务器同时启动(比如在测试类中使用了`@BeforeEach`启动了一个服务器,又在测试方法中启动了一个)?确保测试方法之间不会相互干扰。 另外,注意测试方法执行顺序:默认情况下,JUnit测试方法执行顺序是不确定的,但可以通过`@TestMethodOrder`和`@Order`指定顺序。但最好每个测试都是独立的,使用不同的端口。 由于时间限制,这里只能提供思路,具体实现需要根据项目代码调整。</think>### 问题分析及解决方案 根据错误日志和测试失败信息,主要存在以下问题: 1. **端口冲突(Address already in use)** - 多个测试并行启动服务器时绑定相同端口(8080) - 前一个测试未完全释放端口,后一个测试立即尝试绑定 2. **超时测试失败** - `testResponseTimeout` 预期返回 null 但实际收到响应 - 原因:非响应服务器可能未正确配置,或客户端连接了错误服务器 3. **Netty 内部警告** - `Duplicate field name "$hits$"` 是 Netty 内部 JFR 事件注册问题 - 通常不影响功能,可暂时忽略 --- ### 解决方案 #### 1. 解决端口冲突问题 **修改服务器启动方式:** ```java // 在测试类中定义动态获取空闲端口的方法 private int getAvailablePort() throws IOException { try (ServerSocket socket = new ServerSocket(0)) { return socket.getLocalPort(); } } // 在每个测试中动态分配端口 @Test void testResponseTimeout() throws Exception { int port = getAvailablePort(); // 获取空闲端口 TcpServer nonResponsiveServer = ... // 创建服务器 nonResponsiveServer.start(port); // 使用动态端口启动 TcpClient client = new TcpClient("127.0.0.1", port); // 客户端连接相同端口 // ... 后续测试代码 } ``` #### 2. 修复超时测试 **确保服务器配置正确:** ```java protected ChannelInitializer createChannelInitializer() { return new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { // 移除所有处理器(包括解码器) ch.pipeline().clear(); // 添加空处理器防止自动断开 ch.pipeline().addLast(new ChannelInboundHandlerAdapter()); } }; } ``` **增强客户端超时检测:** ```java // 在客户端发送请求方法中添加超时控制 public GreetResponse sendRequest(GreetRequest request) { // 设置响应等待超时(例如3秒) channel.writeAndFlush(request).awaitUninterruptibly(3, TimeUnit.SECONDS); return responseQueue.poll(3, TimeUnit.SECONDS); // 超时返回null } ``` #### 3. 资源释放优化 **确保彻底关闭服务器:** ```java public void shutdown() throws InterruptedException { if (bossGroup != null) { bossGroup.shutdownGracefully().sync(); // 阻塞直到关闭完成 } // 添加端口释放等待 Thread.sleep(500); } ``` #### 4. 测试顺序调整 **添加测试依赖顺序:** ```java @FixMethodOrder(MethodSorters.NAME_ASCENDING) // 按方法名字典序执行 class TcpServerClientTest { @Test void test1_ResponseTimeout() { ... } @Test void test2_NormalCommunication() { ... } } ``` --- ### 验证要点 1. 使用 `netstat -ano | findstr :8080` 确认测试后端口释放 2. 在 `testResponseTimeout` 中添加日志,确认服务器pipeline配置 3. 检查客户端是否连接了正确的动态端口
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值