网络编程5

本文介绍了Java NIO在客户端和服务器端的应用,包括NioClient和NioServerHandle类的详细代码实现,展示了如何进行网络读写操作。文章还讨论了为何通常不单独注册写事件,以及在高并发情况下如何优化处理。最后提到了单线程Reactor模式和多线程Reactor模式,并简单介绍了Netty框架的优势和基本使用。
摘要由CSDN通过智能技术生成

NIO编程

客户端

NIO客户端—NioClient

  • package cn.enjoyedu.nio.nio;
    
    import java.util.Scanner;
    
    import static cn.enjoyedu.nio.Const.DEFAULT_PORT;
    import static cn.enjoyedu.nio.Const.DEFAULT_SERVER_IP;
    
    
    /**
     * @author Mark老师  
     * 类说明:nio通信客户端
     */
    public class NioClient {
        private static NioClientHandle nioClientHandle;
    
        public static void start(){
            //nioClientHandle = new NioClientHandle(DEFAULT_SERVER_IP,DEFAULT_PORT);
            nioClientHandle = new NioClientHandle(DEFAULT_SERVER_IP,9999);
            new Thread(nioClientHandle,"Server").start();
        }
        //向服务器发送消息
        public static boolean sendMsg(String msg) throws Exception{
            nioClientHandle.sendMsg(msg);
            return true;
        }
        public static void main(String[] args) throws Exception {
            start();
            // 读取键盘的输入,并发送给服务器端
            Scanner scanner = new Scanner(System.in);
            while(NioClient.sendMsg(scanner.next()));
    
        }
    
    }
    
    

