JAVA用nio写CS程序,服务器端接收到的信息不完整,求助!SocketChannel,Bytebuffer

JAVA用nio写CS程序,服务器端接收到的信息不完整,求助!SocketChannel,Bytebuffer,第1张

while(true){

buffclear();

int len = scread(buff);

if(len ==-1){ break;}

buffflip();

content += charsetdecode(buff);

}

javaNIO包里包括三个基本的组件

l buffer:因为NIO是基于缓冲的,所以buffer是最底层的必要类,这也是IO和NIO的根本不同,虽然stream等有buffer开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而NIO却是直接读到buffer中进行 *** 作

因为读取的都是字节,所以在 *** 作文字时,要用charset类进行编解码 *** 作。

l channel:类似于IO的stream,但是不同的是除了FileChannel,其他的channel都能以非阻塞状态运行。FileChannel执行的是文件的 *** 作,可以直接DMA *** 作内存而不依赖于CPU。其他比如socketchannel就可以在数据准备好时才进行调用。

l selector:用于分发请求到不同的channel,这样才能确保channel不处于阻塞状态就可以收发消息。

面向流与面向缓冲

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java

IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

补充一点:NIO的buffer可以使用直接内存缓冲区,该缓冲区不在JVM中,性能会比JVM的缓冲区略好,不过会增加相应的垃圾回收的负担,因为JVM缓冲区的性能已经足够好,所以除非在对缓冲有特别要求的地方使用直接缓冲区,尽量使用JVM缓冲。

阻塞与非阻塞

Java IO是阻塞式的 *** 作,当一个inputstream或outputstream在进行read()或write() *** 作时,是一直处于等待状态的,直到有数据读/写入后才进行处理而NIO是非阻塞式的,当进行读写 *** 作时,只会返回当前已经准备好的数据,没有就返回空,这样当前线程就可以处理其他的事情,提高了资源的使用率

与传统IO的优势

在老的IO包中,serverSocket和socket都是阻塞式的,因此一旦有大规模的并发行为,而每一个访问都会开启一个新线程。这时会有大规模的线程上下文切换 *** 作(因为都在等待,所以资源全都被已有的线程吃掉了),这时无论是等待的线程还是正在处理的线程,响应率都会下降,并且会影响新的线程。

而NIO包中的serverSocket和socket就不是这样,只要注册到一个selector中,当有数据放入通道的时候,selector就会得知哪些channel就绪,这时就可以做响应的处理,这样服务端只有一个线程就可以处理大部分情况(当然有些持续性 *** 作,比如上传下载一个大文件,用NIO的方式不会比IO好)。

通过两个图的比较,可以看出IO是直连的,每个请求都给一条线程来处理,但是NIO却是基于反应堆(selector)来处理,直到读写的数据准备好后,才会通知相应的线程来进行处理。一言以蔽之:“selector不会让channel白占资源,没事的时候给我去睡觉。”

PS:NIO基于字节进行传输,在IO时要注意decode/encode。

更具体的信息请参阅:>

DatagramChannel

最后一个socket通道是DatagramChannel。正如SocketChannel对应Socket,ServerSocketChannel对应ServerSocket,每一个DatagramChannel对象也有一个关联的DatagramSocket对象。不过原命名模式在此并未适用:“DatagramSocketChannel”显得有点笨拙,因此采用了简洁的“DatagramChannel”名称。正如SocketChannel模拟连接导向的流协议(如TCP/IP),DatagramChannel则模拟包导向的无连接协议(如UDP/IP):

创建DatagramChannel的模式和创建其他socket通道是一样的:调用静态的open( )方法来创建一个新实例。新DatagramChannel会有一个可以通过调用socket( )方法获取的对等DatagramSocket对象。DatagramChannel对象既可以充当服务器(监听者)也可以充当客户端(发送者)。如果您希望新创建的通道负责监听,那么通道必须首先被绑定到一个端口或地址/端口组合上。绑定DatagramChannel同绑定一个常规的DatagramSocket没什么区别,都是委托对等socket对象上的API实现的:

