• Java NIO 实现服务端和客户端的通信示例


    温馨提示:阅读本示例前首先需要对 Java NIO 的三大核心有一定了解

    • channel (通道
    • buffer (缓冲区
    • selector(选择器
      可以先看看 Java NIO Tutorial

    服务端

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.InetSocketAddress;
    import java.net.URL;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.nio.charset.StandardCharsets;
    import java.util.Iterator;
    
    public class NIOServer {
        static final Logger LOGGER = LoggerFactory.getLogger(NIOServer.class);
    
        public static void main(String[] args) throws IOException {
            int port = 9090;
            try {
                //1. 获取通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
                //设置为非阻塞才能注册到选择器。FileChannel不能切换到非阻塞模式
                ssChannel.configureBlocking(false);
    
                //2. 绑定端口
                ssChannel.bind(new InetSocketAddress(port));
    
                //4.  将通道注册到选择器上
                Selector selector = Selector.open();
                ssChannel.register(selector, SelectionKey.OP_ACCEPT);
    
                LOGGER.info("NIO服务已启动,绑定端口号为:" + port);
                //5. 通过轮询的方式,获取准备就绪的事件
                while (selector.select() > 0) {   //该方法会一直阻塞,直到至少有一个 SelectionKey 准备就绪
    
                    //6. 获取当前选择器中所有注册的 SelectionKey
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
    
                        SelectionKey key = iterator.next();
    
                        if (key.isAcceptable()) {
                            handleAccept(key);
                        } else if (key.isReadable()) {
                            handleRead(key);
                        }
    
                        //当SelectionKey 任务完成后需要移除,否则会一直执行这个key。
                        iterator.remove();
                    }
                }
                ssChannel.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        static void handleAccept(SelectionKey key) {
            try {
                LOGGER.info("接收就绪!");
    
                //7. 获取接受状态准备就绪的 selectionKey
                //调用accept 方法获取通道
                SocketChannel sChannel = ((ServerSocketChannel) key.channel()).accept();
    
                Selector selector = key.selector();
                //8. 将 sChannel 设置为非阻塞的
                sChannel.configureBlocking(false);
                //9. 将该通道注册到选择器上,让选择器能够监听这个通道
                sChannel.register(selector, SelectionKey.OP_READ);
                LOGGER.info("当前接收的连接: " + sChannel);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        static void handleRead(SelectionKey key) throws IOException {
            LOGGER.info("读取就绪!");
            //10. 获取 读状态 准备就绪的 selectionKey
            SocketChannel sChannel = (SocketChannel) key.channel();
            //创建缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1032);
            byte[] bytes = new byte[1024];
    
            int read = 0;
            StringBuilder receiveMessage = new StringBuilder();
            int temp;
            boolean statusSent = false;
    
            //从通道中循环读取数据写入缓冲区,直到达到流的末尾,也就是客户端主动关闭输出流(SocketChannel.shutdownOutput()),或者关闭通道。但由于稍后客户端还需要从服务端接收数据,因此客户端还不能关闭通道。
            while ((read = sChannel.read(buf)) != -1) {
                //该方法会让缓冲区切换到读取状态,即①ByteBuff.limit = ByteBuff.position; ②ByteBuff.position = 0;
                buf.flip();
                receiveMessage.append(StandardCharsets.UTF_8.newDecoder().decode(buf));
                //该方法让缓冲区清空,并准备下一次写入,即①ByteBuff.limit = ByteBuff.capacity; ②ByteBuff.position = 0;
                buf.clear();
            }
            //执行至此时,地址接收完毕
            String tpUrl = receiveMessage.toString();
            LOGGER.info("长度:[{}],完整url: [{}]", tpUrl.length(), tpUrl);
    
            URL url = new URL(tpUrl);
            InputStream inputStream = null;
            int responseCode = -1;
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setConnectTimeout(10000);
            try {
                inputStream = con.getInputStream();
                responseCode = con.getResponseCode();
            } catch (IOException e) {
                String format = String.format("图片资源不存在或网络不通!图片地址:%s,异常信息:%s,状态码:%d", tpUrl, e.getMessage(), responseCode);
                e.printStackTrace();
                LOGGER.error(format);
                //返回异常信息。我在这里自定义了服务器返回的第一个Int类型数据标识着当前请求成功与否1:成功,0:失败。
                buf.putInt(0);
                buf.put(format.getBytes(StandardCharsets.UTF_8));
                buf.flip();
                sChannel.write(buf);
                buf.clear();
                if (inputStream != null) {
                    inputStream.close();
                }
                //关闭通道,告知客户端写入已达到末尾,让它不再需要等待服务端。
                sChannel.close();
                return;
            }
    
            while ((temp = inputStream.read(bytes)) != -1) {
                if (!statusSent) {
                    //发送成功标识
                    buf.putInt(1);
                    statusSent = true;
                }
                //写入图片数据到缓冲区
                buf.put(bytes, 0, temp);
                //反转一下,准备读取
                buf.flip();
                sChannel.write(buf);
                //清空缓冲区,准备下一次写入
                buf.clear();
            }
    
            LOGGER.info("返回结束.");
            if (inputStream != null) {
                inputStream.close();
            }
            //关闭通道
            sChannel.close();
            LOGGER.info("通道已关闭");
            return;
        }
    }
    
    

    客户端

    import java.io.*;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    import java.nio.charset.StandardCharsets;
    
    public class NIOClient {
        public static void main(String[] args) throws FileNotFoundException {
            nioClient();
        }
    
        public static void nioClient() {
            try {
    
                String[] sends = {
                        "https://pic.cnblogs.com/avatar/1345194/20180308181944.png",
                        "https://pic.cnblogs.com/avatar/1345194/20180308181944.png"
                };
    
                for (int i = 0; i < sends.length; i++) {
                    File file = new File("D:\\test\\IOnNio\\" + i + ".png");
                    FileOutputStream outputStream = new FileOutputStream(file);
                    output(outputStream, sends[i]);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        static void output(OutputStream out, String url) throws IOException {
            //1. 获取通道
            SocketChannel sChannel = SocketChannel.open();
            //设置 10s 超时时间
            sChannel.socket().connect(new InetSocketAddress("127.0.0.1", 9090), 10000);
    
            //1.2 将阻塞的套接字 变为 非阻塞 的
            sChannel.configureBlocking(false);
    
            //2. 创建指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("发送:" + url);
            sChannel.write(ByteBuffer.wrap(url.getBytes()));
            // 主动关闭输出但不关闭通道,通知服务端当前发送请求已经完毕,等待服务端响应
            sChannel.shutdownOutput();
    
            int len = 0;
            int count = 0;
            int anInt = -1;
            boolean statusRead = false;
            //循环读取服务端返回到通道的数据,直到达到末尾
            while ((len = sChannel.read(buf)) != -1) {
                buf.flip();
                if (!statusRead && buf.limit() > 0) {
                    //读取第一位标识成功或失败的 Int 值
                    anInt = buf.getInt();
                    statusRead = true;
                }
                if (anInt == 1) {
                    if (len > 0) {
                        count += buf.limit() - buf.position();
                    }
                    //输出到本地磁盘
                    out.write(buf.array(), buf.position(), buf.limit() - buf.position());
    
                } else if (anInt == 0) {
                    //失败
                    System.out.println("服务端请求图片资源失败!");
                    System.out.println(new String(buf.array(), buf.position(), buf.limit() - buf.position(), StandardCharsets.UTF_8));
                }
                buf.clear();
            }
            out.close();
            System.out.println("返回最终大小:" + count);
            //关闭通道
            sChannel.close();
        }
    }
    

    日志依赖与配置

            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.21</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.21</version>
            </dependency>
    
    #log4j.properties
    log4j.rootLogger=DEBUG, f, console
    log4j.appender.f=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.f.File =logs/pic-download.log
    log4j.appender.f.Append = true
    log4j.appender.f.DatePattern='.'yyyy-MM-dd
    log4j.appender.f.layout=org.apache.log4j.PatternLayout
    log4j.appender.f.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%c{1}]: %m%n
    
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.target=System.out
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%c{1}]: %m%n
    
    
  • 相关阅读:
    Eclipse部署项目到Tomcat中,class文件夹为空的解决方案
    微软(北京).NET俱乐部活动 (2010年6月26日) – Visual Studio 2010 /*LIFE RUNS ON CODE*/
    失望的Vista SP1
    急聘BI DW OLAP开发工程师 (北京)
    急聘.NET开发工程师 (北京)
    开篇
    Windows Vista User Account Control (UAC) 全新安全模块“用户帐户控制”
    Tidy your desktop
    [导入]Vista的屏幕截图小工具:Snipping Tool
    微软发布官方TFS 2010 Scrum 模板
  • 原文地址:https://www.cnblogs.com/deemoo/p/15592210.html
Copyright © 2020-2023  润新知