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 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程socket,BIO,NIO -> 正文阅读

[网络协议]网络编程socket,BIO,NIO

Socket理解

Socket是什么?Socket在JDK包里就是一个普普通通的类,里面定义了一组跟网络编程相关的方法(API),比如容易理解的connect(),bind()方法,分别是客户端建立连接,服务端绑定端口的API,从这个层面理解Socket就是一组处理网络io的API。它把TCP/IP协议族中网络通信代码(如TCP建立连接的过程)进行封装,相当于介于应用程序和TCP/IP协议族之间的中间软件抽象层。

以TCP协议为例,一台主机(client)上的应用程序需要与另一个主机(server)上的应用程序通信就必须通过Socket套接字建立连接,client端每建立一个连接就会创建一个socket实例,而server端每接受一个连接就会创建一个socket实例与client的socket进行通信,有多少个客户端建立连接就会server就会创建多少个socket实例。

短连接,长连接

短连接:建立连接——>数据传输——>关闭连接:数据传输完毕就关闭连接

长连接:建立连接——>数据传输——>数据传输。。。。——>关闭连接:即使没有数据传输也会保持连接状态,直到某一方关闭连接

网络IO编程

BIO(Blocking IO)

BIO,即阻塞的IO。server创建ServerSocket实例并绑定IP地址和端口号,调用accept()启动监听器。当client端创建Socket对server端发起连接时,server端接收到连接创建socket实例与client端socket实例进行通信,读取数据并处理业务,如图:单线程的BIO