DatagramChannel是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据净荷。与面向流的的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址。同样,DatagramChannel对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。

一个未绑定的DatagramChannel仍能接收数据包。当一个底层socket被创建时,一个动态生成的端口号就会分配给它。绑定行为要求通道关联的端口被设置为一个特定的值(此过程可能涉及安全检查或其他验证)。不论通道是否绑定,所有发送的包都含有DatagramChannel的源地址(带端口号)。未绑定的DatagramChannel可以接收发送给它的端口的包,通常是来回应该通道之前发出的一个包。已绑定的通道接收发送给它们所绑定的熟知端口(wellknown port)的包。数据的实际发送或接收是通过send( )和receive( )方法来实现的:

 写了个模拟下载的例子,服务器端模拟一个拥有整个硬盘资源的处理程序。客户端通过发送要下载的文件(通过完整文件路径),从而实现由服务器写文件到客户端,客户端保存接收的整体流程。其中,仅涉及到了数据传输的基本运用,即没有运用到网络编程上的urlConnection,也没有用到专门的socket,客户端也没有实现一个文件多线程下载的机制。仅仅作为一个selector的下载练习使用(当然,如果要求不高,也可以用到实际编程的)。

一、服务端实现:

服务器端基本思路:就是打开链接>绑定端口>接收信息>处理信息。详细过程如下:

第一步:创建服务器端socketChannel,并绑定指定端口,注册到selector上。

[html] view plain copy

selector = Selectoropen();

ServerSocketChannel serverSocketChannel=ServerSocketChannelopen();

serverSocketChannelconfigureBlocking(false);

serverSocketChannelsocket()bind(new InetSocketAddress(1234));

serverSocketChannelregister(selector, SelectionKeyOP_ACCEPT);

都是标准的步骤,先open, 再配置block为非阻塞的,服务器socket绑定本机端口,注册到selector上,并指定key为ACCEPT。

第二步:接收消息,处理信息。

[html] view plain copy

for(; ;) {

selectorselect();

Iterator<SelectionKey> keyIterator = selectorselectedKeys()iterator();

while(keyIteratorhasNext()) {

SelectionKey key = keyIteratornext();

if(keyisValid())

handle(key);

keyIteratorremove();

}

}

这也是标准步骤,先进行select(),再获得selectedKeys,迭代,处理,再remove掉。

在网上,看到有些例子中,对selectorselect

netty为什么快呢?这是因为netty底层使用了JAVA的NIO技术,并在其基础上进行了性能的优化,虽然netty不是单纯的JAVA nio,但是netty的底层还是基于的是nio技术。

nio是JDK14中引入的,用于区别于传统的IO,所以nio也可以称之为new io。

nio的三大核心是Selector,channel和Buffer,本文我们将会深入探究NIO和netty之间的关系。

在讲解netty中的NIO实现之前,我们先来回顾一下JDK中NIO的selector,channel是怎么工作的。对于NIO来说selector主要用来接受客户端的连接,所以一般用在server端。我们以一个NIO的服务器端和客户端聊天室为例来讲解NIO在JDK中是怎么使用的。

因为是一个简单的聊天室,我们选择Socket协议为基础的ServerSocketChannel,首先就是open这个Server channel:

然后向server channel中注册selector:

虽然是NIO,但是对于Selector来说,它的select方法是阻塞方法,只有找到匹配的channel之后才会返回,为了多次进行select *** 作,我们需要在一个while循环里面进行selector的select *** 作:

selector中会有一些SelectionKey,SelectionKey中有一些表示 *** 作状态的OP Status,根据这个OP Status的不同,selectionKey可以有四种状态,分别是isReadable,isWritable,isConnectable和isAcceptable。

当SelectionKey处于isAcceptable状态的时候,表示ServerSocketChannel可以接受连接了,我们需要调用register方法将serverSocketChannel accept生成的socketChannel注册到selector中,以监听它的OP READ状态,后续可以从中读取数据:

