Java Socket NIO

服务端:

public class NIOServer {private static final String HOST = "localhost";private static final int PORT = 10086;public static void main(String[] args) {ServerSocketChannel serverSocketChannel = null;ServerSocket serverSocket = null;Selector selector = null;try {serverSocketChannel = ServerSocketChannel.open();//工厂方法创建ServerSocketChannelserverSocket = serverSocketChannel.socket(); //获取channel对应的ServerSocketserverSocket.bind(new InetSocketAddress(HOST, PORT)); //绑定地址serverSocketChannel.configureBlocking(false); //设置ServerSocketChannel非阻塞模式selector = Selector.open();//工厂方法创建SelectorserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//通道注册选择器,接受连接就绪状态。while (true) {//循环检查if (selector.select() == 0) {//阻塞检查,当有就绪状态发生,返回键集合continue;}// if (selector.select(2000) == 0) {//     //在等待信道准备的同时,也可以异步地执行其他任务,  这里打印*//     System.out.print("*");//     continue;// }Iterator<SelectionKey> it = selector.selectedKeys().iterator(); //获取就绪键遍历对象。while (it.hasNext()) {SelectionKey selectionKey = it.next();//处理就绪状态if (selectionKey.isAcceptable()) {ServerSocketChannel schannel = (ServerSocketChannel) selectionKey.channel();//只负责监听,阻塞,管理,不发送、接收数据SocketChannel socketChannel = schannel.accept();//就绪后的操作,刚到达的socket句柄if (null == socketChannel) {continue;}socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ); //告知选择器关心的通道,准备好读数据} else if (selectionKey.isReadable()) {SocketChannel socketChannel = (SocketChannel) selectionKey.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(4 * 1024);System.out.println(socketChannel.getRemoteAddress());int read = socketChannel.read(byteBuffer);if (read == -1) {//如果不关闭会一直产生isReadable这个消息
                            selectionKey.cancel();socketChannel.close();} else {StringBuilder result = new StringBuilder();while (read > 0) {//确保读完
                                byteBuffer.flip();result.append(new String(byteBuffer.array()));byteBuffer.clear();//每次清空 对应上面flip()read = socketChannel.read(byteBuffer);}System.out.println("server receive: " + result.toString());socketChannel.register(selector, SelectionKey.OP_WRITE);}} else if (selectionKey.isWritable()) {SocketChannel socketChannel = (SocketChannel) selectionKey.channel();String sendStr = "server send data: " + Math.random();ByteBuffer send = ByteBuffer.wrap(sendStr.getBytes());while (send.hasRemaining()) {socketChannel.write(send);}socketChannel.register(selector, SelectionKey.OP_READ);System.out.println(sendStr);}it.remove();}}} catch (IOException e) {e.printStackTrace();}}
}

客户端:

public class NIOClient {public static void main(String[] args) throws Exception {SocketChannel clntChan = SocketChannel.open();clntChan.configureBlocking(false);if (!clntChan.connect(new InetSocketAddress("localhost", 10086))) {//不断地轮询连接状态,直到完成连接while (!clntChan.finishConnect()) {//在等待连接的时间里,可以执行其他任务,以充分发挥非阻塞IO的异步特性//这里为了演示该方法的使用,只是一直打印"."System.out.print(".");}}//为了与后面打印的"."区别开来,这里输出换行符System.out.print("\n");//分别实例化用来读写的缓冲区ByteBuffer writeBuf = ByteBuffer.wrap("send send send".getBytes());ByteBuffer readBuf = ByteBuffer.allocate("send".getBytes().length - 1);while (writeBuf.hasRemaining()) {//如果用来向通道中写数据的缓冲区中还有剩余的字节,则继续将数据写入信道
            clntChan.write(writeBuf);}Thread.sleep(10000);StringBuffer stringBuffer = new StringBuffer();//如果read()接收到-1,表明服务端关闭,抛出异常while ((clntChan.read(readBuf)) > 0) {readBuf.flip();stringBuffer.append(new String(readBuf.array(), 0, readBuf.limit()));readBuf.clear();}//打印出接收到的数据System.out.println("Client Received: " + stringBuffer.toString());//关闭信道
        clntChan.close();}
}

注意当客户端断开socket的时候需要处理,不然服务端对应的channel会一直处于readable的状态,会造成死循环。

当客户端调用:clntChan.close();

这一句是正常关闭代码,它会传给服务端关闭的指令(也就是数据)。

而服务端的socketChannel.read(byteBuffer)=-1则代表收到这个关闭指令(数据),可以依据-1来关闭服务端的channel,不过仍有可能有问题,可能是客户端调用socketChannel.close()的时候,服务端这边的bytebuffer已经满了,那么num只能返回0而不能返回-1。具体该怎么写你应该根据你自己的业务来处理。

 

当socketChannel为阻塞方式时(默认就是阻塞方式)read函数,不会返回0,阻塞方式的socketChannel,若没有数据可读,或者缓冲区满了,就会阻塞,直到满足读的条件,所以一般阻塞方式的read是比较简单的,不过阻塞方式的socketChannel的问题也是显而易见的。这里我结合基于NIO 写ftp服务器调试过程中碰到的问题,总结一下非阻塞场景下的read碰到的问题。注意:这里的场景都是基于客户端以阻塞socket的方式发送数据。

1、read什么时候返回-1

read返回-1说明客户端的数据发送完毕,并且主动的close socket。所以在这种场景下,(服务器程序)你需要关闭socketChannel并且取消key,最好是退出当前函数。注意,这个时候服务端要是继续使用该socketChannel进行读操作的话,就会抛出“远程主机强迫关闭一个现有的连接”的IO异常。

2、read什么时候返回0

其实read返回0有3种情况,一是某一时刻socketChannel中当前(注意是当前)没有数据可以读,这时会返回0,其次是bytebuffer的position等于limit了,即bytebuffer的remaining等于0,这个时候也会返回0,最后一种情况就是客户端的数据发送完毕了(注意看后面的程序里有这样子的代码),这个时候客户端想获取服务端的反馈调用了recv函数,若服务端继续read,这个时候就会返回0。

-------------------------------------------------------------------------------------------------

实际写代码过程中观察发现,如果客户端发送数据后不关闭channel,同时服务端收到数据后反倒再次发给客户端,那么此时客户端read方法永远返回0.

 

SelectionKey.OP_WRITE

当时有一个连接进来后,如果注册了SelectionKey.OP_WRITE消息,selectionKey.isWritable()会一直返回true知道写缓冲区写满。所以这点就值注册了SelectionKey.OP_READ.

OP_WRITE事件的就绪条件并不是发生在调用channel的write方法之后,而是在当底层缓冲区有空闲空间的情况下。因为写缓冲区在绝大部分时候都是有空闲空间的,所以如果你注册了写事件,这会使得写事件一直处于就就绪,选择处理现场就会一直占用着CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。
其实,在大部分情况下,我们直接调用channel的write方法写数据就好了,没必要都用OP_WRITE事件。那么OP_WRITE事件主要是在什么情况下使用的了?
其实OP_WRITE事件主要是在发送缓冲区空间满的情况下使用的。如:
while (buffer.hasRemaining()) {int len = socketChannel.write(buffer);   if (len == 0) {selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);selector.wakeup();break;}
}
当buffer还有数据,但缓冲区已经满的情况下,socketChannel.write(buffer)会返回已经写出去的字节数,此时为0。那么这个时候我们就需要注册OP_WRITE事件,这样当缓冲区又有空闲空间的时候就会触发OP_WRITE事件,这是我们就可以继续将没写完的数据继续写出了。
而且在写完后,一定要记得将OP_WRITE事件注销:
selectionKey.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
注意,这里在修改了interest之后调用了wakeup();方法是为了唤醒被堵塞的selector方法,这样当while中判断selector返回的是0时,会再次调用selector.select()。而selectionKey的interest是在每次selector.select()操作的时候注册到系统进行监听的,所以在selector.select()调用之后修改的interest需要在下一次selector.select()调用才会生效。

nio的select()的时候,只要数据通道允许写,每次select()返回的OP_WRITE都是true。所以在nio的写数据里面,我们在每次需要写数据之前把数据放到缓冲区,并且注册OP_WRITE,对selector进行wakeup(),这样这一轮select()发现有OP_WRITE之后,将缓冲区数据写入channel,清空缓冲区,并且反注册OP_WRITE,写数据完成。这里面需要注意的是,每个SocketChannel只对应一个SelectionKey,也就是说,在上述的注册和反注册OP_WRITE的时候,不是通过channel.register()和key.cancel()做到的,而是通过key.interestOps()做到的。

public void write(MessageSession session, ByteBuffer buffer) throws ClosedChannelException {SelectionKey key = session.key();if((key.interestOps() & SelectionKey.OP_WRITE) == 0) {key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);}try {writebuf.put(buffer);} catch(Exception e) {System.out.println("want put:"+buffer.remaining()+", left:"+writebuf.remaining());e.printStackTrace();}selector.wakeup();
}
while(true) {selector.select();.....if(key.isWritable()) {MessageSession session = (MessageSession)key.attachment();//System.out.println("Select a write");synchronized(session) {writebuf.flip();SocketChannel channel = (SocketChannel)key.channel();int count = channel.write(writebuf);//System.out.println("write "+count+" bytes");
          writebuf.clear();key.interestOps(SelectionKey.OP_READ);}}......}

要点一:不推荐直接写channel,而是通过缓存和attachment传入要写的数据,改变interestOps()来写数据;

要点二:每个channel只对应一个SelectionKey,所以,只能改变interestOps(),不能register()和cancel()。

参考:https://www.cnblogs.com/burgeen/p/3618059.html

转载于:https://www.cnblogs.com/grasp/p/10607271.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/483814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主宰这个世界的10大算法

来源&#xff1a;算法与数学之美什么是算法&#xff1f;简而言之&#xff0c;任何定义明确的计算步骤都可称为算法&#xff0c;接受一个或一组值为输入&#xff0c;输出一个或一组值。(来源&#xff1a;homas H. Cormen, Chales E. Leiserson 《算法导论第3版》)可以这样理解&a…

关联规则分析 Apriori 算法 简介与入门

关联规则的几个概念&#xff1a; 关联规则是形如 X -> Y 的蕴含式&#xff0c;表示通过 X 可以推导出 Y&#xff0c;X称为关联规则的左部&#xff08;Left Hand Side&#xff0c;LHS&#xff09;&#xff0c;Y 称为关联规则的右部&#xff08;Right Hand Side&#xff0c;R…

OO第一单元总结__多项式求导问题

作业一、含幂函数的简单多项式的求导 &#xff08;1&#xff09;基于度量的程序结构分析 1. 统计信息图&#xff1a; 2. 结构信息图&#xff1a; 3. 复杂度分析 基本复杂度&#xff08;Essential Complexity (ev(G))、模块设计复杂度&#xff08;Module Design Complexity (iv(…