client里专门处理网络读写的类–

  • package cn.enjoyedu.nio.nio;
    
    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;
    import java.util.Set;
    
    /**
     * @author Mark老师  
     * 类说明:nio通信客户端处理器
     */
    public class NioClientHandle implements Runnable{
        private String host;
        private int port;
        private volatile boolean started;
        private Selector selector;
        private SocketChannel socketChannel;
    
        public NioClientHandle(String ip, int port) {
            this.host = ip;
            this.port = port;
    
            try {
                /*创建选择器的实例*/
                // 创建selector,不能new
                selector = Selector.open();
                /*创建SocketChannel的实例*/
                // 不再需要创建ServerSocketChannel
                // 因为客户端不需要监听
                // 所以只需要创建SocketChannel
                // 同时不需要绑定端口了
                socketChannel = SocketChannel.open();
                /*设置通道为非阻塞模式*/
                socketChannel.configureBlocking(false);
    
                started = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
        }
        public void stop(){
            started = false;
        }
        @Override
        public void run() {
            try{
                // 发起一个连接
                // 写成一个单独的方法
                doConnect();
            }catch(IOException e){
                e.printStackTrace();
                System.exit(1);
            }
    
            //循环遍历selector
            // 循环获取事件集,并做响应的处理
            while(started){
                try{
                    //无论是否有读写事件发生,selector每隔1s被唤醒一次
                    selector.select(1000);
                    //获取当前有哪些事件可以使用
                    Set<SelectionKey> keys = selector.selectedKeys();
                    //转换为迭代器
                    Iterator<SelectionKey> it = keys.iterator();
                    SelectionKey key = null;
                    while(it.hasNext()){
                        key = it.next();
                        /*我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
                        如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活
                        的键出现,这会导致我们尝试再次处理它。*/
                        it.remove();
                        try{
                            handleInput(key);
                        }catch(Exception e){
                            if(key != null){
                                key.cancel();
                                if(key.channel() != null){
                                    key.channel().close();
                                }
                            }
                        }
                    }
                }catch(Exception e){
                    e.printStackTrace();
                    System.exit(1);
                }
            }
            //selector关闭后会自动释放里面管理的资源
            if(selector != null)
                try{
                    selector.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
        }
    
        //具体的事件处理方法
        private void handleInput(SelectionKey key) throws IOException{
            if(key.isValid()){
                //获得关心当前事件的channel
                SocketChannel sc = (SocketChannel) key.channel();
                //连接事件
                // 相比服务端,多关注了一个连接事件
                if(key.isConnectable()){
                    // 如果连接完成,则注册一个读事件
                    if(sc.finishConnect()){
                        socketChannel.register(selector,
                            SelectionKey.OP_READ);}
                    else System.exit(1);
                }
                //有数据可读事件
                if(key.isReadable()){
                    //创建ByteBuffer,并开辟一个1M的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //读取请求码流,返回读取到的字节数
                    int readBytes = sc.read(buffer);
                    //读取到字节,对字节进行编解码
                    if(readBytes>0){
                        //将缓冲区当前的limit设置为position,position=0,
                        // 用于后续对缓冲区的读取操作
                        buffer.flip();
                        //根据缓冲区可读字节数创建字节数组
                        byte[] bytes = new byte[buffer.remaining()];
                        //将缓冲区可读字节数组复制到新建的数组中
                        buffer.get(bytes);
                        String result = new String(bytes,"UTF-8");
                        System.out.println("客户端收到消息:" + result);
                    }
                    //链路已经关闭,释放资源
                    else if(readBytes<0){
                        key.cancel();
                        sc.close();
                    }
                }
            }
        }
    
        // 写操作
        private void doWrite(SocketChannel channel,String request)
                throws IOException {
            //将消息编码为字节数组
            byte[] bytes = request.getBytes();
            //根据数组容量创建ByteBuffer
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            //将字节数组复制到缓冲区
            // 写模式
            writeBuffer.put(bytes);
            //flip操作
            // 切换成读模式
            writeBuffer.flip();
            //发送缓冲区的字节数组
            /*关心事件和读写网络并不冲突*/
            // 读模式
            channel.write(writeBuffer);
        }
    
        private void doConnect() throws IOException{
            /*非阻塞的连接*/
            // 建立连接后返回一个布尔值
            // 如果方法执行完,连接还没有完成?
            if(socketChannel.connect(new InetSocketAddress(host,port))){
                // 如果建立连接成功了,就只需要关注读写事件了
                socketChannel.register(selector,SelectionKey.OP_READ);
            }else{
                // 如果建立连接失败,就注册一个连接事件,当注册成功了再来通知我
                socketChannel.register(selector,SelectionKey.OP_CONNECT);
            }
        }
    
        //写数据对外暴露的API
        // 往socketChannel里面写数据
        public void sendMsg(String msg) throws Exception{
            doWrite(socketChannel, msg);
        }
    
    
    }
    
    

测试nio

  • 先启动NioServer
  • 再启动NioClient

为什么没有写事件

  • 改造服务端代码,之前是直接通过socketChannel.write()方法直接写的

新建类–创建写事件

  • 与之前的类相比,doWrite()、handleInput()有变化

  • package cn.enjoyedu.nio.nio;
    
    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;
    import java.util.Set;
    
    import static cn.enjoyedu.nio.Const.response;
    
    /**
     * @author Mark老师  
     * 类说明:nio通信服务端处理器
     */
    public class NioServerHandleWriteable implements Runnable{
        private Selector selector;
        private ServerSocketChannel serverChannel;
        private volatile boolean started;
        /**
         * 构造方法
         * @param port 指定要监听的端口号
         */
        public NioServerHandleWriteable(int port) {
            try{
                //创建选择器
                selector = Selector.open();
                //打开监听通道
                serverChannel = ServerSocketChannel.open();
                //如果为 true,则此通道将被置于阻塞模式;
                // 如果为 false,则此通道将被置于非阻塞模式
                serverChannel.configureBlocking(false);//开启非阻塞模式
                //绑定端口 backlog设为1024
                serverChannel.socket()
                        .bind(new InetSocketAddress(port),1024);
                //监听客户端连接请求
                serverChannel.register(selector, SelectionKey.OP_ACCEPT);
                //标记服务器已开启
                started = true;
                System.out.println("服务器已启动,端口号:" + port);
            }catch(IOException e){
                e.printStackTrace();
                System.exit(1);
            }
        }
    
        @Override
        public void run() {
            //循环遍历selector
            while(started){
                try{
                    //阻塞,只有当至少一个注册的事件发生的时候才会继续.
    				selector.select();
                    Set<SelectionKey> keys = selector.selectedKeys();
                    Iterator<SelectionKey> it = keys.iterator();
                    SelectionKey key = null;
                    while(it.hasNext()){
                        key = it.next();
                        it.remove();
                        try{
                            handleInput(key);
                        }catch(Exception e){
                            if(key != null){
                                key.cancel();
                                if(key.channel() != null){
                                    key.channel().close();
                                }
                            }
                        }
                    }
                }catch(Throwable t){
                    t.printStackTrace();
                }
            }
            //selector关闭后会自动释放里面管理的资源
            if(selector != null)
                try{
                    selector.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
        }
        private void handleInput(SelectionKey key) throws IOException{
            
            // 打印当前key上面有哪些事件
            System.out.println("当前通道的事件:"+ key.interestOps());
            if(key.isValid()){
                //处理新接入的请求消息
                if(key.isAcceptable()){
                    //获得关心当前事件的channel
                    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    //通过ServerSocketChannel的accept创建SocketChannel实例
                    //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
                    SocketChannel sc = ssc.accept();
                    System.out.println("======socket channel 建立连接=======");
                    //设置为非阻塞的
                    sc.configureBlocking(false);
                    //连接已经完成了,可以开始关心读事件了
                    sc.register(selector, SelectionKey.OP_READ);
                }
                //读消息
                if(key.isReadable()){
                    System.out.println("======socket channel 数据准备完成," +
                            "可以去读==读取=======");
                    SocketChannel sc = (SocketChannel) key.channel();
                    //创建ByteBuffer,并开辟一个1M的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //读取请求码流,返回读取到的字节数
                    int readBytes = sc.read(buffer);
                    //读取到字节,对字节进行编解码
                    if(readBytes>0){
                        //将缓冲区当前的limit设置为position,position=0,
                        // 用于后续对缓冲区的读取操作
                        buffer.flip();
                        //根据缓冲区可读字节数创建字节数组
                        byte[] bytes = new byte[buffer.remaining()];
                        //将缓冲区可读字节数组复制到新建的数组中
                        buffer.get(bytes);
                        String message = new String(bytes,"UTF-8");
                        System.out.println("服务器收到消息:" + message);
                        //处理数据
                        String result = response(message) ;
                        //发送应答消息
                        doWrite(sc,result);
                    }
                    //链路已经关闭,释放资源
                    else if(readBytes<0){
                        key.cancel();
                        sc.close();
                    }
                }
                
                // 修改2
                // 处理写事件
                if(key.isWritable()){
                    SocketChannel sc = (SocketChannel) key.channel();
                    // 从key上获得附着的buffer,并强制转型成ByteBuffer
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    // 判定这个buffer还有没有要写的
                    if(buffer.hasRemaining()){
                        // 往socketChannel里面写
                        int count = sc.write(buffer);
                        System.out.println("write :"+count
                                +"byte, remaining:"+buffer.hasRemaining());
                    }else{
                        /*取消对写的注册*/
                        key.interestOps(SelectionKey.OP_READ);
                    }
                }
            }
        }
        //发送应答消息
        private void doWrite(SocketChannel channel,String response)
                throws IOException {
            //将消息编码为字节数组
            byte[] bytes = response.getBytes();
            //根据数组容量创建ByteBuffer
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            //将字节数组复制到缓冲区
            writeBuffer.put(bytes);
            //flip操作
            writeBuffer.flip();
            // 修改1
            // 不是直接写,而是注册一个写事件
            // 但是光注册写时间不行,会把之前注册的读事件冲掉
            // 因此|上一个读事件,说明当前的channel既关注读事件,又关注写事件
            // 每一个selectKEy上还可以做附着,把writeBuffer作为附件附着到这个key上
            // 之后应对写事件,就不需要new一个buffer出来了,可以直接获得
            channel.register(selector,SelectionKey.OP_WRITE|SelectionKey.OP_READ,
                    writeBuffer);
        }
    
        public void stop(){
            started = false;
        }
    
    }
    
    
  • 再次按上面的步骤测试下

  • 打印结果

    • 当前通道的事件:16 ---------- accept事件
  • 测试2

    • 打印台输入Mark

      一直刷屏:当前通道的事件:5

      这也就是为什么很多书上不会去写操作写事件的代码!

    • 什么时候发生写事件?

      操作系统的发送缓冲区内有空位的时候,写事件就会不断的触发!selector就会不断触发写事件

      所以buffer里面没有数据了,就要把写事件注销掉-- key.interestOps(SelectionKey.OP_READ);只保存读事件,就相当于取消了写事件

    • 什么时候注册写事件

      当发送的数据有2m,buffer只有8k,必须拆分成很多份的8k,注册一个写事件,写完8K,再写8K…,一直发送完

原生JDK网络编程- NIO之单线程Reactor模式

反应器模式

  • 控制逆转,反应倒置
  • 在设计模式中,称之为好莱坞法则,不要调用我,而是我在合适的时候来调用你

解释

  • reactor是一个线程,这个线程里面会启动一个相关的事件循环
  • 并且通过之前讲的selector来实现io多路复用
  • 首先注册一个accept事件,产生一个socket,然后socket又在reactor里面注册一个关注读的事件
  • 当reactor监听到有读事件或者写事件发生时,进行一个相关的派发,就是handlerInput,交给不同的socketChannel进行相关的事件处理
  • 在这种模式里面,所有的事情都是在一个线程里面做的,不管是事件的处理,还是实际去读取网络上的数据,是一个单线程的reactor模式,
  • 而且我们的selector也是一个线程处理的,所有的事情,不管网络读写、发送、接受连接、具体业务数据的解析、都是由一个线程做的

原生JDK网络编程- NIO之单线程Reactor、工作者线程池模式

  • 把网络读写相关的数据拿出来,这个线程就只关注跟网络通讯有关的事,比如说接受连接、比如读数据、比如发送数据,而在实际情况中其余做的那些解码呀、业务的计算,把它拿出来之后,交给另外一个线程池来做。

  • 好处

    • 1.不是单线程处理全部任务,业务逻辑出了错,不会整个通讯功能挂掉
    • 2.专人做专事,负责网络读写的就负责网络读写,负责业务处理的就负责业务处理,这样相比之前一个线程而言,负责网络读写的线程就轻松了很多
  • 缺点

    • 1.所有的网络操作都是由一个线程来处理的,如果负载不高、并发不高,无所谓,如果是高负载、大并发、大数据量,线程忙着网络读写,这种情况这个线程可能支撑不住

原生JDK网络编程- NIO之多线程的Reactor模式

  • 接受连接单独用一个线程来做
  • 接受完连接之后,具体的每个服务器之上的每一个socketChannel和对端的通讯交给另外一个线程或者线程池来处理

反应器模式与观察者模式的区别?

  • 观察者模式
    • 被观察者发生变化时,依次去通知所有的观察者
  • 反应器模式
    • 会有很多很多的事件发生,在观察者模式里面只有一个事件发生,把这个事件通知所有的观察者,而在反应器模式里面,事件可能有很多个,而且每个时间接触到的人也是不相同的,一个是单事件源,一个是多事件源,而且在反应器模式里面,使用回调更多一点,当事件完成了,我们的接受者会注册事件处理的入口,交给我们的反应器,反应器进来直接调用关注事件的人的方法,告诉它直接取处理

Netty是什么?为什么要用Netty?

  • Netty是由JBOSS提供的一个java开源框架。

  • Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

  • 互联网公司必备 http://netty.io/wiki/adopters.html

  • 性能高

  • netty5已经停止开发了,基于aio开发的,linux上的aio没有真正实现,windows上是真正的实现,所以在linux上是没有意义的,所以现在的高版本是netty4

  • 当前用的代码用的是4.1.28.Final

  • 包名----io.netty

第一个Netty程序

  • Netty核心组件初步了解

    • EventLoop(Group) 、Channel

      EventLoop近似的看成一个线程,EventLoop(Group) 看成线程池;

      Channel对应于nio中的ServerSocketChannel、SocketChannel,负责具体的网络通讯

    • 事件和ChannelHandler、ChannelPipeline

      事件:去动态通知改变,netty创造的概念,根据事件的不同,来做相关业务的处理;

      入栈:网络数据传入到应用程序中进行处理;

      出栈:数据由应用程序处理完后重新发往网络,再发往通讯对端;

      网络和应用程序之间的流转,在流转过程中,事件需要进行处理,通过ChannelHandler来处理,这个handler可能有很多个,ChannelPipeline把这些handler装起来,相当于是保存handler的容器;

    • ChannelFuture

      在netty里面的操作,都是异步的,ChannelFuture代表某个方法的执行结果,在未来的某个时刻去拿某个方法的执行结果,类似于并发编程中的Future

netty服务器—EchoServer

  • package cn.enjoyedu.nettybasic.echo;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark/Maoke
     * 创建日期:2018/08/25
     * 类说明:基于Netty的服务器
     */
    public class EchoServer  {
    
        private final int port;
    
        public EchoServer(int port) {
            this.port = port;
        }
    
        public static void main(String[] args) throws InterruptedException {
            int port = 9999;
            EchoServer echoServer = new EchoServer(port);
            System.out.println("服务器即将启动");
            echoServer.start();
            System.out.println("服务器关闭");
        }
    
        public void start() throws InterruptedException {
            // 创建EchoServerHandler
            final EchoServerHandler serverHandler = new EchoServerHandler();
            /*线程组*/
            // EventLoopGroup相当于线程池的概念
            // 为什么用线程池?
            // 为了网络通讯和业务处理分离
            EventLoopGroup group  = new NioEventLoopGroup();
            try {
                /*服务端启动必备*/
                // 做初始化工作、引导化工作
                ServerBootstrap b = new ServerBootstrap();
                // b.group传入线程组
                // .channel(NioServerSocketChannel.class)指定使用nio
                // .localAddress(new InetSocketAddress(port))指定监听端口
                // .childHandler配置处理的handler
                b.group(group)
                 .channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(port)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 把新创建的handler加到SocketChannel里面
                        ch.pipeline().addLast(serverHandler);
                    }
                });
                // 异步绑定到服务器,sync()会阻塞到完成
                // .sync()保证完成
                ChannelFuture f = b.bind().sync();
                // 阻塞当前线程,直到服务器的ServerChannel被关闭
                f.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully().sync();
    
            }
    
    
        }
    
    
    }
    
    

