1-2 非阻塞I/O 和基于Netty的客户端与服务端通信

本文介绍非阻塞I/O模型并通过Java代码实现客户端和服务端通信过程。进一步探讨了基于Netty框架实现的异步通信机制,包括Channel、EventLoop及ChannelFuture等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

非阻塞I/O 

å¨è¿éæå¥å¾çæè¿°

Client.java

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main (String ... argas){
        ClientHandle clientHandle = null;
        try {
            clientHandle=new ClientHandle("127.0.0.1",9092);
            new Thread(clientHandle,"CLIENT-THREAD").start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

 

ClientHandle.java

import javax.crypto.SecretKey;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ClientHandle implements Runnable{
    private int port;

    private volatile boolean stop;

    private Selector selector;
    private String ip;
    private SocketChannel socketChannel;

    public ClientHandle(String ip ,int port)throws IOException{
        this.ip=ip;
        this.port=port;
        selector=Selector.open();
        socketChannel=SocketChannel.open();
        socketChannel.configureBlocking(false);
    }


    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (!stop){
            try {
                selector.select(2000);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey key=iterator.next();
                iterator.remove();
                try {
                    handleKey(key);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //多路复用器关闭后,所有注册在上面的channel和pipe等资源都会自动去注册并关闭,不会浪费资源
        if(selector !=null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void handleKey(SelectionKey key) throws IOException {
        if(key.isValid()){
            SocketChannel sc = (SocketChannel) key.channel();
            if(key.isConnectable()){
                if(sc.finishConnect()){
                    sc.register(selector,SelectionKey.OP_READ);
                    doWrite(sc);
                }else{
                    System.exit(1);
                }
            }
            if(key.isReadable()){
                ByteBuffer buffer= ByteBuffer.allocate(1024);
                int read = sc.read(buffer);
                if(read > 0 ){
                    //buffer长度是1024 ,假如但是实际存储的位置10,那我要把0-10的数据读取出来,上线limit设置为10
                    buffer.flip();
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    String body = new String(bytes,"UTF-8");
                    System.out.println("客户端接收到服务数据,内容:"+body);
                    this.stop=true;
                }else if(read <0){
                    key.cancel();
                    sc.close();
                }
            }
        }
    }


    public void doConnect() throws IOException{
        //建立间接成功,注册读取时间
        if(socketChannel.connect(new InetSocketAddress(ip,port))){
            socketChannel.register(selector,SelectionKey.OP_READ);
            //成功发送数据给服务器
            doWrite(socketChannel);
        }else {
            //失败继续注册连事件
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
        }
    }
    private void doWrite(SocketChannel socketChannel) throws IOException {
        byte[] bytes = "我是客户端".getBytes();
        ByteBuffer buffer=ByteBuffer.allocate(bytes.length);
        buffer.put(bytes);
        buffer.flip();
        socketChannel.write(buffer);
        //告诉我是否已经达到缓冲区的上界
        if(!buffer.hasRemaining()){
            System.out.println("客户端发送完毕");
        }
    }
}

 

Server.java

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

public class Server {
    public static void main (String ... args ){
        ServerHandle serverHandle = null;
        try {
            serverHandle = new ServerHandle(9092);
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread(serverHandle,"SERVER-THREAD").start();
    }
}

 

ServerHandle.java

 

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class ServerHandle implements Runnable {
    private int port;

    private volatile boolean stop;

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;

    public ServerHandle(int port) throws IOException {
        this.port = port;
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        //设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //监听端口
        serverSocketChannel.bind(new InetSocketAddress(port));
        //注册ACCEPT 事件,表示关注客户端的注册事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器初始化完毕");
    }

    public void stop() {
        this.stop = true;
    }

    @Override
    public void run() {
        while (!stop) {
            try {
                //每隔1s检查一次
                selector.select(1000);
                //获取到已经准备的keys,就是已经准备好的事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    //获取单个事件
                    SelectionKey key = iterator.next();
                    //处理key
                    try {
                        handleKey(key);
                    } catch (Exception ex) {
                        //出异常了,取消key
                        if (null != key) {
                            key.cancel();
                            if (key.channel() != null) {
                                try {
                                    key.channel().close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }finally {
                        //处理后,把key移除掉
                        iterator.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != selector) {
            try {
                //多路复用器关闭后,所有注册在复用器上面的channel和pipe都会自动关闭
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    private void handleKey(SelectionKey key) throws IOException {
        //判断key是否有效
        if (key.isValid()) {
            //key 连接状态
            if(key.isAcceptable()){
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                //连接后,注册OP_READ事件
                sc.register(selector, SelectionKey.OP_READ);
                System.out.println("客户端来连接了,帮他注册读事件");
            }
            //key是否可读?
            if(key.isReadable()){
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //读取数据
                int readData = sc.read(buffer);
                // > 0读到了数据
                if(readData>0){
                    //将指针置位0
                    buffer.flip();
                    //buffer.reamining()返回流中可用的剩余数据长度
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    String body = new String(bytes,"UTF-8");
                    System.out.println("服务器:接收到客户端的数据,内容:"+body);
                    //发送数据给客户端
                    doWrite(sc,"现在时间是:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
                    //将key标记为可读
                    key.interestOps(SelectionKey.OP_READ);
                }else if(readData<0){
                    //链路关闭,取消key,关闭sc
                    key.cancel();
                    sc.close();
                }else {
                    //readData=0 没有取得数据
                }
            }
        }
    }

    private void doWrite(SocketChannel socketChannel, String hello) throws IOException {
       if(hello!=null){
           byte[] bytes = hello.getBytes();
           //在JVM内存中开辟一块内存 Dirtect方法是在系统内存中开辟
           ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
           buffer.put(bytes);
           buffer.flip();
           socketChannel.write(buffer);
       }
    }
}

基于Netty的客户端与服务端通信

先了解Netty的主要结构

原 https://blog.youkuaiyun.com/H_crab/article/details/96484433

关于ChannelFuture https://blog.youkuaiyun.com/liyuan0323/article/details/79145647

Channel接口

//管道

基本的I/O操作(bing()、connect()、read()、和write())依赖于底层网络传输所提供的原始。在基于Java的网络编程中,其基本的构造是class Socket。Netty的Channel接口所提供的API,大大地降低了直接使用Socket类的复杂性。此外,Channel也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单:

  • EmbeddedChannel;
  • LocalServerChannel;
  • NioDatagramChannel;
  • NioSctpChannel;
  • NioSocketChannel;

EventLoop接口

//处理事件

EventLoop定义了Netty的核心抽象,用于处理连接的生命周期中所发生的事件。

  • 一个EventLoopGroup包含一个或者多个EventLoop;
  • 一个EventLoop在它的生命周期内只和一个Thread绑定;
  • 所有由EventLoop处理得I/O事件都将在它专有的Thread上处理
  • 一个Channel在它的生命周期内只注册一个EventLoop;
  • 一个EventLoop可能会被分配给一个或多个Channel。

注意,一个给定的Channel的I/O操作都是由相同的Thread执行的,实际上消除了对于同步的需要

ChannelFuture接口

//ChannelFuture的作用是用来保存Channel异步操作的结果。

Netty所有的I/O操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后得某个时间点确定其结果的方法。为此,Netty提供了ChannelFuture接口,其addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。

Server.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;


public class Server {
    public static void main (String ... args ){
        int port = 9092;
        new Server().bind(port);
    }
    private void bind(int port){
        EventLoopGroup bossGroup = new NioEventLoopGroup();//1 接受请求
        EventLoopGroup workGorup = new NioEventLoopGroup();//2 实际处理
        ServerBootstrap bootstrap = new ServerBootstrap();
        ChannelFuture channelFuture = null;//3
        try {
            channelFuture = bootstrap.group(bossGroup,workGorup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    //ChannelOption.参考
                    //https://www.cnblogs.com/googlemeoften/p/6082785.html
                    .childHandler(new ServerChannelHandle())
                    .bind(port).sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            channelFuture.channel().closeFuture().sync();//4
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGorup.shutdownGracefully();
            bootstrap.clone();
        }
    }
}

ServerChannelHandle.java

import io.netty.channel.ChannelInitializer;
//netty下面的SocketChannel 非nio下面的
import io.netty.channel.socket.SocketChannel;

public class ServerChannelHandle extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new ServerHandle());
    }
}

ServerHandle.java

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.buffer.ByteBuf;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class ServerHandle extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws UnsupportedEncodingException {
        ByteBuf buffer = (ByteBuf) msg;
        byte[] bytes = new byte[buffer.readableBytes()];
        buffer.readBytes(bytes);
        String body = new String(bytes,"UTF-8");
        System.out.println("客户端发送的数据-》"+body);
        String res = "现在时间是:"+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
        ByteBuf byteBuf = Unpooled.copiedBuffer(res.getBytes());
        ctx.writeAndFlush(byteBuf);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx){
        System.out.println("EchoServerHandler.channelReadComplete");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
        cause.printStackTrace();
        ctx.close();
    }
}

Client.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main (String ... argas){
        int port = 9092;
        String host = "127.0.0.1";
        new Client().connection(host,port);
    }
    private void connection(String host,int port){
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        Bootstrap b = bootstrap.group(group).channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY,true)
                .handler(new ChindChannelHandle());
        try {
            ChannelFuture f = b.connect(host,port).sync();
            System.out.println("客户端启动完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

ClientHandle.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.UnsupportedEncodingException;

public class ClientHandle extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg ) throws UnsupportedEncodingException {
        ByteBuf buffer = (ByteBuf) msg;
        byte[] bytes = new byte[buffer.readableBytes()];
        buffer.readBytes(bytes);
        String body = new String (bytes,"UTF-8");
        System.out.println("服务器返回-》"+body);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx){
        ByteBuf buffer = Unpooled.copiedBuffer("我是客户端".getBytes());
        ctx.writeAndFlush(buffer);
    }


}

ChindChannelHandle.java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;

public class ChindChannelHandle extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new ClientHandle());
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值