NIO实现非阻塞Socket编程

本文探讨如何使用Java NIO进行非阻塞Socket编程,解决服务器因为每个客户端分配线程而导致的压力问题。讲解了NIO中的关键概念如Channel、Buffer和Selector,以及如何创建和注册Selector来监听多个Channel的IO状态,从而实现高效的服务器性能优化。
摘要由CSDN通过智能技术生成

前言
基于阿里面试时,面试官问我,我做的聊天项目里,考虑过性能没有,是怎么解决程序卡顿现象的,针对客户端,当在发送文件时,如果卡顿,怎么办,同时想聊天,当时程序我是基于多线程实现的,在客户端里,聊天时启动一个线程,传送文件时,启动另一个线程,所以在客户端并不会影响,面试官就说知道了,但是,针对服务器的性能优化,我做的不好,针对每一个客户端开启线程,造成服务器压力过大,因为现在没有上线,所以不会面对这个问题。
博文地址:http://blog.csdn.net/u011958281/article/details/74556377

问题
网络通信是基于阻塞式API的——即当程序执行输入,输出操作以后,在这些操作返回之前会一直阻塞线程,所以服务器端必须为每个客户端提供一个独立的线程来进行处理。当服务器端需要同时处理大量客户端时,这种做法会导致性能下降,使用NIO API则可以让服务器端使用一个或者有限的几个线程来同时处理连接到服务器端的所有客户端。

解决
补充知识:新IO和传统IO有相同的目的,都是用于进行输入/输出,但是新的IO使用了不同的方式来处理输入流,新的IO采用内存映射的方式来处理输入输出,新IO将文件或文件的一部分区域映射到内存中,这样就可以此昂访问内存一样来访问文件了,通过这种方式来进行输入输出比传统的输入输出要快很多。

Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,与传统的InputStream和OutputStream最大的区别就是。它提供一个map()方法,可以直接将一块数据映射到内存中,如果说传统的IO是针对于面向流的处理,则新IO是面向块的处理。

  • Buffer
    从内部结构来看,Buffer就像一个数组,它可以保存多个类型相同的数据,Buffer是一个抽象类,最常用的子类就是ByteBuffer,它可以在底层字节数组上进行get/set操作,还有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer.
  • Channel
    程序不能直接访问Channel中的数据,包括读取,写入都不行,Channel只能与Buffer进行交互,也就是说,如果要从Channel取一些数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据。

    1. Pipe.SkinChannel和Pipe.SourceChannel用于支持线程之间通信的管道
    2. ServerSocketChannel,SocketChannel是用于支持TCP通信的Channel
    3. DatagramChannel则是用于支持UDP网络通信的

JAVA的NIO为非阻塞式Socket通信提供了下面几个特殊类:

  • Selector:所有希望采用通过非阻塞式方式通信的Channel都应该注册到Selector对象,可以通过调用此类的Open()静态方法来创建Selector实例。
    Selector可以同时监听多个SelectorChannel的IO状态,是非阻塞式IO的核心

仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

Selector的创建
通过调用Selector.open()方法创建一个Selector,如下:

Selector selector = Selector.open();

向Selector注册通道

为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
Selectionkey.OP_READ);

与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

Connect
Accept
Read
Write

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

这四种事件用SelectionKey的四个常量来表示:

SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

这里写图片描述

服务器上所有的Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector注册,而该Selector则负责监视其中的Socket的IO状态,当其中任意一个或几个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0整数,该整数就表示有几个Channel具有IO操作,并提供了secletorKeys()返回selectionKeys集合,正是通过这些Selcetor的实例的select()方法,可以知道当前有多少channel是否有需要的IO操作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值