当selectionKey处于isReadable状态的时候,表示可以从socketChannel中读取数据然后进行处理:

上面的serverResponse方法中,从selectionKey中拿到对应的SocketChannel,然后调用SocketChannel的read方法,将channel中的数据读取到byteBuffer中,要想回复消息到channel中,还是使用同一个socketChannel,然后调用write方法回写消息给client端,到这里一个简单的回写客户端消息的server端就完成了。

接下来就是对应的NIO客户端,在NIO客户端需要使用SocketChannel,首先建立和服务器的连接:

然后就可以使用这个channel来发送和接受消息了:

向channel中写入消息可以使用write方法,从channel中读取消息可以使用read方法。

这样一个NIO的客户端就完成了。

虽然以上是NIO的server和client的基本使用,但是基本上涵盖了NIO的所有要点。接下来我们来详细了解一下netty中NIO到底是怎么使用的。

以netty的ServerBootstrap为例,启动的时候需要指定它的group,先来看一下ServerBootstrap的group方法:

ServerBootstrap可以接受一个EventLoopGroup或者两个EventLoopGroup,EventLoopGroup被用来处理所有的event和IO,对于ServerBootstrap来说,可以有两个EventLoopGroup,对于Bootstrap来说只有一个EventLoopGroup。两个EventLoopGroup表示acceptor group和worker group。

EventLoopGroup只是一个接口,我们常用的一个实现就是NioEventLoopGroup,如下所示是一个常用的netty服务器端代码:

这里和NIO相关的有两个类,分别是NioEventLoopGroup和NioServerSocketChannel,事实上在他们的底层还有两个类似的类分别叫做NioEventLoop和NioSocketChannel,接下来我们分别讲解一些他们的底层实现和逻辑关系。

NioEventLoopGroup和DefaultEventLoopGroup一样都是继承自MultithreadEventLoopGroup:

他们的不同之处在于newChild方法的不同,newChild用来构建Group中的实际对象,NioEventLoopGroup来说,newChild返回的是一个NioEventLoop对象,先来看下NioEventLoopGroup的newChild方法:

这个newChild方法除了固定的executor参数之外,还可以根据NioEventLoopGroup的构造函数传入的参数来实现更多的功能。

这里参数中传入了SelectorProvider、SelectStrategyFactory、RejectedExecutionHandler、taskQueueFactory和tailTaskQueueFactory这几个参数,其中后面的两个EventLoopTaskQueueFactory并不是必须的。

最后所有的参数都会传递给NioEventLoop的构造函数用来构造出一个新的NioEventLoop。

在详细讲解NioEventLoop之前,我们来研读一下传入的这几个参数类型的实际作用。

SelectorProvider是JDK中的类,它提供了一个静态的provider()方法可以从Property或者ServiceLoader中加载对应的SelectorProvider类并实例化。

另外还提供了openDatagramChannel、openPipe、openSelector、openServerSocketChannel和openSocketChannel等实用的NIO *** 作方法。

SelectStrategyFactory是一个接口,里面只定义了一个方法,用来返回SelectStrategy:

什么是SelectStrategy呢?

先看下SelectStrategy中定义了哪些Strategy:

SelectStrategy中定义了3个strategy,分别是SELECT、CONTINUE和BUSY_WAIT。

我们知道一般情况下,在NIO中select *** 作本身是一个阻塞 *** 作,也就是block *** 作,这个 *** 作对应的strategy是SELECT,也就是select block状态。

如果我们想跳过这个block,重新进入下一个event loop,那么对应的strategy就是CONTINUE。

BUSY_WAIT是一个特殊的strategy,是指IO 循环轮询新事件而不阻塞,这个strategy只有在epoll模式下才支持,NIO和Kqueue模式并不支持这个strategy。

RejectedExecutionHandler是netty自己的类,和 javautilconcurrentRejectedExecutionHandler类似,但是是特别针对SingleThreadEventExecutor来说的。这个接口定义了一个rejected方法,用来表示因为SingleThreadEventExecutor容量限制导致的任务添加失败而被拒绝的情况:

