在计算机系统中,最不可靠的就是网络请求,我们通过服务器端给客户端echo信息(客户端请求什么信息服务端就返回给客户端什么信息)。比较两种socket io的优劣。

标准io socket:

    服务端使用多线程处理的结构示意图:
   

服务器端代码:

主线程负责不断地请求echoServer.accept(),如果没有客户端请求主线程会阻塞,当有客户端请求服务器端时,主线程会用线程池新创建一个线程执行。也就是说一个线程负责一个客户端socket,当一个客户端socket因为网络延迟时,服务器端负责这个客户端的线程就会等待,浪费资源。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.Buffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 常规的socket服务端,服务器端采用一个线程接受一个客户端来处理。* Created by chenyang on 2017/3/26.*/
public class MultiThreadEchoServer {private static ExecutorService tp= Executors.newCachedThreadPool();static class HandleMsg implements Runnable{Socket clientSocket;public HandleMsg(Socket clientSocket) {this.clientSocket = clientSocket;}@Overridepublic void run() {BufferedReader is=null;PrintWriter os=null;try {is=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));os=new PrintWriter(clientSocket.getOutputStream(),true);//从InputStream当中读取客户端所发送的数据String inputLine=null;long b=System.currentTimeMillis();while ((inputLine=is.readLine())!=null){os.println(inputLine);}long e=System.currentTimeMillis();System.out.println("spend:"+(e-b)+"ms");}catch (IOException e){e.printStackTrace();}finally {try {if(is!=null) is.close();if(os!=null) os.close();clientSocket.close();}catch (IOException ex){ex.printStackTrace();}}}}public static void main(String[] args) {ServerSocket echoServer=null;Socket clientSocket=null;try {echoServer=new ServerSocket(8000);}catch (IOException e){System.out.println(e);}while (true){try {clientSocket =echoServer.accept();//阻塞System.out.println(clientSocket.getRemoteSocketAddress()+" connect!"+System.currentTimeMillis());
     //子线程负责执行与client socket 交互的操作。tp.execute(new HandleMsg(clientSocket));}catch (IOException e){System.out.println(e);}}}
}

客户端代码:

主线程创建10个子线程去请求server:这是个模拟网络拥堵时的客户端socket,每打一个字符就会停1秒。这样服务端的线程也要等待,这样服务器端的资源浪费的就很多。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport;/*** 传统IO下,模拟10个网络不好的客户端同时访问server.* Created by chenyang on 2017/4/8.*/
public class HeavyThreadEchoClient {static ExecutorService es= Executors.newCachedThreadPool();static Long sleep_time=1000*1000*1000L;public static class EchoClient implements Runnable{@Overridepublic void run() {Socket client=null;PrintWriter writer=null;BufferedReader reader=null;try {client=new Socket();client.connect(new InetSocketAddress("localhost",8000));writer=new PrintWriter(client.getOutputStream(),true);writer.print("h");LockSupport.parkNanos(sleep_time);writer.print("e");LockSupport.parkNanos(sleep_time);writer.print("l");LockSupport.parkNanos(sleep_time);writer.print("l");LockSupport.parkNanos(sleep_time);writer.print("o");LockSupport.parkNanos(sleep_time);writer.print("!");LockSupport.parkNanos(sleep_time);writer.println();writer.flush();reader=new BufferedReader(new InputStreamReader(client.getInputStream()));System.out.println("from server:"+reader.readLine());}catch (UnknownHostException ex){ex.printStackTrace();}catch (IOException e){e.printStackTrace();} finally {if(writer!=null){writer.close();}if(reader!=null){try {reader.close();}catch (IOException ex){ex.printStackTrace();}}if(client!=null){try {client.close();}catch (IOException ex){ex.printStackTrace();}}}}}public static void main(String[] args) {EchoClient ec=new EchoClient();for(int i=0;i<10;i++){es.execute(ec);}}
}
当服务器端和客户端代码执行后的结果:
spend:6023ms
spend:6023ms
spend:6024ms
spend:6024ms
spend:6025ms
spend:6025ms
spend:6026ms
spend:6027ms
spend:6027ms
spend:6028ms

都有6秒的延迟,这都是网络io等待时间造成的。

nio socket:

