Netty学习之读netty权威指南(一)

Netty学习之读netty权威指南(一)大家问我为什么读这个来学netty,嗯嗯嗯??我也说不上来,因为我以前看过某个培训班的课程,初步了解了一下netty,但是现在回想一下发现我所有的知识基本忘光了,不过没关系,慢慢来,一点一点的找回来不久好了吗,现在开始咱们读一读Netty权威指南这本书,学习一下Netty。当然了不会全部按照这本书来,我会加上自己学习的东西。I/O演进之路JDK1.4以前Java对IO的支持不完…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

大家问我为什么读这个来学netty,嗯嗯嗯??我也说不上来,因为我以前看过某个培训班的课程,初步了解了一下netty,但是现在回想一下发现我所有的知识基本忘光了,不过没关系,慢慢来,一点一点的找回来不久好了吗,现在开始咱们读一读Netty权威指南这本书,学习一下Netty。当然了不会全部按照这本书来,我会加上自己学习的东西。

 I/O 演进之路

JDK 1.4 以前Java对 IO 的支持不完善,Java开发大佬在开发高并发的程序的时候不如 C 或者 C++,会面临一些挑战: 没有数据缓冲区啦,没有Channel的概念啦,BIO 性能太菜啦,支持的字符集有限呀之类的问题。后来大佬们开始励精图治,改变改变再改变最后终于达成所愿的励志故事,这个咱们就不细说了,百度一下子Linux的网络IO模型看一看,咱们就往后简单的说说BIO、NIO、AIO的简单例子,大家看看有啥不懂的先百度搜一下这三个东西。

 BIO

现在我们就以客户端向服务端发送请求获取当前时间的指令,而服务端返回当前时间给客户端的例子开始BIO。

TimeServer  也就是服务端代码

package BIO;

import javax.sound.midi.Soundbank;
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.text.SimpleDateFormat;
import java.util.Date;

public class TimeServer {
    public static void main(String[] args) {
        int port = 8989;//服务端启动的端口号

        ServerSocket serverSocket = null;
        try{
            serverSocket = new ServerSocket(port);//server端启动对接口的监听
            System.out.println("服务端启动了端口号是: "+ port);
            Socket socket = null;
            while (true){ //无限循环监听客户端的连接
                System.out.println("开始等待客户端连接");
                socket = serverSocket.accept();
                //启动一个线程用来处理客户端的连接
                System.out.println("客户端连接了");
                new Thread(new TimeServerHandler(socket)).start();
            }
        }catch (Exception e){
            System.out.println("服务端发生异常");
        }finally {
            if (serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    System.out.println("服务端关闭异常");
                    e.printStackTrace();
                }
            }
        }
    }
}

class TimeServerHandler implements Runnable{

   private  Socket socket;
    public TimeServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //定义输入输出流
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);

            String order = null;//接收客户端的数据
            String response = null ;//返回客户端的数据
            while (true){
                order = in.readLine();
                if (null == order) break;//如果读不到数据了就跳出循环
                response = "获取当前时间".equals(order)?new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()):"指令有误";
                //将当前时间返回去
                out.println(response);
                System.out.println("向客户端响应成功");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (in !=null)
                in.close();
                if (out !=null)
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (socket !=null)
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Timeclient  也就是客户端代码