EchoServerHandler–继承ChannelInboundHandlerAdapter

  • package cn.enjoyedu.nettybasic.echo;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerAdapter;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.CharsetUtil;
    
    @ChannelHandler.Sharable
    public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
        // 重写读方法---channelRead
        // 读取客户端传过来的数据,再回传回去
        // nio中的buffer对应于这里的ByteBuf
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf in = (ByteBuf)msg;
            System.out.println("Server accept: "+in.toString(CharsetUtil.UTF_8));
            // 回传给客户端
            ctx.writeAndFlush(in);
            //ctx.close();
        }
    
        // 发生异常时的对应处理
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    

netty客户端

  • package cn.enjoyedu.nettybasic.echo;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark/Maoke
     * 创建日期:2018/08/26
     * 类说明:基于Netty的客户端
     */
    public class EchoClient {
    
        private final int port;
        private final String host;
    
        public EchoClient(int port, String host) {
            this.port = port;
            this.host = host;
        }
    
        public void start() throws InterruptedException {
    
            /*线程组*/
            EventLoopGroup group  = new NioEventLoopGroup();
            try {
                /*客户端启动必备*/
                // 服务端要用Bootstrap
                Bootstrap b = new Bootstrap();
                b.group(group)
                    // 用NioSocketChannel.class
                    // 不能再用NioServerSocketChannel
                    // 不能用localAddress,而是要用remoteAddress
                    // 为什么客户端要用handler,不用childHandler?
                    // 服务端告诉netty,流经客户端的处理要用childHandler,客户端没有这个概念
                        .channel(NioSocketChannel.class)/*指定使用NIO的通信模式*/
                        .remoteAddress(new InetSocketAddress(host,port))/*指定服务器的IP地址和端口*/
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                // 把创建的handler加入到SocketChannel中
                                ch.pipeline().addLast(new EchoClientHandler());
    
                            }
                        });
                // 客户端不存在绑定,而是连接远端的服务器
                // 连接是异步的
                ChannelFuture f = b.connect().sync();/*异步连接到服务器,sync()会阻塞到完成*/
                f.channel().closeFuture().sync();/*阻塞当前线程,直到客户端的Channel被关闭*/
            } finally {
                group.shutdownGracefully().sync();
    
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 启动客户端
            new EchoClient(9999,"127.0.0.1").start();
        }
    }
    
    