    通过事件通知的机制,当数据准备好了才会通知服务器端线程进行读写,避免了网络io等待。

服务端多线程的结构示意图:

一个线程控制一个selector,一个selector可以轮询多个客户端的channel,这样服务器端线程不用等待网络io,只会处理准备好的数据。

服务器端代码:

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
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.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** Created by chenyang on 2017/4/8.*/
public class MultiThreadNIOEchoServer {public static Map<Socket,Long> geym_time_stat=new HashMap<Socket,Long>(10240);class EchoClient{private LinkedList<ByteBuffer> outq;EchoClient(){outq=new LinkedList<ByteBuffer>();}//return the output queuepublic LinkedList<ByteBuffer> getOutputQueue(){return outq;}//enqueue a ByteBuffer on the output queue.public void enqueue(ByteBuffer bb){outq.addFirst(bb);}}class HandleMsg implements Runnable{SelectionKey sk;ByteBuffer bb;public HandleMsg(SelectionKey sk, ByteBuffer bb) {this.sk = sk;this.bb = bb;}@Overridepublic void run() {EchoClient echoClient=(EchoClient)sk.attachment();echoClient.enqueue(bb);//we've enqueued data to be written to the client,we must//not set interest in OP_WRITEsk.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);selector.wakeup();}}private Selector selector;private ExecutorService tp= Executors.newCachedThreadPool();/*accept a new client and set it up for reading*/private void doAccept(SelectionKey sk){ServerSocketChannel server=(ServerSocketChannel)sk.channel();SocketChannel clientChannel;try {//获取客户端的channelclientChannel = server.accept();clientChannel.configureBlocking(false);//register the channel for readingSelectionKey clientKey=clientChannel.register(selector,SelectionKey.OP_READ);//Allocate an EchoClient instance and attach it to this selection key.EchoClient echoClient=new EchoClient();clientKey.attach(echoClient);InetAddress clientAddress=clientChannel.socket().getInetAddress();System.out.println("Accepted connetion from "+clientAddress.getHostAddress()+".");}catch (Exception e){System.out.println("Failed to accept new client");e.printStackTrace();}}private void doRead(SelectionKey sk){SocketChannel channel=(SocketChannel)sk.channel();ByteBuffer bb=ByteBuffer.allocate(8192);int len;try {len=channel.read(bb);if(len<0){disconnect(sk);return;}}catch (Exception e){System.out.println("Fail to read from client");e.printStackTrace();disconnect(sk);return;}bb.flip();tp.execute(new HandleMsg(sk,bb));}private void doWrite(SelectionKey sk){SocketChannel channel=(SocketChannel)sk.channel();EchoClient echoClient=(EchoClient)sk.attachment();LinkedList<ByteBuffer> outq=echoClient.getOutputQueue();ByteBuffer bb=outq.getLast();try {int len=channel.write(bb);if(len==-1){disconnect(sk);return;}if(bb.remaining()==0){outq.removeLast();}}catch (Exception e){e.printStackTrace();System.out.println("fail to write to client");disconnect(sk);}if(outq.size()==0){sk.interestOps(SelectionKey.OP_READ);}}private void disconnect(SelectionKey sk){SocketChannel sc=(SocketChannel)sk.channel();try {sc.finishConnect();}catch (IOException e){}}private void startServer() throws Exception{//声明一个selectorselector= SelectorProvider.provider().openSelector();//声明一个server socket channel,而且是非阻塞的。ServerSocketChannel ssc=ServerSocketChannel.open();ssc.configureBlocking(false);//        InetSocketAddress isa=new InetSocketAddress(InetAddress.getLocalHost(),8000);//声明服务器端的端口InetSocketAddress isa=new InetSocketAddress(8000);//服务器端的socket channel绑定在这个端口。ssc.socket().bind(isa);//把一个socketchannel注册到一个selector上,同时选择监听的事件,SelectionKey.OP_ACCEPT表示对selector如果//监听到注册在它上面的server socket channel准备去接受一个连接,或 有个错误挂起,selector将把OP_ACCEPT加到//key ready set 并把key加到selected-key set.SelectionKey acceptKey=ssc.register(selector,SelectionKey.OP_ACCEPT);for(;;){selector.select();Set readyKeys=selector.selectedKeys();Iterator i=readyKeys.iterator();long e=0;while (i.hasNext()){SelectionKey sk=(SelectionKey)i.next();i.remove();if(sk.isAcceptable()){doAccept(sk);}else if(sk.isValid()&&sk.isReadable()){if(!geym_time_stat.containsKey(((SocketChannel)sk.channel()).socket())){geym_time_stat.put(((SocketChannel)sk.channel()).socket(),System.currentTimeMillis());doRead(sk);}}else if(sk.isValid()&&sk.isWritable()){doWrite(sk);e=System.currentTimeMillis();long b=geym_time_stat.remove(((SocketChannel)sk.channel()).socket());System.out.println("spend"+(e-b)+"ms");}}}}public static void main(String[] args) {MultiThreadNIOEchoServer echoServer=new MultiThreadNIOEchoServer();try {echoServer.startServer();}catch (Exception e){e.printStackTrace();}}
}

同样的客户端代码测试nio的服务器端结果:

spend8ms
spend10ms
spend11ms
spend15ms
spend7ms
spend7ms
spend6ms
spend6ms
spend6ms
spend8ms

几乎没有多少延迟。

总结:

nio在数据准备好后,再交由应用进行处理,数据的读写过程仍在应用线程中。也就是说应用线程不用再等待网络io了,准备好了读写还是要处理的。

节省的数据准备时间(因为selector可以多个channel复用)

      


java nio 传统标准io socket 和nio socket比较与学习相关推荐