package BIO;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TimeClient {


    public static void main(String[] args) {
        int port = 8989;
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1",port);
            //获取输入流()
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //获取输出流
            out = new PrintWriter(socket.getOutputStream(),true);
            String order = "获取当前时间";
            out.println(order);//向服务器发送数据
            System.out.println("向服务器发送指令完成,指令内容是:"+order);
            String recive = in.readLine();
            System.out.println("从服务器获取到的当前时间是:"+ recive);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (socket !=null)
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null)
                in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

上边的例子挺简单的,具体咱们就不分析了。BIO是同步阻塞的,一个客户端连接了,服务端就会启动一个线程来响应,太浪费资源了。BIO大家不是很了解的话建议先了解一下再往下看。

NIO

书上的太官方了,我通俗的解释一下。烧开水,去厨房烧开水,烧上水之后就爱干啥干啥去,隔段时间过来看看水开没开。NIO有三大组件

  • Selector:多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于“就绪状态”后(accept接收连接事件、connect连接完成事件、read读事件、write写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
  • Channel:封装了socket。
    • ServerSocketChannel:封装了ServerSocket,用于accept客户端连接请求;
    • SocketChannel:一对SocketChannel组成一条虚电路,进行读写通信
  • Buffer:用于存取数据,最主要的是ByteBuffer
    • position:下一个将被操作的字节位置
    • limit:在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数
    • capacity:Buffer的大小

现在咱们看一下NIO写的这个时间服务器的例子我理解的都写在注释里了

TimeServer 

package NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class TimeServer {
    public static void main(String[] args) {
        int port = 8999;
        //创建Handler
        MultiplexerTimeServer multiplexerTimeServer = new MultiplexerTimeServer(port);
        new Thread(multiplexerTimeServer,"Nio-00001").start();
    }
}


class MultiplexerTimeServer implements Runnable{

    private int port;

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private volatile boolean stop;
    public MultiplexerTimeServer(int port) {

        try {

            //开启一个通道
            serverSocketChannel = ServerSocketChannel.open();
            //将通道设置为非阻塞
            //在该socket上的读写都不阻塞,也就是读写操作立即返回,无论有没有数据。
            serverSocketChannel.configureBlocking(false);
            // serverSocketChannel.socket() 返回的是serverSocket
            //serverSocket.bind()的解释是开发中需要开启服务端的时候,本地测试都是直接写端口,实际环境也是需要指定要邦定的IP才可以。
            //因为对于服务器来说,有时它不止一块网卡,而我们系统一定是通过指定的IP和端口进行通信的,所以服务端所使用的IP和端口都需要定义配置文件。
            //那么在平常测试时,在没有指定IP的情况下,ServerSocket到底邦定到哪里了?
            //在这种情况下,服务器会把此端口绑定到0.0.0.0上面,即在所有IP上面都绑定,即能在每个ip上面收到请求。
            //1024 是最大连接数
            serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
            //启动多路复用器
            selector = Selector.open();
            //将channel注册进多路复用器 OP_CONNECT代表有客户端连接
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            //相当于做个开关
            this.stop = false;

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void stop(){
        this.stop = true;
    }

    //run正式处理业务
    @Override
    public void run() {
        //不执行stop()就是死循环
        while (!stop){
            try {
                //轮询器阻塞1秒钟
                selector.select(1000);
                //查询就绪的通道返回
                //SelectionKey是一个抽象类,
                // 表示selectableChannel在Selector中注册的标识.
                // 每个Channel向Selector注册时,都将会创建一个selectionKey
                Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                //Iterator遍历
                Iterator<SelectionKey> it = selectionKeySet.iterator();
                SelectionKey key = null;
                while (it.hasNext()){
                    key = it.next();
                    try {
                        handleInput(key);
                    }catch (Exception e){

                    }
                    //处理过key之后关闭 ???
                    if(key != null) {
                        key.cancel();
                        if(key.channel() != null) {
                            key.channel().close();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //多路复用器关闭以后,所有注册在上面的Channel和Pipe等资源都会自动去注册并关闭,所以不需要重复释放资源
        if(selector != null) {
            try{
                selector.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    public void handleInput(SelectionKey key) throws IOException {
        //判断这个key是否有效
        if (key.isValid()){
            //判断是哪种事件
            //SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
            //SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
            //SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
            //SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)
            if (key.isAcceptable()){
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                //获得处理请求的通道
                SocketChannel sc = serverSocketChannel.accept();
                //将channel设置为非阻塞
                sc.configureBlocking(false);
                //将此通道注册进轮询器,并监听输入事件
                sc.register(selector,SelectionKey.OP_READ);
            }
        }
        //判读是输入事件时处理
        if (key.isReadable()){
            SocketChannel sc = (SocketChannel)key.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            //将通道的内容读到buffer
            int readBytes = sc.read(readBuffer);
            //如果有数据
            if (readBytes > 0){
                readBuffer.flip();
                //创建一个跟buffer一样容量的字节数组
                byte[] bytes = new byte[readBuffer.remaining()];
                //将数据从buffer读到字节数组
                readBuffer.get(bytes);

                String body = new String(bytes, "UTF-8");
                String response = "获取当前时间".equals(body)?new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()):"指令有误";
                byte[] bytes1 = response.getBytes();
                ByteBuffer writeBuffer  = ByteBuffer.allocate(bytes1.length);
                writeBuffer.put(bytes1);
                writeBuffer.flip();
                sc.write(writeBuffer);
            }else{
                key.cancel();
                sc.close();
            }
        }

    }
}

AIO

咱们暂且先不用AIO,因为netty4没有用到,所以略过了,后边有机会补上。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/186654.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • java 线程interupt stop(dep)[通俗易懂]

    java 线程interupt stop(dep)[通俗易懂] java线程interuptstop(dep) https://blog.csdn.net/zbw18297786698/article/details/53432879/1、Java中API自带的stop()方法,来终止线程       查阅JDK,不难发现Thread提供了一个stop()方法,但是stop()方法是一个被废弃的方法。为什么sto…

    2022年10月24日
  • 织梦dedecsm系统”企业简介”类单栏目模版如何修改和调用

    织梦dedecsm系统”企业简介”类单栏目模版如何修改和调用

  • dategrip激活码【2021最新】

    (dategrip激活码)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

  • 如何完全禁止win10自动更新(自动升级)

    如何完全禁止win10自动更新(自动升级)一般来说,及时更新升级的操作系统是比较安全的。但是有的人对自动升级却非常讨厌。这里将介绍如何完全禁止win10自动升级。有多种方法,参照其一即可。转自:https://jingyan.baidu.com/article/1e5468f94dc9a3484961b7a8.html方法一(修改组策略)在cortana中输入gpedit.msc,打开通用管理文档在cortana中输入gpedit.m…

  • python:类基础「建议收藏」

    python:类基础「建议收藏」1、面向对象编程(oop)是一种程序设计思想。oop把对象作为程序的基本单元,一个对象包含数据和操作数据的函数2、在python中,所有数据类型都被视为对象,也可以自定义对象。自定义对象数据类型就是面向对象中类的概念1、类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例2、方法:类中定义的函数3、类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体(方法)之外。类变量通常不作为实例变量使用,类变量也称作属性4、数

  • android toast显示时间,Android Toast自定义显示时间「建议收藏」

    android toast显示时间,Android Toast自定义显示时间「建议收藏」Toast是Android中使用频率较高的弹窗提示手段,使用起来简单、方便。常规使用方法这里不做说明,继前一篇博客《Android中Toast全屏显示》,其中抛砖引玉的给出一个简单的实现Toast全屏显示的方法后,发现无法控制Toast的显示时长。虽然Toast中有setDuration(intduration)接口,但是跟踪代码发现,设置的时间没起作用,只有系统默认的两个时间LENGTH_D…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号