流式处理和批处理的区别

流式处理&#xff08;Stream Processing&#xff09; 是针对 批处理&#xff08;Batch Processing&#xff09;来讲的&#xff0c;即它们是两种截然不同的数据处理模式&#xff0c;具有不同的特点&#xff0c;适用于不同的应用场合。不能简单地认为其中一种数据处理模式优于另一…

Go语言中的`sync`包同步原语

通过sync包掌握Go语言的并发 并发是现代软件开发的基本方面&#xff0c;而Go&#xff08;也称为Golang&#xff09;为并发编程提供了一套强大的工具。在Go中用于管理并发的基本包之一是sync包。在本文中&#xff0c;我们将概述sync包&#xff0c;并深入探讨其最关键的同步原语…

linux 安装svn客户端

安装命令&#xff1a;yum install -y subversion 客户端使用命令&#xff1a; svn help 帮助命令 svn checkout --help 子帮助命令 转载于:https://www.cnblogs.com/wesky/p/10607649.html

AdaBoost 算法 入门

AdaBoost 是一种迭代算法&#xff0c;其核心思想是针对同一个训练集训练不同的分类器&#xff0c;即弱分类器&#xff0c;然后把这些弱分类器集合起来&#xff0c;构造一个更强的最终分类器。算法的适应性在于前一个基本分类器分错的样本会得到加强&#xff0c;加权后的全体样本…

流数据模型

流数据模型和传统的关系模型&#xff08;Relational Model&#xff09;有几个重要的区别&#xff1a; &#xff08;1&#xff09; 数据流的数据元素持续到达 &#xff08;2&#xff09; 流数据处理系统不能控制数据元素到达的顺序 &#xff08;3&#xff09; 数据流有可能是无限…

第一单元总结

一、作业分析 第一次作业 与后两次作业相比&#xff0c;第一次作业非常简单&#xff0c;仅要求对由常数项和幂函数组成的多项式求导。但由于缺少面向对象编程经验&#xff0c;我在这次作业中栽了不少跟头。 &#xff08;1&#xff09;度量分析 在第一次作业中&#xff0c;我还没…

