Java NIO服务器端开发详解

一、NIO类库简介

  1、缓冲区Buffer

  Buffer是一个对象,包含一些要写入和读出的数据。

  在NIO中,所有的数据都是用缓冲区处理的,读取数据时,它是从通道(Channel)直接读到缓冲区中,在写入数据时,也是从缓冲区写入到通道。

  缓冲区实质上是一个数组,通常是一个字节数组(ByteBuffer),也可以是其它类型的数组,此外缓冲区还提供了对数据的结构化访问以及维护读写位置等信息。

  Buffer类的继承关系如下图所示:

2、通道Channel

  Channel是一个通道,网络数据通过Channel读取和写入。通道和流的不同之处在于通道是双向的(通道可以用于读、写后者二者同时进行),流只是在一个方向上移动。

  Channel大体上可以分为两类:用于网络读写的SelectableChannel(ServerSocketChannel和SocketChannel就是其子类)、用于文件操作的FileChannel。

  下面的例子给出通过FileChannel来向文件中写入数据、从文件中读取数据,将文件数据拷贝到另一个文件中:

public class NioTest
{
  public static void main(String[] args) throws IOException
  {
    copyFile();
  }
  //拷贝文件
  private static void copyFile()
  {
    FileInputStream in=null;
    FileOutputStream out=null;
    try
    {
      in=new FileInputStream("src/main/java/data/in-data.txt");
      out=new FileOutputStream("src/main/java/data/out-data.txt");
      FileChannel inChannel=in.getChannel();
      FileChannel outChannel=out.getChannel();
      ByteBuffer buffer=ByteBuffer.allocate(1024);
      int bytesRead = inChannel.read(buffer);
      while (bytesRead!=-1)
      {
        buffer.flip();
        outChannel.write(buffer);
        buffer.clear();
        bytesRead = inChannel.read(buffer);
      }
    }
    catch (FileNotFoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  //写文件
  private static void writeFileNio()
  {
    try
    {
      RandomAccessFile fout = new RandomAccessFile("src/main/java/data/nio-data.txt", "rw");
      FileChannel fc=fout.getChannel();
      ByteBuffer buffer=ByteBuffer.allocate(1024);
      buffer.put("hi123".getBytes());
      buffer.flip();
      try
      {
        fc.write(buffer);
      } catch (IOException e)
      {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    catch (FileNotFoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  //读文件
  private static void readFileNio()
  {
    FileInputStream fileInputStream;
    try
    {
      fileInputStream = new FileInputStream("src/main/java/data/nio-data.txt");
      FileChannel fileChannel=fileInputStream.getChannel();//从 FileInputStream 获取通道
      ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//创建缓冲区
      int bytesRead=fileChannel.read(byteBuffer);//将数据读到缓冲区
      while(bytesRead!=-1)
      {
        /*limit=position
         * position=0;
         */
        byteBuffer.flip();
        //hasRemaining():告知在当前位置和限制之间是否有元素
        while (byteBuffer.hasRemaining())
        {
          System.out.print((char) byteBuffer.get());
        }
        /*
         * 清空缓冲区
         * position=0;
         * limit=capacity;
         */
        byteBuffer.clear();
        bytesRead = fileChannel.read(byteBuffer);
      }
    } catch (FileNotFoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

3、多路复用器Selector

  多路复用器提供选择已经就绪的任务的能力。Selector会不断的轮询注册在其上的Channel,如果某个Channel上面发送读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

  一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替了传统的select实现,所以它没有最大连接句柄1024/2048的限制,意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。其模型如下图所示:

用单线程处理一个Selector。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

  注:

  1、什么select模型?

  select是事件触发机制,当等待的事件发生就触发进行处理,多用于Linux实现的服务器对客户端的处理。

  可以阻塞地同时探测一组支持非阻塞的IO设备,是否有事件发生(如可读、可写,有高优先级错误输出等),直至某一个设备触发了事件或者超过了指定的等待时间。也就是它们的职责不是做IO,而是帮助调用者寻找当前就绪的设备。

  2、什么是epoll模型?

  epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个wait。此外,内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。

二、NIO服务器端开发

  功能说明:开启服务器端,对每一个接入的客户端都向其发送hello字符串。

  使用NIO进行服务器端开发主要有以下几个步骤:

  1、创建ServerSocketChannel,配置它为非阻塞模式

  serverSocketChannel = ServerSocketChannel.open();
  serverSocketChannel.configureBlocking(false);

  2、绑定监听,配置TCP参数,如backlog大小

  serverSocketChannel.socket().bind(new InetSocketAddress(8080));

  3、创建一个独立的I/O线程,用于轮询多路复用器Selector

  4、创建Selector,将之前创建的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT

  selector=Selector.open();
  serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

  5、启动I/O线程,在循环体内执行Selector.select()方法,轮询就绪的Channel

while(true)
  {
    try
    {
      //select()阻塞到至少有一个通道在你注册的事件上就绪了
      //如果没有准备好的channel,就在这一直阻塞
      //select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
      selector.select();
    }
    catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
      break;
     }
 }

  6、当轮询到了处于就绪状态的Channel时,需对其进行判断,如果是OP_ACCEPT状态,说明是新的客户端接入,则调用ServerSocketChannel.accept()方法接受新的客户端

//返回已经就绪的SelectionKey,然后迭代执行
      Set<SelectionKey> readKeys=selector.selectedKeys();
      for(Iterator<SelectionKey> it=readKeys.iterator();it.hasNext();)
      {
        SelectionKey key=it.next();
        it.remove();
        try
        {
          if(key.isAcceptable())
          {
            ServerSocketChannel server=(ServerSocketChannel) key.channel();
            SocketChannel client=server.accept();
            client.configureBlocking(false);
            client.register(selector,SelectionKey.OP_WRITE);
          }
          else if(key.isWritable())
          {
            SocketChannel client=(SocketChannel) key.channel();
            ByteBuffer buffer=ByteBuffer.allocate(20);
            String str="hello";
            buffer=ByteBuffer.wrap(str.getBytes());
            client.write(buffer);
            key.cancel();
          }
        }catch(IOException e)
        {
          e.printStackTrace();
          key.cancel();
          try
          {
            key.channel().close();
          } catch (IOException e1)
          {
            // TODO Auto-generated catch block
            e1.printStackTrace();
          }

        }
      }

 7、设置新接入的客户端链路SocketChannel为非阻塞模式,配置其他的一些TCP参数

if(key.isAcceptable())
  {
    ServerSocketChannel server=(ServerSocketChannel) key.channel();
    SocketChannel client=server.accept();
    client.configureBlocking(false);
    ...
  }

  8、将SocketChannel注册到Selector,监听OP_WRITE

  client.register(selector,SelectionKey.OP_WRITE);

  9、如果轮询的Channel为OP_WRITE,则说明要向SockChannel中写入数据,则构造ByteBuffer对象,写入数据包

else if(key.isWritable())
  {
    SocketChannel client=(SocketChannel) key.channel();
    ByteBuffer buffer=ByteBuffer.allocate(20);
    String str="hello";
    buffer=ByteBuffer.wrap(str.getBytes());
    client.write(buffer);
    key.cancel();
  }

 完整代码如下:

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;
public class ServerSocketChannelDemo
{
	public static void main(String[] args)
	  {
		ServerSocketChannel serverSocketChannel;
		Selector selector=null;
		try
		    {
			serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.configureBlocking(false);
			serverSocketChannel.socket().bind(new InetSocketAddress(8080));
			selector=Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		}
		catch (IOException e)
		    {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while(true)
		    {
			try
			      {
				//select()阻塞到至少有一个通道在你注册的事件上就绪了
				//如果没有准备好的channel,就在这一直阻塞
				//select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
				selector.select();
			}
			catch (IOException e)
			      {
				// TODO Auto-generated catch block
				e.printStackTrace();
				break;
			}
			//返回已经就绪的SelectionKey,然后迭代执行
			Set<SelectionKey> readKeys=selector.selectedKeys();
			for (Iterator<SelectionKey> it=readKeys.iterator();it.hasNext();)
			      {
				SelectionKey key=it.next();
				it.remove();
				try
				        {
					if(key.isAcceptable())
					          {
						ServerSocketChannel server=(ServerSocketChannel) key.channel();
						SocketChannel client=server.accept();
						client.configureBlocking(false);
						client.register(selector,SelectionKey.OP_WRITE);
					} else if(key.isWritable())
					          {
						SocketChannel client=(SocketChannel) key.channel();
						ByteBuffer buffer=ByteBuffer.allocate(20);
						String str="hello";
						buffer=ByteBuffer.wrap(str.getBytes());
						client.write(buffer);
						key.cancel();
					}
				}
				catch(IOException e)
				        {
					e.printStackTrace();
					key.cancel();
					try
					          {
						key.channel().close();
					}
					catch (IOException e1)
					          {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		}
	}
}

我们用telnet localhost 8080模拟出多个客户端:

程序运行结果如下:

总结

以上就是本文关于Java NIO服务器端开发详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • JAVA-4NIO之Channel之间的数据传输方法

    在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel. transferFrom():被动接收 FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中). 方法的输入参数position表示从position处开始向目标文件写入数据,cou

  • JAVA-NIO之Socket/ServerSocket Channel(详解)

    一.ServerSocketChannel Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样.ServerSocketChannel类在 java.nio.channels包中. 打开 ServerSocketChannel 通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel. 关闭 ServerSocketChannel 通过调用Se

  • Java NIO:浅析IO模型_动力节点Java学院整理

    也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗.在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型.下面本文先从同步和异步的概念 说起,然后接着阐述了阻塞和非阻塞的区别,接着介绍了阻塞IO和非阻塞IO的区别,然后介绍了同步IO和异步IO的区别,接下来介绍了5种IO模型,最后介绍了两种和高性能IO设计相关的设计模式(Reactor和Proactor). 以下是本文的目录大纲: 一.什么是同步?什么是异步? 二.什么是阻塞?什么是非阻塞

  • Java中网络IO的实现方式(BIO、NIO、AIO)介绍

    在网络编程中,接触到最多的就是利用Socket进行网络通信开发.在Java中主要是以下三种实现方式BIO.NIO.AIO. 关于这三个概念的辨析以前一直都是好像懂,但是表达的不是很清楚,下面做个总结完全辨析清楚. 1. BIO方式 首先我用一个较为通俗的语言来说明: BIO 就是阻塞IO,每个TCP连接进来服务端都需要创建一个线程来建立连接并进行消息的处理.如果中间发生了阻塞(比如建立连接.读数据.写数据时发生阻碍),线程也会发生阻塞,并发情况下,N个连接需要N个线程来处理. 这种方式的缺点就是

  • JDK1.7 之java.nio.file.Files 读取文件仅需一行代码实现

    JDK1.7中引入了新的文件操作类java.nio.file这个包,其中有个Files类它包含了很多有用的方法来操作文件,比如检查文件是否为隐藏文件,或者是检查文件是否为只读文件.开发者还可以使用Files.readAllBytes(Path)方法把整个文件读入内存,此方法返回一个字节数组,还可以把结果传递给String的构造器,以便创建字符串输出.此方法确保了当读入文件的所有字节内容时,无论是否出现IO异常或其它的未检查异常,资源都会关闭.这意味着在读文件到最后的块内容后,无需关闭文件.要注意

  • Java NIO实例UDP发送接收数据代码分享

    Java的NIO包中,有一个专门用于发送UDP数据包的类:DatagramChannel,UDP是一种无连接的网络协议, 一般用于发送一些准确度要求不太高的数据等. 完整的服务端程序如下: public class StatisticsServer { //每次发送接收的数据包大小 private final int MAX_BUFF_SIZE = 1024 * 10; //服务端监听端口,客户端也通过该端口发送数据 private int port; private DatagramChann

  • Java使用NioSocket手动实现HTTP服务器

    NioSocket简单复习 重要概念 NioSocket里面的三个重要概念:Buffer.Channel.Selector Buffer为要传输的数据 Channel为传输数据的通道 Selector为通道的分配调度者 使用步骤 使用NioSocket实现通信大概如以下步骤: ServerSocketChannel可以通过configureBlocking方法来设置是否采用阻塞模式,设置为false后就可以调用register注册Selector,阻塞模式下不可以用Selector. 注册后,S

  • Java NIO Path接口和Files类配合操作文件的实例

    Path接口 1.Path表示的是一个目录名序列,其后还可以跟着一个文件名,路径中第一个部件是根部件时就是绝对路径,例如 / 或 C:\ ,而允许访问的根部件取决于文件系统: 2.以根部件开始的路径是绝对路径,否则就是相对路径: 3.静态的Paths.get方法接受一个或多个字符串,字符串之间自动使用默认文件系统的路径分隔符连接起来(Unix是 /,Windows是 \ ),这就解决了跨平台的问题,接着解析连接起来的结果,如果不是合法路径就抛出InvalidPathException异常,否则就

  • Java NIO服务器端开发详解

    一.NIO类库简介 1.缓冲区Buffer Buffer是一个对象,包含一些要写入和读出的数据. 在NIO中,所有的数据都是用缓冲区处理的,读取数据时,它是从通道(Channel)直接读到缓冲区中,在写入数据时,也是从缓冲区写入到通道. 缓冲区实质上是一个数组,通常是一个字节数组(ByteBuffer),也可以是其它类型的数组,此外缓冲区还提供了对数据的结构化访问以及维护读写位置等信息. Buffer类的继承关系如下图所示: 2.通道Channel Channel是一个通道,网络数据通过Chan

  • Java NIO通信基础示例详解

    目录 Java NIO 通信基础介绍 NIO 和 OIO 的对比 使用 FileChannel 完成文件复制的实践案例 使用 DatagramChannel 数据包通道发送数据的实践案例 使用 NIO 实现 Discard 服务器的实践案例 Java NIO 通信基础介绍 高性能的 Java 通信,绝对离不开 Java NIO 技术,现在主流的技术框架或中间件服务器,都使 用了 Java NIO 技术,譬如:Tomcat.Jetty.Netty. Java NIO 由以下三个核心组件组成: Ch

  • Java NIO Buffer过程详解

    前言 在与NIO通道交互时使用Java NIO Buffer. 如您所知,数据从通道读入缓冲区,并从缓冲区写入通道. 缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取. 此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块. 基本缓冲区用法 使用缓冲区读取和写入数据通常遵循这4个小步骤: 1.写入数据到缓冲区 2.调用 buffer.flip() 3.从缓冲区读取数据 4.调用 buffer.clear() 或者 buffer.compact() 当你将

  • Java NIO Selector用法详解【含多人聊天室实例】

    本文实例讲述了Java NIO Selector用法.分享给大家供大家参考,具体如下: 一.Java NIO 的核心组件 Java NIO的核心组件包括:Channel(通道),Buffer(缓冲区),Selector(选择器),其中Channel和Buffer比较好理解 简单来说 NIO是面向通道和缓冲区的,意思就是:数据总是从通道中读到buffer缓冲区内,或者从buffer写入到通道中. 关于Channel 和 Buffer的详细讲解请看:Java NIO 教程 二.Java NIO Se

  • Java双色球系统开发详解

    目录 总结 功能说明 1.1. 菜单页功能 package lotteryDemo1; import java.util.Scanner; public class Menu { public static void main(String[] args) { Scanner sc=new Scanner(System.in); o:while(true){ System.out.println("欢迎使用双色球系统,官方网站www.zhongbuliao.com"); System.

  • Java Mybatis框架多表操作与注解开发详解分析

    目录 一对一查询 多对多查询 Mybatis的注解开发 Mybatis的增删查改 MyBatis的注解实现复杂映射开发 一对一查询 一对一查询的模型 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户. 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户 一对一查询的语句 对应的sql语句: select * from orders o,user u where o.uid=u.id;查询的结果如下: 创建Order和User实体 创建OrderMapper接口 p

  • Java Apache Shiro安全框架快速开发详解流程

    目录 一.Shiro简介: shiro功能: Shiro架构(外部) Shiro架构(内部) 二.快速入门 1.拷贝案例 2.分析代码 三.SpringBoot 集成 Shiro 1.编写测试环境 2.使用 1.登录拦截 2.用户认证 四.Shiro整合Mybatis 五.实现请求授权 六.Shiro整合Thymeleaf 一.Shiro简介: Apache Shiro是一个Java的安全(权限)框架. Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在Ja

  • Java SpringBoot 集成 Redis详解

    目录 1.概述 Redis是什么? Redis能该干什么? 特性 2.测试Redis 3.自定义redisTemplate 1.概述 Redis是什么? Redis(Remote Dictionary Server ),即远程字典服务. 是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加

  • 关于Java中的 JSP 详解

    目录 1.JSP的特点 2.JSP的优势 3.JSP的缺点 4.JSP的用途 前言: JSP 代表 Java 服务器页面.它是一种在应用服务器端使用的编程工具.JSP 基本上用于支持平台 – 独立和动态的方法来构建 Web 依赖的应用程序.JSP 页面类似于 ASP 页面,因为它们是在服务器上编译的,而不是在用户的 Web 浏览器上进行编译. JSP 是由 Sun Microsystems 公司于 1999 年开发的.JSP 的开发使用语言,其中内置的所有功能都是用 Java 编程语言创建的.

  • Java Agent探针技术详解示例

    目录 什么是java agent? 使用示例 入门 进阶(一款接口mock数据小插件) 使用 什么是java agent? 在JVM中运行中,类是通过classLoader加载.class文件进行生成的.在类加载加载.class文件生成对应的类对象之前时,我们可以通过修改.class文件内容(就是字节码修改技术),达到修改类的目的.JDK提供了对字节码进行操作的一系列api,而使用这些api开发出的程序就可以称之为java agent. java agent能做什么? 不修改目标应用达到代码增强

随机推荐