EventLoopTaskQueueFactory是一个接口,用来创建存储提交给EventLoop的taskQueue:

这个Queue必须是线程安全的,并且继承自javautilconcurrentBlockingQueue

讲解完这几个参数,接下来我们就可以详细查看NioEventLoop的具体NIO实现了。

首先NioEventLoop和DefaultEventLoop一样,都是继承自SingleThreadEventLoop:

表示的是使用单一线程来执行任务的EventLoop。

首先作为一个NIO的实现,必须要有selector,在NioEventLoop中定义了两个selector,分别是selector和unwrappedSelector:

在NioEventLoop的构造函数中,他们是这样定义的:

首先调用openSelector方法,然后通过返回的SelectorTuple来获取对应的selector和unwrappedSelector。

这两个selector有什么区别呢?

在openSelector方法中,首先通过调用provider的openSelector方法返回一个Selector,这个Selector就是unwrappedSelector:

然后检查DISABLE_KEY_SET_OPTIMIZATION是否设置,如果没有设置那么unwrappedSelector和selector实际上是同一个Selector:

DISABLE_KEY_SET_OPTIMIZATION表示的是是否对select key set进行优化:

如果DISABLE_KEY_SET_OPTIMIZATION被设置为false,那么意味着我们需要对select key set进行优化,具体是怎么进行优化的呢?

先来看下最后的返回:

最后返回的SelectorTuple第二个参数就是selector,这里的selector是一个SelectedSelectionKeySetSelector对象。

SelectedSelectionKeySetSelector继承自selector,构造函数传入的第一个参数是一个delegate,所有的Selector中定义的方法都是通过调用

delegate来实现的,不同的是对于select方法来说,会首先调用selectedKeySet的reset方法,下面是以isOpen和select方法为例观察一下代码的实现:

selectedKeySet是一个SelectedSelectionKeySet对象,是一个set集合,用来存储SelectionKey,在openSelector()方法中,使用new来实例化这个对象:

netty实际是想用这个SelectedSelectionKeySet类来管理Selector中的selectedKeys,所以接下来netty用了一个高技巧性的对象替换 *** 作。

首先判断系统中有没有sunniochSelectorImpl的实现:

SelectorImpl中有两个Set字段:

这两个字段就是我们需要替换的对象。如果有SelectorImpl的话,首先使用Unsafe类,调用PlatformDependent中的objectFieldOffset方法拿到这两个字段相对于对象示例的偏移量,然后调用putObject将这两个字段替换成为前面初始化的selectedKeySet对象:

如果系统设置不支持Unsafe,那么就用反射再做一次:

还记得前面我们提到的selectStrategy吗?run方法通过调用selectStrategycalculateStrategy返回了select的strategy,然后通过判断

strategy的值来进行对应的处理。

如果strategy是CONTINUE,这跳过这次循环,进入到下一个loop中。

BUSY_WAIT在NIO中是不支持的,如果是SELECT状态,那么会在curDeadlineNanos之后再次进行select *** 作:

如果strategy > 0,表示有拿到了SelectedKeys,那么需要调用processSelectedKeys方法对SelectedKeys进行处理:

上面提到了NioEventLoop中有两个selector,还有一个selectedKeys属性,这个selectedKeys存储的就是Optimized SelectedKeys,如果这个值不为空,就调用processSelectedKeysOptimized方法,否则就调用processSelectedKeysPlain方法。

processSelectedKeysOptimized和processSelectedKeysPlain这两个方法差别不大,只是传入的要处理的selectedKeys不同。

处理的逻辑是首先拿到selectedKeys的key,然后调用它的attachment方法拿到attach的对象:

如果channel还没有建立连接,那么这个对象可能是一个NioTask,用来处理channelReady和channelUnregistered的事件。

如果channel已经建立好连接了,那么这个对象可能是一个AbstractNioChannel。

针对两种不同的对象,会去分别调用不同的processSelectedKey方法。

对第一种情况,会调用task的channelReady方法:

