一、概念

在传统的java网络编程中,都是在服务端创建一个ServerSocket,然后为每一个客户端单独创建一个线程Thread分别处理各自的请求,由于对于CPU而言,线程的开销是很大的,无限创建线程会让操作系统崩溃,因此,比较好的方法是在系统启动的时候创建一个动态的线程池,例如鼎鼎大名的服务器Tomcat,就是采用这种解决方案,然而,这种解决方案在高并发的情况下,情况就不太乐观了,当线程池大小超过CPU瓶颈的时候,相应速度,就极其低下了。

传统的java网络编程的结构图如下

 
 
 
       在JDK1.4后,java引入的NIO的概念,即非阻塞的IO,服务端无需创建多个线程,仅仅只需要1个线程(将读写分别创建线程有利于提高性能)即可以处理全部客户端,解决了在性能和并发的2大问题。

NIO采用了通道Channel和选择器Selector的核心对象,Select 机制,不用为每一个客户端连接新启线程处理,而是将其注册到特定的Selector 对象上,这就可以在单线程中利用Selector 对象管理大量并发的网络连接,更好的利用了系统资源;采用非阻塞I/O 的通信方式,不要求阻塞等待I/O 操作完成即可返回,从而减少了管理I/O 连接导致的系统开销,大幅度提高了系统性能。

当有读或写等任何注册的事件发生时,可以从Selector 中获得相应的SelectionKey , 从SelectionKey 中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。由于在非阻塞网络I/O 中采用了事件触发机制,处理程序可以得到系统的主动通知,从而可以实现底层网络I/O 无阻塞、流畅地读写,而不像在原来的阻塞模式下处理程序需要不断循环等待。使用NIO,可以编写出性能更好、更易扩展的并发型服务器程序。

NIO的结构如下

由此可见,服务端最少只需要一个线程,既可以处理所有客户端Socket

NIO的设计原理

设计原理有点像设计模式中的观察者模式,由Selector去轮流咨询各个SocketChannel通道是否有事件发生,如果有,则选择出所有的Key集合,然后传递给处理程序。我们通过每个key就可以获取客户端的SocketChannel,从而进行通信。

如果Selector发现所有通道都没有事件发生,则线程进入睡眠状态Sleep,阻塞。等到客户端有事件发生,会自动唤醒wakeup选择器selector,是不是有点类似观察者模式!!!!

下面以两个例子来说明,工程目录如下:

虽然是两个例子,但是代码都放在了一个工程里面,下面将分开介绍

二、例子1

1、DataPacket类

该类是服务端和客户端传输的数据包

package com.nio;

import java.io.Serializable;
import java.util.Date; /**
* 数据包
* @author Administrator
*
*/
public class DataPacket implements Serializable{
private long id;
private String content;
private Date sendTime;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
} }

2、服务端

  NIOServer,服务端,接收客户端发送过来的数据,并将接受到的数据再发送到客户端

