IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: 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 劣势

  1. 线程内存消耗;
  2. cpu调度消耗;
  3. C10K问题;

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

解决了POLLSelect存在的遍历问题,将复杂度降为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地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-12-02 17:07:55  更:2021-12-02 17:08:19 
 
开发: 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年7日历 -2024/7/6 7:08:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码