?client代码

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        OutputStream os = null;
        try {
            socket.connect(new InetSocketAddress("localhost", 8888));
            os = socket.getOutputStream();
            os.write("hello world".getBytes("UTF-8"));
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

server代码

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("BIO server start.");
        while (true) {
            Socket accept = null;
            try {
                accept = serverSocket.accept();
                InputStream is = accept.getInputStream();
                byte[] b = new byte[1];
                int current;
                StringBuilder sb = new StringBuilder();
                //is.read(b): 会把is流中的数据读到字节数组中,返回这一次读取的字节数,
                //当字节数组满了,则会进行下一次循环。最后如果没有数据可读返回-1。
                while ((current = is.read(b)) != -1) {
                    sb.append(new String(b, 0, current));
                }
                System.out.println("read data: " + sb.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (accept != null) {
                    accept.close();
                }
            }
        }
    }

上面代码client向server发送"hello world",server读取数据并打印出来之后就关闭socket。

既然BIO是阻塞IO,那么阻塞体现在哪里呢?主要体现在两点

一、server启动应用程序调用serverSocket.accept()方法,线程就会一直阻塞在这里即使没有接收到连接,知道有连接才会继续执行。

二、启动server应用程序后,client1端debug断点打在建立连接之后,发送数据之前,并启动这个client1,此时同样的代码再启动一个client2向server发送数据,client2很快执行完成了,但是server此时没有任何打印,这是因为server端一直阻塞在read()等待client1发送数据过来。

由此看出,单线程模式的BIO处理效率有限,如果由于某client数据发送出现问题,会导致server段阻塞,自然会想到多线程去处理任务。

多线程处理任务BIO?

server每接收到一个连接,就创建一个线程处理该socket实例读取数据,执行业务。

多线程BIO server代码?

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8888);
            System.out.println("BIO server start.");
            while (true) {
                Socket accept = serverSocket.accept();
                new Thread(new Handler(accept)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class Handler implements Runnable {

        private Socket socket;

        public Handler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            InputStream is = null;
            try {
                byte[] b = new byte[1024];
                int current;
                StringBuilder sb = new StringBuilder();
                is = socket.getInputStream();
                //is.read(b): 会把is流中的数据读到字节数组中,返回这一次读取的字节数,
                //当字节数组满了,则会进行下一次循环。最后如果没有数据可读返回-1。
                while ((current = is.read(b)) != -1) {
                    sb.append(new String(b, 0, current));
                }
                System.out.println("read data: " + sb.toString());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

这种方式处理少数连接没有太大的问题,但是如果连接多了,很明显这种处理方式比较浪费线程资源,所以我们可以把线程资源通过线程池管理起来。

伪异步BIO

伪异步BIO把线程资源管理起来,通过一定数量的线程去处理多个连接。

代码就不演示了,比较简单。这种模式优势在于对线程资源的控制,但是限制了线程数量,在大并发量,数据读取比较慢时,其他连接就只能等待,这就是最大的弊端。

NIO

NIO即nonblock-IO,非阻塞IO。

它和IO最大的区别就是,NIO是面向缓冲区的,而java IO是面向流的。java IO在读取数据时,一次只能顺序读一个字节或多个字节,直到读取所有的数据;而NIO是从缓冲区读取或向缓冲区写入数据,缓冲区数据可以通过指针灵活读取数据。其次java IO在调用read()和write()方法时,会一直阻塞直到有数据可读或者可写。而java NIO的非阻塞模式体现在从通道读取数据到buffer时,只会读取目前可用的数据,如果没有可用的数据,它不会一直等待阻塞,而是什么都不做或者去处理其他事情。通过缓冲区向通道写入数据时,线程不需要等数据完全写入,同时可以去处理其他事情。java NIO通过利用线程空闲时间去做其他事情充分利用线程资源,因此一个线程可以管理多个输入和输出通道。

NIO之reactor模式

reactor译为反应堆,看各种资料“反应”即“倒置”或者“控制反转”,从中文角度来讲我有点想不通。无论如何,reactor模式表达的是client向server发起连接或者发送数据后,server端并不是被动执行连接读取数据,而是把ServerSocketChannel注册selector选择器上,再通过Reactor线程轮询selector读取这些已经就绪的事件(读、写或连接事件)。

单线程Reactor模式

单线程Reactor-工作者线程池?

多线程Reactor模式?

NIO三大核心组件

selector选择器

selector选择器是主要有两个作用:其一提供channel注册事件的容器,其二提供轮询事件的API,如:select(),select(int timeout)等。对于客户端和服务端在启动的时候,客户端会向Selector实例中注册一个连接事件,而这些事件存放在而线程会轮询检查是否到该事件。

channel通道

通道是被建立的一个应用程序和操作系统进行交互事件、传递数据的渠道。代码中体现出来的就是ServerSocketChannel和SocketChannel,从这两个channel中可以获取ServerSocket和Socket,不难看出,应用程序可以通过它们读取数据,同时也可以向它们写入数据。注意,通道是面向缓冲区,他只能操作缓冲区(buffer)

buffer缓冲区

buffer说到底就是一块内存空间(字节组数或者其他类型数组),应用程序通过channel可以向缓冲区写入数据,也可以从缓冲区读取数据。

三大组件处理数据方式

参考Netty权威指南NIO源码示例:

1、NIO server启动,创建selector选择器对象和ServerSocketChannel对象(简称ssc),将ssc通道设置为非阻塞模式,通过ssc向selector注册该通道感兴趣的接收事件。

2、NIO client启动,创建selector2选择器对象和SocketChannel对象(简称sc),将sc通道设置为非阻塞模式,通过sc.connect()向NIO server端发起tcp异步连接,如果连接成功则向selector2注册读事件,如果失败则注册连接事件。

3、server接收到连接事件,轮询调用selector.select()会收到就绪的事件并交由感兴趣的通道ssc处理,ssc接收到连接并创建socketChannel,socketChannel向selector注册感兴趣的读事件

4、client连接server成功后,调用doWrite()向server发送数据,server通过轮询selector.select()感知到有数据就绪(即读事件),通过selectionKey获取到socketChannel将数据读到buffer缓冲区,交由应用程序进行业务处理。

5、应用程序处理完成后需要响应客户端,将数据复制到buffer写缓冲,调用socketChannel.write()方法将buffer数据写出去,这是直接写出去;也可以通过调用socketChannel.register()向selector注册感兴趣的读事件,与此同时也会向就绪的事件集合中添加写事件,让selector能轮询到,最后通过write()发送给客户端,通过这种方式需要注意防止写空转的问题,在将数据发送给客户端以后可以通过调用key.interestOps(SelectionKey.OP_READ);将原来注册的写事件覆盖,否则selector中就绪事件集合中会一直存在写事件导致发生写空转,与此同时isWritable()是判断写缓冲是否有空闲或者说是否可以写入数据,那么它会一直返回true。

nio-client流程

nio-server流程

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:20:11  更:2022-04-18 18:20:26 
 
开发: 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 4:42:30-

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