package com.nio;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
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; public class NIOServer { private Selector selector;
private ServerSocketChannel serverSocketChannel;
private ServerSocket serverSocket;
private static int PORT;
private static int BUFFER_SIZE;
private ByteBuffer buf; /**
* 服务器构造
* @param port
* @param buffersize
*/
public NIOServer(int port,int buffersize){
this.PORT=port;
this.BUFFER_SIZE=buffersize;
buf=ByteBuffer.allocate(BUFFER_SIZE);
}
/**
* 启动监听服务
* @throws Exception
*/
public void startListen() throws Exception{
//打开选择器
selector=Selector.open();
//打开服务通道
serverSocketChannel=ServerSocketChannel.open();
//将服务通道设置为非阻塞
serverSocketChannel.configureBlocking(false);
//创建服务端Socket
serverSocket=serverSocketChannel.socket();
//服务端socket绑定端口
serverSocket.bind(new InetSocketAddress(PORT));
//服务端通道注册链接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("端口注册完毕");
Iterator<SelectionKey> iterator=null;
SelectionKey selectionKey=null;
while(true){
//选择一批选择键(线程在此阻塞)
selector.select();
iterator=selector.selectedKeys().iterator();
while(iterator.hasNext()){
//selectionKey里包含了客户端发送过来的信息
selectionKey=iterator.next();
this.handleKey(selectionKey);
iterator.remove();
}
}
} /**
* 处理选择的键
* @param selectionKey
* @throws Exception
*/
@SuppressWarnings("unused")
private void handleKey(SelectionKey selectionKey)throws Exception{
//如果是链接事件
if(selectionKey.isAcceptable()){
//链接客户端通道(非阻塞)
SocketChannel socketChannel=this.serverSocketChannel.accept();
//设置客户端通道(非阻塞)
socketChannel.configureBlocking(false);
//注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("有新链接");
}
//如果是读信息事件
else if(selectionKey.isReadable()){
//获取客户端socket通道
SocketChannel socketChannel=(SocketChannel)selectionKey.channel();
//清空缓冲区
buf.clear();
//读取数据到缓冲区,并返回读取的字节数
int a=socketChannel.read(buf);
if(a>0){
//将开始指针指向0;把结束指针指向实际有效位置
buf.flip();
//得到的b数据组大小
byte[] b=new byte[buf.limit()];
//取的时实际有效的数据
buf.get(b,buf.position(),buf.limit());
//ObjectInputStream 不能直接接受byte数组,所以先转换成ByteArrayInputStream
ByteArrayInputStream byteIn=new ByteArrayInputStream(b);
ObjectInputStream objIn=new ObjectInputStream(byteIn);
DataPacket dataPacket=(DataPacket) objIn.readObject();
objIn.close();
byteIn.close(); System.out.println("从客户端发送到服务端:"+dataPacket.getContent());
System.out.println("接收时间:"+dataPacket.getSendTime().toLocaleString()); buf.flip();
//将发过来的数据再发送到客户端
socketChannel.write(buf);
}
else{
//关闭客户端socket通道
socketChannel.close();
}
}
} }

3、客户端

  NIOClient,客户端输入提示字符按回车,只要不是null都将信息发送到服务端,并监听客户端传过来的数据