对第二种情况,会根据SelectionKey的readyOps()的各种状态调用chunsafe()中的各种方法,去进行read或者close等 *** 作。

NioEventLoop虽然也是一个SingleThreadEventLoop,但是通过使用NIO技术,可以更好的利用现有资源实现更好的效率,这也就是为什么我们在项目中使用NioEventLoopGroup而不是DefaultEventLoopGroup的原因。

JDK14中加入了一个新的包:NIO(javanio)这个库最大的功能就是增加了对异步套接字的支持异步套接字对服务器程序来说更具吸引力一般同步SOCKET服务器的实现都是采用线程池来处理客户请求的,基于请求超时时间和并发线程数目的限制,如果并发处理能力能够达到上千就已经是不错了异步服务器的能力则至少是它的数倍(有人测试一个简单的ECHO服务程序,说可以达到上万个并发,不知道是否真的能达到) SocketChannel的读写是通过一个类叫ByteBuffer(javanioByteBuffer)来 *** 作的这个类本身的设计是不错的,比直接 *** 作byte[]方便多了 ByteBuffer有两种模式:直接/间接间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即 *** 作堆内存(byte[])但是内存毕竟有限,如果我要发送一个1G的文件怎么办不可能真的去分配1G的内存这时就必须使用"直接"模式,即MappedByteBuffer,文件映射 MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer这是一个很好的设计,除了一点,令人头疼的一点 MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式但是令人奇怪的是,SUN提供了map()却没有提供unmap()这样会导致什么后果呢 举个例子,文件testtmp是一个临时构建的文件,在业务处理(通过SocketChannel发送)完之后将不再有效一般的做法都是这样的: (1)File file = new File("testtmp"); FileInputStream in = new FileInputStream(file); FileChannel ch = ingetChannel(); MappedByteBuffer buf = chmap(FileChannelMapModeREAD_ONLY, 0, filelength()); (2)SocketChannel sch = 已经构造好了; while (bufhasRemaining()) schwrite(buf); (3)chclose(); inclose(); filedelete(); 上面的 *** 作都会正常的完成,除了最后一步:文件无法删除!即使你通过资源管理器直接强制删除也不行,说"文件正在使用" 为什么会出现这种情况 说"文件正在使用",说明文件句柄没有清零,还有在使用它的地方---就是被MappedByteBuffer占用了!尽管FileChannel,FileInputStream都已经关闭了,但是在map里还打开着一个文件句柄但是在外部看不见也无法 *** 作它那么这个句柄在什么时候才会正常地关闭呢根据JAVADOC的说明,是在垃圾收集的时候而众所周知垃圾收集是程序根本无法控制的 既然MappedByteBuffer是从FileChannel中map()出来的,为什么它又不提供unmap()呢SUN自己也没有讲清楚为什么O'Reilly的<>中说是因为"安全"的原因,但是到底unmap()会怎么不安全,作者也没有讲清楚 在SUN的BUG库中,这个问题在02年就有人提交了BUG报告,但是SUN自己不认为是BUG,而只是一个RFE(Request For Enhancement),有待改进 好在网上牛人多在BUG报告( >

JDK70和JDK60有什么区别?

jdk7是模块化程序,模块间的依赖性变小了jdk的好多功能间有相互依赖性,导致一个配置不对,好多不能用举例来说:假设你正使用Logging API(javautillogging)),Logging需要NIO和JMX,JMX需要JavaBeans, JNDI, RMI和CORBA,JNDI需要javaappletApplet而且JavaBeans依赖AWT

JDK7 新特性:

JSR203:JDK中会更多的IO API(“NIO2”)访问文件系统与之前的JDK中通过javaioFile访问文件的方式不同,JDK7将通过javaniofile包中的类完成。JDK7会使用javaniofilePath类来 *** 作任何文件系统中的文件。(这里说的任何文件系统指的是可以使用任何文件存储方式的文件系统)

示例:

Java7之前

File file = new File(“some_file”);

使用Java7

