| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> 网络IO Socket -> 正文阅读 |
|
[网络协议]网络IO Socket |
1. 概念 网络IO的过程,就是操作系统接收到网卡的数据,缓存到一个buffer中,然后应用程序调用操作系统的函数,从对应的buffer中取出数据。 2. 常见IO模型 模拟客户端连接: public static void main(String[] args) { ??? try { ??????? Socket socket = new Socket("127.0.0.1", 8082); ??????? DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); ??????? dos.writeUTF("helloServer!"); ??????? dos.flush(); ??????? dos.close(); ??????? socket.close(); ??? } catch (IOException e) { ??????? e.printStackTrace(); ??? } } 2.1 BIO 无论是获取新的连接还是读取指定连接的数据,调用操作系统的函数都是阻塞的,如果要实现服务多个连接,就必须每个连接建立一个线程异步处理,否则,当建立起一个连接,但是客户端不发送数据,服务端就会被这个客户端占用,无法接受新的连接。 2.1.1模拟服务端连接 public static void main(String[] args) { ??? try { ? ??????ServerSocket serverSocket = new ServerSocket(8082); ??????? while(true){ ??????????? Socket socket = serverSocket.accept();//阻塞 ?????????? //服务端与客户端每建立一次连接就会开启一个线程处理 ??????????? new Thread(new Runnable() { ??????????????? @Override ??????????????? public void run() { ??????????????????? try { ??????????????????????? DataInputStream dis = new DataInputStream(socket.getInputStream());//阻塞 ??????????????????????? System.out.println(dis.readUTF()); ??????????????????????? dis.close(); ? ??????????????????????socket.close(); ??????????????????? } catch (IOException e) { ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????? }).start(); ??????? } ??? } catch (IOException e) { ??????? e.printStackTrace(); ??? } } 2.1.2 优势 1.多线程,多连接,可以接收很多连接 2.1.3 劣势
2.2 NIO 解决了阻塞的问题,程序调用操作系统的函数,如果没有连接或数据,会立即返回,不会阻塞,避免了资源无效浪费。但是,它的问题在于,如果我有1万个连接,每次我需要挨个询问1万次,这个复杂度是O(n)的。每次询问都是一次系统调用,涉及到CPU的用户态内核态切换,成本很高。 2.2.1模拟服务端连接 public static void main(String[] args) throws Exception { ??? ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ??? serverSocketChannel.bind(new InetSocketAddress(8082)); ??? //OS false-NONBLOCKING,true-BLOCKING ??? serverSocketChannel.configureBlocking(false); ??? //客户端连接socket集合 ??? LinkedList<SocketChannel> clients = new LinkedList<>(); ??? while(true){ ??????? Thread.sleep(1000); ??????? //serverSocketChannel.accept()不阻塞,linux底层没有客户端连接时返回-1,有客户端连接返回客户端的fd ??????? SocketChannel socketChannel = serverSocketChannel.accept(); ??????? if(socketChannel == null){ ??????????? System.out.println("null..."); ??????? }else{ ??????????? //OS false-NONBLOCKING,true-BLOCKING ??????????? socketChannel.configureBlocking(false); ??????????? System.out.println("clientport:" + socketChannel.socket().getPort()); ??????????? clients.add(socketChannel); ??????? } ??????? //遍历连接上的客户端socket读写数据 ??????? ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096); ??????? clients.forEach(client->{ ??????????? try { ???????? ???????int num = client.read(byteBuffer); ??????????????? if(num > 0){ ??????????????????? byteBuffer.flip(); ??????????????????? byte[] bytes = new byte[byteBuffer.limit()]; ??????????????????? byteBuffer.get(bytes); ??????????????????? String result = new String(bytes); ??????????????????? System.out.println(result); ??????????????????? byteBuffer.clear(); ??????????????? } ??????????? } catch (IOException e) { ??????????????? e.printStackTrace(); ??????????? } ????? ??}); ??? } } 2.2.2优势 1.规避了C10K问题; 2.2.3劣势 1.存在很多无意义的系统调用,用户态和内核态切换消耗时间和资源; 2.3多路复用器 2.3.1 select 这是最初级的多路复用器,从NIO到多路复用器,其实就是一个从多次到批量的演进,多路复用器支持一次询问多个文件描述符(fd)(linux中,一切皆为文件,连接也是文件,有对应的文件描述符)。从多次到批量,就能节省大量的运行态切换成本。但是select的问题在于,批量有上限,是有限的批量。 2.3.2 poll 解决了select的上限问题,一次可以询问任意个数的fd,真正做到了批量。但是,即使减少了运行态切换的成本,针对每次传来的fd,操作系统依然需要逐个遍历,复杂度依然是O(n),只是每次操作的损耗降低了。 2.3.3 epoll 解决了POLL和Select存在的遍历问题,将复杂度降为O(1),操作系统提前维护好用户程序对应的fd,每次有数据到达,就把对应的fd放到一个数据结构中存起来,当用户程序需要读取数据时,直接把这些有状态的fd返回,用户程序一次性获取fd,逐个读取即可。用户只要调用一次,操作系统也不需要遍历。这是目前大部分场景下,最高效的模型。 2.3.4模拟服务端调用 //select poll epoll public static void main(String[] args) throws Exception { ??? ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ??? serverSocketChannel.bind(new InetSocketAddress(8082)); ??? //OS false-NONBLOCKING,true-BLOCKING ??? serverSocketChannel.configureBlocking(false); ??? /* ??? 多路复用器?select poll epoll?优先选择:epoll?但是可以-D修正 ??? epoll: epoll_create -> fd7 ???? */ ??? Selector selector = Selector.open(); ??? /* ??? 将channel注册到selector上 ??? select,poll:jvm里开辟一个数组fd3放进去 ??? epoll:epoll_ctl(fd7,ADD,fd3,EPOLLIN ???? */ ??? serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); ??? while(true){ ??????? /* ??????? 调用多路复用器 ??????? select,poll:内核的select(fd3) poll(fd3) ??????? epoll:内核的epoll_wait() ??????? 阻塞,有时间设置一个超时 ???????? */ ??????? while(selector.select(500) > 0){ ??????????? Set<SelectionKey> selectionKeys = selector.selectedKeys(); ??????????? Iterator<SelectionKey> iterator = selectionKeys.iterator(); ??????????? while(iterator.hasNext()){ ??????????????? SelectionKey key = iterator.next(); ??????????????? iterator.remove();//set 不移除会重复循环处理 ??????????????? if(key.isAcceptable()){ ??????????????????? /* ??????????????????? accept接受连接且返回新连接的fd,那新的fd怎么办? ??????????????????? select,poll:因为他们内核没有空间,那么jvm中保存和前边的fd3那个listen的一起 ??????????????????? epoll:我们希望通过epoll_ctl把新的客户端fd注册到内核空间 ???????????????????? */ ??????????????????? System.out.println("a connection was accepted by a ServerSocketChannel."); ????????? ??????????ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); ??????????????????? SocketChannel client = ssc.accept();//目的是调用accept接受客户端fd8 ??????????????????? client.configureBlocking(false); ??????????????????? ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8192); ??????????????????? /* ??????????????????? select,poll:jvm开辟一个数组fd8放进去 ??????????????????? epoll:epoll_ctl(fd7,ADD,fd8,EPOLLIN ???????????????????? */ ??????????????????? client.register(selector, SelectionKey.OP_READ, byteBuffer); ??????????????????? System.out.println("新客户端:" + client.getRemoteAddress()); ??????????????? }else if(key.isConnectable()){ ??????????????????? System.out.println("a connection was established with a remote server."); ??????????????? }else if(key.isReadable()){ ??????????????????? System.out.println("a channel is ready for reading"); ??????????????????? SocketChannel socketChannel = (SocketChannel) key.channel(); ??????????????????? ByteBuffer byteBuffer = (ByteBuffer) key.attachment(); ?????????????????? ?byteBuffer.clear(); ??????????????????? int read = 0; ??????????????????? while(true){ ??????????????????????? //... ??????????????????? } ??????????????? }else if(key.isWritable()){ ??????????????????? System.out.println("a channel is ready for writing"); ??????????????? } ??????????? } ??????? } ??? } } 2.4 AIO 以上由于IO还是需要应用程序自己读取,所以都属于同步IO模型,AIO是由操作系统内核来读取IO,再通知应用系统; |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 9:55:56- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |