zoukankan      html  css  js  c++  java
  • Java NIO系列教程(十)client和server 示例

    //客户但
    package com.example.demo.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.SocketChannel;
    import java.util.Iterator;
    import java.util.Set;

    public class NIOClient {
    private static boolean isEnding = false;
    //表示数字
    private static int flag= 0;
    //缓冲区大小
    private static int BLOCK = 4096;
    //接受数据缓冲区
    private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
    //发送数据缓冲区
    private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
    //服务器地址
    private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost",8888);

    public static void main(String[] args) throws IOException {
    //打开socker通道
    SocketChannel socketChannel = SocketChannel.open();
    //设置为非阻塞模式
    socketChannel.configureBlocking(false);
    //打开选择器
    Selector selector = Selector.open();
    //注册连接服务端socket动作
    socketChannel.register(selector,SelectionKey.OP_CONNECT);
    //连接
    socketChannel.connect(SERVER_ADDRESS);
    //分配缓冲区内存大小
    Set<SelectionKey> selectionKeys;
    Iterator<SelectionKey> iterator;
    SelectionKey selectionKey;
    SocketChannel client;
    String receiveText;
    String sendText;
    int count = 0;
    while (!isEnding){
    //选择一组键,其对应的通道已为 I/O 操作准备就绪
    //此方法执行处于阻塞模式的选择操作
    selector.select();
    //返回此选择器的已选择的键集
    selectionKeys = selector.selectedKeys();
    iterator = selectionKeys.iterator();
    while (iterator.hasNext()){
    selectionKey = iterator.next();
    if(selectionKey.isConnectable()){
    System.out.println("client connect");
    client = (SocketChannel) selectionKey.channel();
    //判断此通道伤是否正在进行连接操作
    //完成套接字通道的连接过程
    if(client.isConnectionPending()){
    client.finishConnect();
    System.out.println("完成连接!");
    sendbuffer.clear();
    sendbuffer.put("Hello,Server".getBytes());
    sendbuffer.flip();
    client.write(sendbuffer);
    }
    client.register(selector,SelectionKey.OP_READ);
    }else if(selectionKey.isReadable()){
    client = (SocketChannel) selectionKey.channel();
    receiveBuffer.clear();
    count = client.read(receiveBuffer);
    if(count >0){
    receiveText = new String(receiveBuffer.array(),0,count);
    System.out.println("客户端接受服务器端的数据--:"+receiveText);
    client.register(selector,SelectionKey.OP_WRITE);
    }
    }else if (selectionKey.isWritable()) {
    sendbuffer.clear();
    client = (SocketChannel) selectionKey.channel();
    sendText = "message from client--" + (flag++);
    sendbuffer.put(sendText.getBytes());
    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
    sendbuffer.flip();
    client.write(sendbuffer);
    System.out.println("客户端向服务器端发送数据--:"+sendText);
    client.register(selector, SelectionKey.OP_READ);
    isEnding = true;
    }
    }
    selectionKeys.clear();
    }
    selector.close();
    socketChannel.close();
    }
    }

    //服务端
    package com.example.demo.nio;

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    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 NIOServer {
    //标识数字
    private int flag = 0;
    //缓冲区大小
    private int BLOCK = 4096;
    //接收数据缓冲区
    private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
    //发送数据缓冲区
    private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);

    private Selector selector;

    public NIOServer(int port) throws IOException{
    /**
    * 以下的所有说明均已linux系统底层进行说明:
    * nio 的底层实现是 epoll 模式,采用多路复用技术,对nio的代码进行深入分析,结合epoll的底层实现
    * 进行详细的说明
    * 1.linux网络编程是两个进程之间的通信,跨集群合网络
    * 2.开启一个socket线程,在linux系统上任何操作均以文件句柄数表示,默认情况下
    * 一个线程可以打开1024个句柄,也就说最多同时支持1024个网络连接请求。阿里云默认打开65535个文件
    * 句柄,通常情况下,1G内存最多可以打开10w个句柄数
    *
    *
    */
    //打开服务器套接字通道
    //底层;在linux上面开启socker服务,启动一个线程,绑定ip地址和端口号
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //服务器配置为非阻塞
    serverSocketChannel.configureBlocking(false);
    //检索与此通道关联的服务器套接字
    ServerSocket serverSocket = serverSocketChannel.socket();
    //进行服务绑定
    serverSocket.bind(new InetSocketAddress(port));
    //通过open()方法找到Selector
    //底层:开启epoll,为当前socket服务创建epoll服务,epoll_create
    selector = Selector.open();
    //注册到selector
    /**
    * 底层:
    * 1.将当前的epoll,服务器地址,端口号绑定,如果有连接请求,直接添加到epoll中,epoll的底层是红黑树,
    * 可以快速的实现连接的查找和状态更新。如果有新的连接过来,直接存放到epoll中。如果有连接过期,中断,
    * 会从epoll中删除。
    * 2.通过epoll_ctl添加到epoll的同时,会注册一个回调函数给内核,当网卡有数据来的时候,会通知内核,内核
    * 调用回调函数,将当前内核数据的事件状态添加到list链表中
    */
    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    System.out.println("Server Start:"+port);
    }

    //监听
    private void listen() throws IOException{
    while (true){
    //选择一组键,并且相应得通道已经打开
    /**
    * epoll底层维护一个链表,rdlist,基于事件驱动模式,当网卡有数据请求过来,会发起硬件中断,通知内核已经有来了。内核调用
    * 回调函数,将当前的事件添加到rdlist中,将当前可用的rdlist列表发送给用户态,用户去遍历rdlist中的事件,进行处理
    */
    int readyChannels = selector.select();
    if(readyChannels == 0) continue;
    //返回此选择器得已选择键集
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()){
    SelectionKey selectionKey = iterator.next();
    //获取当前epoll的rdlist复制到用户态,遍历,同时删除当前rdlist中的事件
    iterator.remove();
    handleKEY(selectionKey);
    }
    }
    }

    //处理请求
    private void handleKEY(SelectionKey selectionKey) throws IOException {
    //接受请求
    ServerSocketChannel server = null;
    SocketChannel client = null;
    String receiveText;
    String sendText;
    int count = 0;
    //测试此键的通道是否已准备好接受新的套接字连接
    if(selectionKey.isAcceptable()){
    System.out.println("测试此键的通道是否已准备好接受新的套接字连接");
    //返回为止创建此键的通道
    server = (ServerSocketChannel) selectionKey.channel();
    //接受次通道套接字的连接
    //此方法返回的套接字通道(如果有)将处于阻塞模式
    client = server.accept();
    //配置为非阻塞
    client.configureBlocking(false);
    //注册到selector,等待连接
    client.register(selector,SelectionKey.OP_READ);
    }else if(selectionKey.isReadable()){
    //返回为之创建此键的通道
    client = (SocketChannel) selectionKey.channel();
    //将缓冲区清空以备下次读取
    receiveBuffer.clear();
    //读取服务器发送来的数据到缓冲区
    count = client.read(receiveBuffer);
    if(count>0){
    receiveText = new String(receiveBuffer.array(),0,count);
    System.out.println("服务器端接受客户端数据--:"+receiveText);
    client.register(selector,SelectionKey.OP_WRITE);
    }
    }else if(selectionKey.isWritable()){
    //将缓冲区清空以备下次写入
    sendBuffer.clear();
    //返回为之创建此键的通道
    client = (SocketChannel) selectionKey.channel();
    sendText = "message form server --"+flag++;
    //向缓冲区中输入数据
    sendBuffer.put(sendText.getBytes());
    //将缓冲区个标志复位,因为李米娜put了数据标志被改变要想从中读取数据发向服务器,就要复位
    sendBuffer.flip();
    //输出到通道
    client.write(sendBuffer);
    System.out.println("服务器端向客服端发送数据--:"+sendText);
    client.register(selector,SelectionKey.OP_READ);
    }
    }

    public static void main(String[] args) throws IOException {
    int port = 8888;
    NIOServer server = new NIOServer(port);
    server.listen();
    }
    }
  • 相关阅读:
    Linux:目录结构
    Linux安装日志(anaconda-ks.cfg、install.log、install.log.syslog)
    Docker:Dockerfile基础知识
    Docker:容器数据卷
    多线程设计模式:两阶段终止模式
    多线程:Thread中的常见方法
    多线程:查看进程线程方法
    多线程:创建线程
    Apollo:工作原理 核心概念
    Apollo:环境搭建
  • 原文地址:https://www.cnblogs.com/chenhg/p/13231538.html
Copyright © 2011-2022 走看看