`
dannyhz
  • 浏览: 394814 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

NIO 例子

    博客分类:
  • NIO
NIO 
阅读更多
  我们都知道TCP是面向连接的传输层协议,一个socket必定会有绑定一个连接,在普通的BIO(阻塞式IO)中,需要有三次握手,然后一般的socket编程就是这样的形式。
Socket服务器端流程如下:加载套接字->创建监听的套接字->绑定套接字->监听套接字->处理客户端相关请求。

Socket客户端同样需要先加载套接字,然后创建套接字,不过之后不用绑定和监听了,而是直接连接服务器,发送相关请求。



       他们一直就占用这个连接,如果有信息发送,那么就响应,否则就一直阻塞着。如果有多连接,那么就要使用多线程,一个线程处理一个连接,在连接还少的情况下,是允许的,但如果同时处理的连接过多比如说1000,那么在win平台上就会遇到瓶颈了如果2000,那么在linux上就遇到瓶颈了,因为在不同的平台上每一个进程能够创建的线程数是有限度的,并且过多的线程必将会引起系统对线程调度的效率问题,再怎么也要保证线程优先队列,阻塞队列;假设一千个线程,一个线程最少一兆的栈大小,对内存也是一个很大的消耗。

       总之阻塞式的IO是:一连接<一一一>一线程  。



       然后出现了NIO,在java1.4引入了java.nio包,java new I/O。引入了操作系统中常用的缓冲区和通道等概念。



       缓冲区: 在操作系统中缓冲区是为了解决CPU的计算速度和外设输入输出速度不匹配的问题,因为外设太慢了,如果没有缓冲区,那么CPU在外设输入的时候就要一直等着,就会造成CPU处理效率的低下,引入了缓冲之后,外设直接把数据放到缓冲中,当数据传输完成之后,给CPU一个中断信号,通知CPU:“我的数据传完了,你自己从缓冲里面去取吧”。如果是输出也是一样的道理。

       通道: 那么通道用来做什么呢?其实从他的名字就可以看出,它就是一条通道,您想传递出去的数据被放置在缓冲区中,然后缓冲区中怎么从哪里传输出去呢?或者外设怎么把数据传输到缓冲中呢?这里就要用到通道。它可以进一步的减少CPU的干预,同时更有效率的提高整个系统的资源利用率,例如当CPU要完成一组相关的读操作时,只需要向I/O通道发送一条指令,以给出其要执行的通道程序的首地址和要访问的设备,通道执行通道程序便可以完成CPU指定的I/O任务。

      选择器: 另外一项创新是选择器,当我们使用通道的时候也许通道没有准备好,或者有了新的请求过来,或者线程遇到了阻塞,而选择器恰恰可以帮助CPU了解到这些信息,但前提是将这个通道注册到了这个选择器。





下面一个例子是我看过的一个讲述的很贴切的例子:  

一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?  

1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )  

2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )    

很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。 





在非阻塞式IO中实现的是:一请求<一一一>一线程



     下面这个例子实现了一个线程监听两个ServerSocket,只有等到请求的时候才会有处理。

Server


package cn.vicky.channel;

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.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

/**  
* TCP/IP的NIO非阻塞方式 
* 服务器端 
* */
public class Server implements Runnable {

    //第一个端口  
    private Integer port1 = 8099;
    //第二个端口  
    private Integer port2 = 9099;
    //第一个服务器通道 服务A  
    private ServerSocketChannel serversocket1;
    //第二个服务器通道 服务B  
    private ServerSocketChannel serversocket2;
    //连接1  
    private SocketChannel clientchannel1;
    //连接2  
    private SocketChannel clientchannel2;

    //选择器,主要用来监控各个通道的事件  
    private Selector selector;
   
    //缓冲区  
    private ByteBuffer buf = ByteBuffer.allocate(512);
   
    public Server() {
        init();
    }

    /** 
     * 这个method的作用
     * 1:是初始化选择器 
     * 2:打开两个通道 
     * 3:给通道上绑定一个socket 
     * 4:将选择器注册到通道上 
     * */
    public void init() {
        try {
            //创建选择器  
            this.selector = SelectorProvider.provider().openSelector();
            //打开第一个服务器通道  
            this.serversocket1 = ServerSocketChannel.open();
            //告诉程序现在不是阻塞方式的  
            this.serversocket1.configureBlocking(false);
            //获取现在与该通道关联的套接字  
            this.serversocket1.socket().bind(new InetSocketAddress("localhost", this.port1));
            //将选择器注册到通道上,返回一个选择键  
            //OP_ACCEPT用于套接字接受操作的操作集位  
            this.serversocket1.register(this.selector, SelectionKey.OP_ACCEPT);

            //然后初始化第二个服务端  
            this.serversocket2 = ServerSocketChannel.open();
            this.serversocket2.configureBlocking(false);
            this.serversocket2.socket().bind(new InetSocketAddress("localhost", this.port2));
            this.serversocket2.register(this.selector, SelectionKey.OP_ACCEPT);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /** 
     * 这个方法是连接 
     * 客户端连接服务器 
     * @throws IOException  
     * */
    public void accept(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        if (server.equals(serversocket1)) {
            clientchannel1 = server.accept();
            clientchannel1.configureBlocking(false);
            //OP_READ用于读取操作的操作集位  
            clientchannel1.register(this.selector, SelectionKey.OP_READ);
        } else {
            clientchannel2 = server.accept();
            clientchannel2.configureBlocking(false);
            //OP_READ用于读取操作的操作集位  
            clientchannel2.register(this.selector, SelectionKey.OP_READ);
        }
    }

    /** 
     * 从通道中读取数据 
     * 并且判断是给那个服务通道的 
     * @throws IOException  
     * */
    public void read(SelectionKey key) throws IOException {

        this.buf.clear();
        //通过选择键来找到之前注册的通道  
        //但是这里注册的是ServerSocketChannel为什么会返回一个SocketChannel??  
        SocketChannel channel = (SocketChannel) key.channel();
        //从通道里面读取数据到缓冲区并返回读取字节数  
        int count = channel.read(this.buf);

        if (count == -1) {
            //取消这个通道的注册  
            key.channel().close();
            key.cancel();
            return;
        }

        //将数据从缓冲区中拿出来  
        String input = new String(this.buf.array()).trim();
        //那么现在判断是连接的那种服务  
        if (channel.equals(this.clientchannel1)) {
            System.out.println("欢迎您使用服务A");
            System.out.println("您的输入为:" + input);
        } else {
            System.out.println("欢迎您使用服务B");
            System.out.println("您的输入为:" + input);
        }

    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("running ... ");
                //选择一组键,其相应的通道已为 I/O 操作准备就绪。  
                this.selector.select();

                //返回此选择器的已选择键集  
                //public abstract Set<SelectionKey> selectedKeys()  
                Iterator selectorKeys = this.selector.selectedKeys().iterator();
                while (selectorKeys.hasNext()) {
                    System.out.println("running2 ... ");
                    //这里找到当前的选择键  
                    SelectionKey key = (SelectionKey) selectorKeys.next();
                    //然后将它从返回键队列中删除  
                    selectorKeys.remove();
                    if (!key.isValid()) { // 选择键无效
                        continue;
                    }
                    if (key.isAcceptable()) {
                        //如果遇到请求那么就响应  
                        this.accept(key);
                    } else if (key.isReadable()) {
                        //读取客户端的数据  
                        this.read(key);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        Thread thread = new Thread(server);
        thread.start();
    }
}



Client
package cn.vicky.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetAddress;

/** 
* TCP/IP的NIO非阻塞方式 
* 客户端 
* */
public class Client {

    //创建缓冲区  
    private ByteBuffer buffer = ByteBuffer.allocate(512);
    //访问服务器  

    public void query(String host, int port) throws IOException {
        InetSocketAddress address = new InetSocketAddress(InetAddress.getByName(host), port);
        SocketChannel socket = null;
        byte[] bytes = new byte[512];
        while (true) {
            try {
                System.in.read(bytes);
                socket = SocketChannel.open();
                socket.connect(address);
                buffer.clear();
                buffer.put(bytes);
                buffer.flip();
                socket.write(buffer);
                buffer.clear();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    socket.close();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Client().query("localhost", 8099);

    }
}



 

以上的服务端一个线程监听两个服务,整个服务端只有一个阻塞的方法:

//选择一组键,其相应的通道已为 I/O 操作准备就绪。 

this.selector.select(); 



当客户请求服务器的时候,那么这造成了TCP没有面向连接的假象,其实至少在传输数据的时候是连接的,只是在一次I/O请求结束之后服务器端就把连接给断开,继而继续去处理更多的请求。而在客户端,可以看到也是遇到一次请求的时候就connect服务端一次。所以TCP还是面向连接的。

     现在终于知道了为什么叫非阻塞式IO了,大概就是这个意思。
分享到:
评论

相关推荐

    实现java网络与nio例子

    这个例子包含了NIO在网络通信中的应用,包括服务器端(Server)和客户端(Client)的实现。 在Java NIO中,核心组件有通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。通道是数据传输的途径,如套接字...

    一个java NIO的例子

    本例子中的"NioServer"可能是一个简单的Java NIO服务器端程序,用于演示如何使用NIO进行网络通信。下面我们将深入探讨Java NIO的关键组件和工作原理。 1. **通道(Channel)**:通道是数据传输的途径,类似于传统的...

    netty示例NIO示例

    myeclipse开发通信示例,框架netty,代码本人写的,而且已测试通过,先运行NettyService,再运行NettyClient即可看到效果。nio示例也有,原理一样,运行先后顺序同netty.

    JAVA nio的一个简单的例子

    在这个“JAVA nio的一个简单的例子”中,我们将探讨如何使用Java NIO进行简单的服务器-客户端通信,并计算字符串的哈希值。 在传统的BIO模型中,每个连接都需要一个线程来处理,当并发连接数量增加时,系统会创建...

    java nio socket 例子

    本例包含服务器端和客户端,多线程,每线程多次发送,Eclipse工程,启动服务器使用 nu.javafaq.server.NioServer,启动客户端使用 nu.javafaq.client.NioClient。另本例取自javafaq.nv上的程序修改而成

    NIO 服务器客户端例子

    在这个"NIO 服务器客户端例子"中,`TestServer.java`和`TestClient.java`分别代表服务器端和客户端的实现。 **NIO服务器端(TestServer.java)的关键知识点:** 1. **选择器(Selector)**:服务器通常会创建一个...

    自己写的NIO的多播例子

    标题中的“NIO的多播例子”指的是使用Java NIO实现的多播通信应用。NIO提供了选择器(Selector)、通道(Channel)和缓冲区(Buffer)等组件,使得在处理多个连接时可以更加高效。多播是IP层的UDP服务,它通过组播...

    java nio 通信服务器、客户端完整例子

    用java编写的nio通信的例子,nio是io编程的新版本,比io较流行。同时本例子是适用socket通信的。可以在此基础上,添加您的个人应用。本例子适用于:java通信的学习者,android平台通信的学习者。

    一个NIO服务端,客户端的例子

    总的来说,这个NIO服务端和客户端的例子可以帮助我们深入理解Java NIO和Netty框架的工作原理,学习如何构建高效、可靠的网络应用。通过实践和分析这个示例代码,我们可以更好地掌握异步I/O、事件驱动编程以及Netty...

    NIO socket编程小例子 加法服务器

    总结来说,"NIO socket编程小例子 加法服务器"是一个很好的学习NIO网络编程的起点。通过这个实例,我们可以了解NIO Channel、Buffer和Selector的基本用法,以及如何构建一个简单的网络通信应用。对于任何想要提升...

    javaNiO.doc

    为了更直观地理解NIO的优势,下面通过一个具体的例子来比较使用传统I/O和NIO读取文件的差异。 ```java // 使用传统I/O读取文件 public void ioRead(String file) throws IOException { FileInputStream in = new ...

    自己写的Java NIO 同步不阻塞IO操作

    描述中提到的"用nio想的一个不阻塞NIOSocket例子"可能是一个Java NIO的Socket通信示例,利用NIO的Channel和Selector来实现客户端和服务器之间的非阻塞通信。通常,NIO中的SocketChannel用于网络通信,Selector用于...

    nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio

    描述中的“java nio 编程一个实例子.服务端程序”提示我们,这个实例是一个服务器端的应用,它利用Java NIO来处理客户端的连接请求。在NIO服务端编程中,通常会使用`Selector`来监听多个通道的事件,当有新的连接、...

    NIO_MINA学习例子_源代码

    在“NIO_MINA学习例子_源代码”中,你可以找到以下学习要点: 1. **NIO基础**:研究源代码中的通道和缓冲区的使用,理解如何通过Channel.read()和Channel.write()进行数据传输,以及如何使用Selector注册和监听通道...

    Java Nio selector例程

    我研究并实现的Java Nio selector例子。java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server...

    javaNIO实例

    此外,这个例子应该包含了异常处理,确保在出现错误时能够正确关闭资源,防止资源泄露。 总的来说,这个javaNIO实例是一个很好的学习资源,通过实践了解NIO的基本用法和优势。对于希望提升Java I/O性能或者处理高...

    NIO.rar_NIO_java nio

    Java NIO,全称为Non-Blocking ..."NIO修正版書面例子"这个压缩包文件很可能是包含了一些示例代码,可以帮助你更深入地理解和实践Java NIO的各种特性。通过结合这些示例和Java API文档,你将能够更好地掌握NIO的使用。

    java nio im(server+client)

    在这个例子中,可能会包含类图、序列图或状态图,来描绘服务器和客户端的架构、交互流程以及对象状态的变迁。 5. **源码分析** - **服务器源码**:服务器主要代码可能包括设置监听、接受连接、注册选择器、处理...

    nio演示代码

    在这个例子中,我们首先打开一个文件通道,然后创建一个ByteBuffer并从文件通道中读取数据。当缓冲区满时,我们反转缓冲区以读取数据,然后清空缓冲区以接收更多的数据。这个过程一直持续到文件读取完毕。 通过以上...

    nio.rar_Different_NIO_java nio package

    这些例子可以帮助开发者深入理解NIO的工作原理,掌握如何在实际项目中应用NIO技术,提高Java应用程序的性能和并发处理能力。通过分析和学习这些示例,可以更好地理解和实践Java NIO的各种特性。

Global site tag (gtag.js) - Google Analytics