NIO非阻塞通信服务端部分代码
来源:互联网 发布:国安数据 编辑:程序博客网 时间:2024/05/22 00:15
第一篇自己写的博客,之前总觉得不把东西做出个大概就写出来自己会不爽所以一直没写,今天终于厚着脸皮写了第一篇,希望我的脸皮今后越来越厚。
之前自己一直在搞PC端和手机端通信的软件,中途受了百度云推送(BaiduPush)的诱惑去做了两三个月基于百度云推送的聊天软件,结果在我的真机上跑不动,后台服务死都不出来,让我备受打击。几周前回归局域网通信,那时还是ServerSocket、Socket的阻塞式IO通信,感觉很不顺手,前几天换了NIO非阻塞通信,感觉舒爽多了,于是跑来这里分享一下成果。
NIO原理
NIO与IO的区别
Buffer
即一个缓冲区,固定数据容量的容器,一般来说倾向于存储字节(ByteBuffer)。它是向通道(channel)写的数据的直接来源,也是从通道读取的数据的直接容器,通俗的说,Buffer里的数据直接写到channel里,从channel读的数据首先会存到Buffer里。
Channel
Selector
选择器,提供选择执行已经就绪的任务的能力。selector充当一个监视者,一个或多个创建的Channel注册到Selector中,返回一个表示通道和选择器的键(Key),它记录了这个通道。调用Selector的select()方法时会刷新所有key并检查注册的通道,可以获取key的集合进而遍历得到就绪的通道,对就绪的通道进行读写操作。形象的比喻:Selector是一条走廊,Channel都是走廊两边的房间,要打开门进房间就要先获得钥匙(Key),要获得钥匙(Key)就要先去管理员那登记入住信息(Register注册)。
Key
表示对应的注册关系,要获得通道Channel需要先获得Key,Key可以被取消,获得对应关系是否有效的状态等。
以上是我对于这些个基本概念的个人见解,比较浅薄,如果有不对的欢迎评论指正。
代码分享
下面这一部分是Java服务器端一个线程LinkServer的run()方法,循环对客户端通信通道进行处理、接收数据需要读取的请求就新建一个线程处理请求。
public void run() {try {// 打开一个选择器selector = Selector.open();// 服务端通道ServerSocketChannel server = ServerSocketChannel.open();// 地址,系统自动给ip,端口为指定的9090InetSocketAddress addr = new InetSocketAddress(PORT);// 绑定地址server.bind(addr);// 设定非阻塞server.configureBlocking(false);// 向此选择器注册给定的通道server.register(selector, SelectionKey.OP_ACCEPT);// 更新界面,GetLocalLanIP()是通过DOS指令获取本机局域网IP的方法ui.tf_IP.setText(GetLocalLanIP());ui.tf_port.setText(PORT + "");while (true) {selector.select(); // 循环到这里会阻塞,直到至少有一个SelectionKeySystem.out.println("接入数量:" + selector.selectedKeys().size());// SelectionKey是一个注册标记或选择键,跟通道一一对应,可以通过它操作通道for (SelectionKey key : selector.selectedKeys()) {// 从selector上的已选择key集中删除正在处理的SelectionKeyselector.selectedKeys().remove(key);// 后边的操作中有些会将key取消、将通道关闭掉,但是key不会立即被移除// 它会被放到选择器的已取消键集里,标为无效,以便之后移除// 所以这个判断是跳过 已无效但未移除的keyif (!key.isValid()) {continue;}if (key.isAcceptable()) { // 接到客户端的连接请求// 接到一个和客户端通信的通道SocketChannel client = server.accept();// 设为非阻塞client.configureBlocking(false);// 注册通道,初始操作为 读client.register(selector, SelectionKey.OP_READ);// key的interest 集合设置为给定值// interest 集合 确定了下一次调用某个选择器的选择方法时,将测试哪类操作的准备就绪信息。key.interestOps(SelectionKey.OP_ACCEPT);// 附加一个客户端地址的字符串的对象,是个对象都可以附加key.attach(client.getRemoteAddress());// 打印一下客户端地址System.out.println(client.getRemoteAddress());}if (key.isReadable()) { // 数据需要读取的请求// ArrayList<SelectionKey> keyDing// 某个key如果存在于keyDing表示它正在被某个线程处理if (!keyDing.contains(key)) {// 如果这个key当前没有线程处理,新建线程处理它keyDing.add(key);// 线程处理完毕后会把key从keyDing里踢掉new SysMsgReadThread(key).start();}}}// 给一点时间SysMsgReadThread作为缓冲,// key很多的时候可以不给时间,后期可以动态优化Thread.sleep(5);}} catch (Exception e) {e.printStackTrace();}// 更新界面ui.ta_state.append("退出线程LinkServer\n");}
下面这部分代码是数据读取请求处理线程SysMsgReadThread的run()方法,通道没有数据就结束读取,客户端断开就取消SelectionKey并关闭通道。
对读取的字符串处理的方法ParseSysMsg(String SysMsgStr)我就不贴出来了,不同的人有不同的处理方式。
public void run() {// long n = getId();获取线程的idSystem.out.println("线程 " + n + " 启动");// 通过key获得通道client = (SocketChannel) key.channel();// 缓存ByteBuffer buff = ByteBuffer.allocate(1024);// 接收的字符串String SysMsgStr = "";try {while (true) {// 读取,read(buff)返回读取的字节数,如果没数据就一直返回0// 如果客户端已经断开、进(线)程被杀掉、强退等,就一直返回-1int num = client.read(buff);if (num > 0) {// 这表示通道里读取到了数据System.out.println("线程 " + n + " 读到" + num + "字节");buff.flip();SysMsgStr += charset.decode(buff);} else {// 这表示通道里没数据if (num == -1) {// 这进一步表示客户端断开了System.out.println("线程 " + n + " 读到了-1");// 这里对key进行cancel对通道进行关闭// 但由于key不会立即移除所以之前要判断isValid()来跳过取消的keykey.cancel();client.close();}break;}}System.out.println("线程 " + n + " 读取的数据(长 " + SysMsgStr.length() + "):" + SysMsgStr);// 这个判断是由于上几行取消了key,不判断的话这里会异常if (key.isValid()) {key.interestOps(SelectionKey.OP_READ);// 为下一次读取做准备}} catch (IOException ex) {key.cancel();try {client.close();} catch (IOException e) {e.printStackTrace();}}if (SysMsgStr.length() > 0) { // 聊天信息不为空ParseSysMsg(SysMsgStr); // 调用方法对读取到的消息进行处理}super.run();System.out.println("线程 " + n + " 结束");keyDing.remove(key);// 线程处理这个key结束后就把key从队列里踢掉}
public static String GetLocalLanIP() {try {String command = "cmd.exe /c ipconfig";Process p = Runtime.getRuntime().exec(command);BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("GBK")));ArrayList<String> lines = new ArrayList<String>();String line = null;while ((line = br.readLine()) != null) {lines.add(line);if (line.indexOf("默认网关") == 3 && line.length() > 40) {String result = lines.get(lines.size() - 3);result = result.substring(result.lastIndexOf(":") + 2, result.length());return result;}}br.close();p.destroy();} catch (IOException e) {e.printStackTrace();}return null;}
以上就是我目前PC(Java)服务器端和手机(Android)客户端中服务器端比较关键的代码了,注释挺多的便于理解,客户端代码过一段时间之后会有分享。
代码虽然low,但是以后会越改越好,期待与路过的各位相互交流。
Buffer
即一个缓冲区,固定数据容量的容器,一般来说倾向于存储字节(ByteBuffer)。它是向通道(channel)写的数据的直接来源,也是从通道读取的数据的直接容器,通俗的说,Buffer里的数据直接写到channel里,从channel读的数据首先会存到Buffer里。
- NIO非阻塞通信服务端部分代码
- socket nio非阻塞通信
- Java NIO-非阻塞通信
- Java NIO-非阻塞通信
- NIO的非阻塞通信
- nio 非阻塞式数据传输,服务端
- NIO实现的简单的客户端与服务端通信(非阻塞)
- 使用NIO实现非阻塞Socket通信
- 使用NIO实现非阻塞Socket通信
- java NIO 实现非阻塞socket通信
- NIO实现TCP的非阻塞通信
- NIO Socket实现非阻塞通信示例
- NIO实现非阻塞Socket通信
- Java NIO 非阻塞socket通信案例
- java-非阻塞异步通信-NIO初探
- NIO Socket非阻塞模式代码示例
- java nio实现非阻塞Socket通信实例
- NIO(二)--CS架构的非阻塞通信
- java 小知识 关系运算符的全面总结
- Linux下C/C++帮助手册安装方法 及使用方法
- c/c++整理--函数重载
- Java可变参数(转载)
- jzoj. 3889. 【NOIP2014模拟10.25B组】序列问题
- NIO非阻塞通信服务端部分代码
- hdu 2546 饭卡
- css样式
- 暴搜——51nod1400 序列分解
- java运算符之++、--
- 验证对象在创建时就会先调用(默认)构造方法
- 十分钟搞清字符集和字符编码
- STM32 心电滤波
- python 清新脏数据