Path path = Pathsget(“some_file”);

在File类中加入了新的方法toPath(),可以方便的转换File到Path

Path path = new File(“some_file”)toPath();

Socket通道绑定和配置在JDK7中面向通道的网络编程也得以更新!JDK7中可以直接绑定通道的socket和直接 *** 作socket属性。JDK7提供了平台socket属性和指定实现的socket属性。

JDK7加入了一个新的字节通道类,SeekableByteChannel

NetworkChannel是面向网络通道编程模块中的又一个新的超接口。利用它可以方便的绑定通道socket,并且方便设置和获取socket的属性。

MulticastChannel接口方便创建IP协议多播。多播实现直接绑定到本地的多播设备。

灵活的异步I/O可以通过真正的异步I/O,在不同的线程中运行数以万计的流 *** 作!JKD7提供了对文件和socket的异步 *** 作。一些JDK7中的新通道:

AsynchronousFileChannel:异步文件通道可以完成对文件的异步读写 *** 作。

AsynchronouseSocketChannel:Socket中的一个简单异步通道,方法是异步的并且支持超时。

AsynchronousServerSocketChannel:异步的ServerSocket

AsynchronousDatagramChannel:基于数据包的异步socket

JSR292:Java平台中的动态编程语言Da Vinci Machine项目(JSR292)的主旨是扩展JVM支持除Java以外的其它编程语言,尤其是对动态编程语言的支持。所支持的语言必须和Java一样不收到歧视并共同存在。JSR334:Java语言的一些改进OpenJDK项目的创造(JSR334)的主旨是对Java语言进行一些小的改进来提高每天的Java开发人员的工作。这些改进包括:

Switch语句允许使用String类型

支持二进制常量和数字常量中可以使用下划线

使用一个catch语言来处理多种异常类型

对通用类型实例的创建提供类型推理

Try-with-resources语句来自动关闭资源

JSR119:Java编译器APIJSR199是在JDK6中加入的,主要用来提供调用Java编译器的API。除了提供javac的命令行工具,JSR199提供Java编译器到程序交互的能力。Java编译器API要达到三个目标:

对编译器和其它工具的调用

对结构化的编译信息进行访问

对文件输入输出定制化处理的能力

JSR206:Java XML处理的API (JAXP)JSR206即Java API for XML Processing(JAXP),是Java处理XML文档的一个与实现无关,灵活的API。

JAXP13的主要特性包括:

DOM3

内建通过XML Schema进行文档校验的处理器

对XML Schema中的数据类型的实现,在javaxxmldatatype包中。

XSLTC,最快的转换器,也是XSLT处理中的默认引擎。

提供对XInclude的实现。这将会方便我们使用文本和其它已有的XML来创建新的文档,这样可以对文档片段进行重用。

JDK7中会包含JAXP13,这个是JAXP的最新实现。

绑定技术(JAXB)JSR222即Java Architecture for XML Binding(JAXB)。JAXB的目的是便于Java程序进行Java类到XML文档的映射。

JAXB2的主要特性:

支持全部的W3C XML Schema特性。(JAXB10说明了对于W3C XML Schema中某些特性的不支持)

支持绑定Java到XML文档,通过添加javaxxmlbindannotation包来控制绑定。

大量减少了对于schema衍生出来的类。

通过JAXP13的校验API来提供额外的校验能力。

JDK7中将包括JAXB22

JSR224:基于XML的Web服务API(JAX-WS)JSR224即Java API for XML-based Web Services(JAX-WS),是一个基于Annotation标注的编程模型,主要针对Web Service应用和客户端开发。

以上就是关于JAVA用nio写CS程序,服务器端接收到的信息不完整,求助!SocketChannel,Bytebuffer全部的内容,包括:JAVA用nio写CS程序,服务器端接收到的信息不完整,求助!SocketChannel,Bytebuffer、java中IO和NIO的区别和适用场景、DatagramChannel等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9454632.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-28
下一篇2023-04-28

发表评论

登录后才能评论

评论列表(0条)

    保存