EchoClientHandler—继承SimpleChannelInboundHandler

  • package cn.enjoyedu.nettybasic.echo;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    // SimpleChannelInboundHandler是更简单的实现
    public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        // 这个方法是继承父类强制重写的
        // 读取到网络数据后进行业务处理
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
            System.out.println("client Accept"+msg.toString(CharsetUtil.UTF_8));
            //ctx.close();
        }
    
        // 客户端和服务器建立连接后,客户端发出起始数据
        /*channel活跃后,做业务处理*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            // 往服务端发送数据
            // 发送的数据必须是buffer类型
            // Unpooled.copiedBuffer工具类把字符串转成buffer
            ctx.writeAndFlush(Unpooled.copiedBuffer(
                    "Hello,Netty",CharsetUtil.UTF_8));
        }
    }
    
    

测试1

  • 先启动EchoServer
  • 再启动EchoClient
  • 打印正常

测试2

  • 先启动EchoServer

  • 再启动NioClient

  • 报错,Failed to initialize channel

    • pipeline里面每一个socketChannel里面都会有handler,而且handler不能在每一个socketChannel里面共享的,

      怎么解决?

      1.像EchoClient,每次都是new一个handler出来

      2.在EchoServerHandler加@ChannelHandler.Sharable的注解

大多时候不注册写事件?

  • 因为写数据的buffer只要有空闲,就会触发一个写事件,所以都是单次全部直接写入,但是当写入的数据超过了buffer容量时,就必须要创建写事件,然后一次一次的写,直到写完
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值