牛客16437 买铅笔

题目描述 P老师需要去商店买n支铅笔作为小朋友们参加NOIP的礼物。她发现商店一共有 3 种包装的铅笔&#xff0c;不同包装内的铅笔数量有可能不同&#xff0c;价格也有可能不同。为了公平起见&#xff0c;P老师决定只买同一种包装的铅笔。 商店不允许将铅笔的包装拆开&#xff0…

牛客16426 玩具谜题

题目描述 南有一套可爱的玩具小人&#xff0c;它们各有不同的职业。 有一天&#xff0c;这些玩具小人把小南的眼镜藏了起来。小南发现玩具小人们围成了一个圈&#xff0c;它们有的面朝圈内&#xff0c;有的面朝圈外&#xff0c;如下图&#xff1a; 这时 singer 告诉小南一个谜…

ubuntu 更新软件源

ubuntu 更新软件源 问题&#xff1a;正在等待packagekitd退出 解决办法&#xff1a;systemctl stop packagekit 或者 systemctl disable packagekit转载于:https://www.cnblogs.com/xpylovely/p/10611394.html

牛客16438 回文日期

date1 input() date2 input()""" 判断是否是闰年 """ def isLeap(year):if (year%4 0 and year%100 ! 0) or (year%400 0):return 1else:return 0""" 判断是否是回文数 """ def isPalindrome(year):for i in …

Codeforces 1139F Dish Shopping 树状数组套平衡树 || 平衡树

Dish Shopping 将每个物品拆成p 和 s 再加上人排序。 然后问题就变成了&#xff0c; 对于一个线段(L - R)&#xff0c; 问有多少个(li, ri)满足 L > li && R > ri&#xff0c; 这个东西可以直接树状数组套平衡树维护。 但是这个题目有个特殊性&#xff0c;因为排…

牛客16494 生活大爆炸版石头剪刀布

题目描述 石头剪刀布是常见的猜拳游戏&#xff1a;石头胜剪刀&#xff0c;剪刀胜布&#xff0c;布胜石头。如果两个人出拳一样&#xff0c;则不分胜负。在《生活大爆炸》第二季第8集中出现了一种石头剪刀布的升级版游戏。升级版游戏在传统的石头剪刀布游戏的基础上&#xff0c;…

codeforces 1073E

题解&#xff1a; 考虑数位DP,状压出现过的数字集合S&#xff0c;f ( l , x , S , pz , lim )表示到第 l 位&#xff0c;数字为x&#xff0c; 数字集合为S &#xff0c;是否为前导0&#xff0c;是否贴上界 然后同时定义g为该状态下的数字和&#xff0c;利用 10^(l-1) * f(l , x…

时空AI技术:深度强化学习在智能城市领域应时空AI技术:深度强化学习在智能城市领域应用介绍...

来源&#xff1a;海豚数据科学实验室作者&#xff1a;京东科技 时空AI团队深度强化学习是近年来热起来的一项技术。深度强化学习的控制与决策流程必须包含状态&#xff0c;动作&#xff0c;奖励是三要素。在建模过程中&#xff0c;智能体根据环境的当前状态信息输出动作作用于环…

牛客16500 珠心算测试

题目描述 珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练&#xff0c;既能够开发智力&#xff0c;又能够为日常生活带来很多便利&#xff0c;因而在很多学校得到普及。 某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成…

读书笔记007:《伤寒论》- 手少阴心经

手少阴脉起心中&#xff0c;下膈直与小肠通&#xff0c;支者还从肺系走&#xff0c;直上喉咙系目瞳。直者上肺出腋下&#xff0c;臑后肘内少海从&#xff0c;臂内后廉抵掌中&#xff0c;锐骨之端注少冲。多气少血属此经&#xff0c;是动心脾痛难任&#xff0c;渴欲饮水咽干燥&a…

牛客16585 统计单词数

题目描述 一般的文本编辑器都有查找单词的功能&#xff0c;该功能可以快速定位特定单词在文章中的位置&#xff0c;有的还能统计出特定单词在文章中出现的次数。 现在&#xff0c;请你编程实现这一功能&#xff0c;具体要求是&#xff1a;给定一个单词&#xff0c;请你输出它在…