  1. 以Java的视角来聊聊BIO、NIO与AIO的区别

    说一说I/O 首先来说一下什么是I/O? 在计算机系统中I/O就是输入(Input)和输出(Output)的意思,针对不同的操作对象,可以划分为磁盘I/O模型,网络I/O模型,内存映射I/O, Dir ...

  2. 重拾Java基础知识:IO流

    I0流 前言 字节流 InputStream OutputStream 字符流 Reader Writer 缓存流 转换流 序列化流 数据流 字节数组流 打印流 校验流 数据压缩 ZIP压缩和解压 G ...

  3. java nio 阻塞_Java NIO和传统阻塞IO

    传统的阻塞IO我就不多说,因为之前我发过几篇使用传统阻塞IO做QQ聊天写的几篇文章,服务器端每得到一个新的连接就开一个线程,在这个线程中接收数据方向是阻塞的,​​ 这种方式对性能的开销是非常大的,总结 ...

  4. Java传统的io和nio区别_Java中IO和NIO的本质和区别

    简介 终于要写到java中最最让人激动的部分了IO和NIO.IO的全称是input output,是java程序跟外部世界交流的桥梁,IO指的是java.io包中的所有类,他们是从java1.0开始就 ...

  5. Java的IO:BIO | NIO | AIO

    原文: http://my.oschina.net/bluesky0leon/blog/132361 BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  6. 尚硅谷java——NIO(New IO)

    Java NIO(New IO或 Non Blocking IO)是从java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO支持面向缓冲区的.基于通道的IO操 ...

  7. java 非阻塞_Java之NIO(非阻塞IO)

    [1]NIO的与IO的区别: 总的来说java 中的IO 和NIO的区别主要有3点: 1)IO是面向流的,NIO是面向缓冲的: 2)IO是阻塞的,NIO是非阻塞的: 3)IO是单线程的,NIO 是通过 ...

  8. 【学习笔记】JAVA IO与NIO(new IO)的对比与不同IO模型的理解

    JAVA IO 分类: 几种IO 模型 1. 阻塞 IO 模型 2. 非阻塞 IO 模型 JAVA NIO 多路复用 IO 模型(即Java中的NIO) JAVA IO 思维导图: 分类: 按照流的方 ...

  9. io 错误: socket closed_Tomcat NIO(9)IO线程Overall流程和关键类

    在上一篇文章里我们主要介绍了 tomcat NIO 中 poller 线程的阻塞与唤醒,根据以前文章当 poller 线程监测到连接有数据可读事件的时候,会把原始 socket 的包装对象委托到 to ...

最新文章

  1. 序列比对-BLAST
  2. beeline执行sql语句_由“Beeline连接HiveServer2后如何使用指定的队列(Yarn)运行Hive SQL语句”引发的一系列思考...
  3. js替换数组中字符串实例
  4. 2020中国男士美妆市场洞察报告
  5. 居然之家完成35亿定增 小米、阿里等认购
  6. 项目管理 计算机仿真,刘宝林老师【项目管理】《挑战埃及》沙盘课程内容分享...
  7. 智慧社区电商后台管理系统
  8. 人工智能的发展历史概览
  9. 安装docker多系统操作示列(window为例)
  10. 三维重建 建立客观世界的虚拟现实||时空克隆 三维视频融合 投影融合 点卯 魔镜系列
  11. Xbrowser远程登录Ubuntu闪退问题的解决方案
  12. 从白嫖百度AI Studi Telsa V100 32G训练资源到使用tensorflow训练全流程(个人用)
  13. 计算机分区的优点,NTFS分区格式的优点及其转换
  14. 阿里巴巴矢量图标库icon图标在线引用
  15. 5e服务器显示fps被锁定,csgo强制被锁60帧 被锁60fps解决方法
  16. yolov5 6.0版本->onnx->ncnn +安卓部署 附加ncnn环境配置 保姆级详细教程
  17. Maven-Archetype Catalog
  18. JAVA计算机毕业设计二手手机回收平台系统Mybatis+源码+数据库+lw文档+系统+调试部署
  19. 2014 -- ISO 面试题
  20. 基于JAVA设计师品牌服装租赁网站计算机毕业设计源码+数据库+lw文档+系统+部署

热门文章

  1. 1到10所有数的立方
  2. android 悬浮按钮和可交互提示,悬浮按钮的使用原则
  3. Kony Development Cloud
  4. 《血族》全民模式火热开启 南北之战一触即发
  5. 解决数值输入框可以输入字母E的问题
  6. 为电子书迷测评几款PDF阅读器
  7. 机器学习-chapter1机器学习的生态系统
  8. J-Flash中添加MDK制作的QSPI Flash下载算法方法
  9. 7-1 定期存款 (10 分)
  10. python提取txt关键内容_python爬取关键字所在行并输出到txt