package com.nio;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date; /**
*
* @author Administrator
*
*/
public class NIOClient { public static void main(String[] args){ try {
SocketAddress address=new InetSocketAddress("127.0.0.1",9999);
//客户端通道打开的时候要指向一个地址和端口
SocketChannel clientChannel=SocketChannel.open(address);
clientChannel.configureBlocking(false);
ByteBuffer buf=ByteBuffer.allocate(1024);
while(true){
buf.clear();
System.out.println("请输入发送数据包:");
//把输入的字节流转换成字符串
String msg=new BufferedReader(new InputStreamReader(System.in)).readLine();
if(msg.equals("null")){
break;
}
DataPacket dataPacket=new DataPacket();
dataPacket.setContent("I am hzb");
dataPacket.setSendTime(new Date());
dataPacket.setId(1);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
//把对象写入了oos流里面,但是没有到缓冲
oos.writeObject(dataPacket);
//把流的数据写入到缓冲区
buf.put(baos.toByteArray());
buf.flip();
//把缓冲区里面的数据写到通道里面
clientChannel.write(buf);
System.out.println("客户端发送数据:"+msg);
while(true){
int len=clientChannel.read(buf);
if(len>0){
buf.flip();
byte[] b=new byte[buf.limit()];
buf.get(b,buf.position(),buf.limit());
//注意:如果想要把服务端传过来的数据还原成对像,需要用
//ByteArrayInputStream byteIn=new ByteArrayInputStream(b);
//ObjectInputStream objIn=new ObjectInputStream(byteIn);
//DataPacket dataPacket=(DataPacket) objIn.readObject();
System.out.println("服务端传来数据:"+new String(b,"utf-8"));
break;
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

、例子2

   功能是,客户端将F:/work/nioSample/fileTest/client/client_send.txt发送给服务端,服务端接收到后存成F:/work/nioSample/fileTest/server/server_receive.txt

然后,服务端将F:/work/nioSample/fileTest/server/server_send.txt发送给客户端,客户端接收到后存成F:/work/nioSample/fileTest/client/client_receive.txt

1、服务端

package com.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectableChannel;
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.logging.Level;
import java.util.logging.Logger; public class NIOFileServer {
private final static Logger logger = Logger.getLogger(NIOFileServer.class.getName());
/**
* 主方法
* @param args
*/
public static void main(String[] args){
Selector selector=null;
ServerSocketChannel serverSocketChannel=null;
try {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(new InetSocketAddress(10000));
//注册链接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select()>0){
Iterator<SelectionKey> iterator=selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key=iterator.next();
iterator.remove();
doiterator((ServerSocketChannel) key.channel());
}
} } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logger.log(Level.SEVERE, e.getMessage(),e);
}finally{
try {
selector.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
serverSocketChannel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*
* @param serverSocketChannel,定义成final保证在方法内部serverSocketChannel的内容不会被改变
*/
private static void doiterator(final ServerSocketChannel serverSocketChannel){
SocketChannel socketChannel=null;
try {
socketChannel=serverSocketChannel.accept();
receiveFile(socketChannel, new File("F:/work/nioSample/fileTest/server/server_receive.txt"));
sendFile(socketChannel, new File("F:/work/nioSample/fileTest/server/server_send.txt"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} /**
* 接收文件
* @param socketChannel
* @param file
* @throws IOException
*/
private static void receiveFile(SocketChannel socketChannel,File file) throws IOException{
FileOutputStream fos=null;
FileChannel fileChannel=null;
try{
//保存文件要保存的路径
fos=new FileOutputStream(file);
fileChannel=fos.getChannel();
ByteBuffer buffer=ByteBuffer.allocateDirect(1024);
int len=0;
//把客户端通道socketChannel的文件读到缓冲区,再从缓冲区写到本地文件通道channel的路径下
while((len=socketChannel.read(buffer))!=-1){
buffer.flip();
if(len>0){
buffer.limit(len);
fileChannel.write(buffer);
buffer.clear();
}
}
}catch(Exception ex){
ex.printStackTrace();
}finally{
fos.close();
fileChannel.close();
}
} /**
* 发送文件
* @param socketChannel
* @param file
* @throws IOException
*/
private static void sendFile(SocketChannel socketChannel,File file) throws IOException{
FileInputStream fis=null;
FileChannel fileChannel=null;
try{
fis=new FileInputStream(file);
fileChannel=fis.getChannel();
ByteBuffer buffer=ByteBuffer.allocateDirect(1024);
int len=0;
while((len=fileChannel.read(buffer))!=-1){
//将buffer的游标position指向0
buffer.rewind();
buffer.limit(len);
socketChannel.write(buffer);
buffer.clear();
}
//防止正在发送的过程中又发送一个文件
socketChannel.socket().shutdownOutput();
}catch(Exception ex){
ex.printStackTrace();
}finally{
fis.close();
fileChannel.close();
}
}
}

2、客户端

package com.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger; public class NIOFileClient { private final static Logger logger = Logger.getLogger(NIOFileClient.class.getName()); public static void main(String[] args) throws Exception {
new Thread(new MyRunnable()).start();
} private static final class MyRunnable implements Runnable {
public void run() {
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 10000);
socketChannel.connect(socketAddress); sendFile(socketChannel, new File("F:/work/nioSample/fileTest/client/client_send.txt"));
receiveFile(socketChannel, new File("F:/work/nioSample/fileTest/client/client_receive.txt")); } catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
} finally {
try {
socketChannel.close();
} catch(Exception ex) {}
}
} private void sendFile(SocketChannel socketChannel, File file) throws IOException {
FileInputStream fis = null;
FileChannel channel = null;
try {
fis = new FileInputStream(file);
channel = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int size = 0;
while ((size = channel.read(buffer)) != -1) {
buffer.rewind();
buffer.limit(size);
socketChannel.write(buffer);
buffer.clear();
}
socketChannel.socket().shutdownOutput();
} finally {
try {
channel.close();
} catch(Exception ex) {}
try {
fis.close();
} catch(Exception ex) {}
}
} private void receiveFile(SocketChannel socketChannel, File file) throws IOException {
FileOutputStream fos = null;
FileChannel channel = null; try {
fos = new FileOutputStream(file);
channel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); int size = 0;
while ((size = socketChannel.read(buffer)) != -1) {
buffer.flip();
if (size > 0) {
buffer.limit(size);
channel.write(buffer);
buffer.clear();
}
}
} finally {
try {
channel.close();
} catch(Exception ex) {}
try {
fos.close();
} catch(Exception ex) {}
}
}
}
}

java NIO编程(转)的更多相关文章

  1. Java IO编程全解(四)——NIO编程

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7793964.html 前面讲到:Java IO编程全解(三)——伪异步IO编程 NIO,即New I/O,这 ...

  2. Java NIO:浅析I/O模型

    也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗.在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型.下面本文先从同步和异步的概念 说起, ...

  3. Java NIO:NIO概述

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  4. JDK NIO编程

    我们首先需要澄清一个概念:NIO到底是什么的简称?有人称之为New I/O,因为它相对于之前的I/O类库是新增的,所以被称为New I/O,这是它的官方叫法.但是,由于之前老的I/O类库是阻塞I/O, ...

  5. JAVA NIO 类库的异步通信框架netty和mina

    Netty 和 Mina 我究竟该选择哪个? 根据我的经验,无论选择哪个,都是个正确的选择.两者各有千秋,Netty 在内存管理方面更胜一筹,综合性能也更优.但是,API 变更的管理和兼容性做的不是太 ...

  6. (转载)Java NIO:NIO概述(一)

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  7. Java IO编程全解(六)——4种I/O的对比与选型

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语 ...

  8. Java NIO系列教程(六) 多路复用器Selector

    多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要.多路复用器提供选择已经就绪的任务的能力.简单来讲,Selector会不断地轮询注册在其上的 ...

  9. Java多线程:Linux多路复用,Java NIO与Netty简述

    JVM的多路复用器实现原理 Linux 2.5以前:select/poll Linux 2.6以后: epoll Windows: IOCP Free BSD, OS X: kqueue 下面仅讲解L ...

随机推荐

  1. select(下拉标签和textarea(文本框)

    Title 北京 南京 天津 武汉 石家庄 太原 dsadasd   <!DOCTYPE html> <html lang="en"> <head&g ...

  2. shelve模块使用说明

    一种字典形式储存数据的方式 import datetime, shelve d = shelve.open('shelve_test.txt') info = {'age':22, 'job':'it ...

  3. Oracle SQL Developer在进行查询的时候只显示50条数据

    在查询结果大于50条的时候,软件默认会只显示50条,向下拉会继续显示. 想要显示所有结果的话,光标放在结果集:ctrl+End或者是ctrl+PgDn都可以.

  4. css兼容性记录

    *        , ie6,ie7可以识别: _和- ,  ie6可以识别: !important  ,表示高优先级,ie7及以上,firefox都支持,ie6认识带!important的样式属性, ...

  5. 【转载】 Java并发编程:深入剖析ThreadLocal

    原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...

  6. python 的None 探究

    a = None b = None print id(a),id(b),id(None) # 9430224 9430224 9430224 可能在别的环境下运行不是这个数,但是这三个数应该是一样的. ...

  7. sar命令详细信息

    sar(System Activity Reporter系统活动情况报告)是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况.系统调用的使用情 ...

  8. Ubuntu 18.04上安装R及Rstudio

    安装R引用自:https://www.howtoing.com/how-to-install-r-on-ubuntu-18-04 安装Rstudio引用自:https://www.rstudio.co ...

  9. sysbench——服务器cpu性能测试

    一.前言 最近在工作中需要测试cpu占用率.内存占用率,我想要寻找一种合适的能提高cpu占用率的工具及方法.先尝试了使用 echo "scale=5000; 4*a(1)" | b ...

  10. SQL Server 2016/2014/2012/2008/2005/2000简体中文企业版下载地址

    为什么只提供企业版下载呢?因为不管你是学生还是工作研究人员,企业版都是功能最为齐全的一个版本,比如企业版都集成了SQL Server Management Studio管理界面(俗称企业管理器的可视化 ...