IO的模型有三种,BIO(同步阻塞式IO),NIO(同步非阻塞式IO),AIO(异步非阻塞式IO),今天我们来谈谈BIO。

Java BIO:在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。

BIO:线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。具体如下图:

redis exporter的指标有哪些_jedis

上面谈到同步、异步、阻塞、非阻塞又是什么意思呢?以食堂打饭为例。

  • 同步:正常调用。你自己去食堂拿饭卡亲自去打饭(使用同步IO时,Java自己处理IO读写)
  • 异步:基于回调。委托一小弟拿饭卡到食堂打饭,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(饭卡),OS需要支持异步IO操作API)
  • 阻塞:没有开启新的线程。食堂排队打饭,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)
  • 非阻塞:付好钱,取个号,然后坐在椅子上做其它事,等号广播会通知你取饭,没到号你就不能取,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能取(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

我们下面来利用Java的BIO来实现一个时间服务器。书写的代码如下:

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//客户端代码
public class BioClient {
    public static void main(String[] args) {
        Socket socket = null;
        OutputStream outputStream = null;

        try {
            //连接服务器端
            socket = new Socket("127.0.0.1", 9999);
            //开启一个线程处理从服务端发送来
            new Thread(new BioClientHandler(socket)).start();
            //获取对应的输出流
            outputStream = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入要发送的消息:");
            while (true) {
                String s = scanner.nextLine();
                if (s.trim().equals("by")) {
                    break;
                }
                outputStream.write(s.getBytes());
                outputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
//客户端的处理器
public class BioClientHandler implements Runnable {

    private Socket socket;

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

    @Override
    public void run() {
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
            int count = 0;
            byte[] bytes = new byte[1024];
            while ((count = inputStream.read(bytes)) != -1) {
                System.out.println("\n收到服务器的消息:" + new String(bytes, 0, count, StandardCharsets.UTF_8));
                System.out.println("请输入要发送的消息:");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
//服务器端代码
public class BioServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(9999);
            TimeServerHandlerExecutorPool executorPool = new TimeServerHandlerExecutorPool();
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客户端" + socket.getRemoteSocketAddress().toString() + "来连接了");
                executorPool.execute(new BioServerHandler(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import java.util.concurrent.*;

public class TimeServerHandlerExecutorPool implements Executor {
    private ExecutorService executorService;

    /**
     * @param corePoolSize    核心线程数量
     * @param maximumPoolSize 线程创建最大数量
     * @param keepAliveTime   当创建到了线程池最大数量时  多长时间线程没有处理任务,则线程销毁
     * @param unit            keepAliveTime时间单位
     * @param workQueue       此线程池使用什么队列
     */
    public TimeServerHandlerExecutorPool(int maxPoolSize, int queueSize) {
        this.executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
                maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
    }

    /**
     * @param corePoolSize    核心线程数量
     * @param maximumPoolSize 线程创建最大数量
     * @param keepAliveTime   当创建到了线程池最大数量时  多长时间线程没有处理任务,则线程销毁
     * @param unit            keepAliveTime时间单位
     * @param workQueue       此线程池使用什么队列
     */
    public TimeServerHandlerExecutorPool(int corePoolSize, int maxPoolSize, int queueSize) {
        this.executorService = new ThreadPoolExecutor(corePoolSize,
                maxPoolSize, 120L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(queueSize));
    }

    @Override
    public void execute(Runnable command) {
        executorService.execute(command);
    }

}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;

public class BioServerHandler implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            int count = 0;
            String content = null;
            byte[] bytes = new byte[1024];
            while ((count = inputStream.read(bytes)) != -1) {
                String line = new String(bytes, 0, count, StandardCharsets.UTF_8);
                System.out.println(line);
                content = line.trim().equalsIgnoreCase("SJ") ? new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) : "你发的是啥?";
                outputStream.write(content.getBytes());
                outputStream.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

然后先运行BioServer,然后在运行BioClient,输入SJ,然后运行结果如下:

redis exporter的指标有哪些_java_02

redis exporter的指标有哪些_redis_03

可以看到我们程序并没有结束,一直在阻塞的接受,这就是BIO(同步阻塞式IO)

接下来我们要模拟的就是Redis的客户端,首先我们通过Jedis来连接我们自己写的服务器,看发送的是什么,然后手写Redis的客户端模拟发送的数据,最后再看看是否真的将我们的数据存入到Redis服务器中

  1. 我们首先导入jedis的依赖
<dependencies>
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>3.0.1</version>
	</dependency>
</dependencies>
  1. 书写Jedis客户端,我们利用的是自己书写的BIO服务器,主要看下发送的是什么命令
import redis.clients.jedis.Jedis;

public class JedisTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 9999);
        jedis.set("ys", "haha");
        jedis.incr("yy");
        jedis.get("ys");
    }
}

运行的结果如下:

redis exporter的指标有哪些_jedis_04

可以看到发送的内容如上图所示,但是这些又是什么东西呢?于是上网搜,原来Redis客户端Redis服务端通信的协议使RESP协议具体的中文官方的解释:Resp协议

自己对命令的通俗的解释

*3 //*表示接下来有几条命令,3表示有三组命令,以$分隔
$3 //$表示接下来的操作的字符是长度,3表示长度是3
SET
$2
ys
$4
haha
  1. 有了上述的知识后,大概知道要发送内容如下
jedis.set("ys", "haha");
jedis.incr("yy");
jedis.get("ys");

转换后的内容如下

*3 
$3 
SET
$2
ys
$4
haha
*2
$3
GET
$2
ys
*2
$4
INCR
$2
yy
  1. 有了上述的知识我们可以手写Redis的客户端
package myRedis;
//定义好命令的参数的类
public class Resp {

    public static final String STAR = "*";
    public static final String CRLF = "\r\n";
    public static final String DOLLAR = "$";

    public static enum command{
        SET,GET,INCR
    }
}
package myRedis;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class RedisSocket {

    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    //对应的构造函数,传入IP地址,端口号
    public RedisSocket(String ip, int prot) {
        if (!isCon()) {
            try {
                socket = new Socket(ip, prot);
                inputStream = socket.getInputStream();
                outputStream = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //发送命令
    public void send(String string) {
        System.out.println(string);
        try {
            outputStream.write(string.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读取返回的结果
    public String read() {
        byte[] b = new byte[1024];
        int count = 0;
        try {
            count = inputStream.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new String(b, 0, count);
    }

    //判断是否断开连接
    private boolean isCon() {
        return socket != null && socket.isClosed() && socket.isConnected();
    }

    //关闭连接
    public void close() {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
package myRedis;

public class RedisClient {

    private RedisSocket socket;

    public RedisClient(String ip, int prot) {
        this.socket = new RedisSocket(ip, prot);
    }

    //对应的set命令
    public String set(String key, String value) {
        socket.send(RedisUtil(Resp.command.SET, key.getBytes(), value.getBytes()));
        return socket.read();
    }

    public void close() {
        socket.close();
    }

    //对应的get命令
    public String get(String key) {
        socket.send(RedisUtil(Resp.command.GET, key.getBytes()));
        return socket.read();
    }

    //对应的incr命令
    public String incr(String key) {
        socket.send(RedisUtil(Resp.command.INCR, key.getBytes()));
        return socket.read();
    }

    //将传进来的参数转成Redis的命令
    private String RedisUtil(Resp.command command, byte[]... bytes) {
        StringBuilder sb = new StringBuilder();
        sb.append(Resp.STAR).append(1 + bytes.length).append(Resp.CRLF);
        sb.append(Resp.DOLLAR).append(command.toString().getBytes().length).append(Resp.CRLF);
        sb.append(command.toString()).append(Resp.CRLF);
        for (byte[] aByte : bytes) {
            sb.append(Resp.DOLLAR).append(aByte.length).append(Resp.CRLF);
            sb.append(new String(aByte)).append(Resp.CRLF);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        RedisClient redisClient = new RedisClient("192.168.182.133", 6379);
        System.out.println(redisClient.set("ys", "haha"));
        System.out.println(redisClient.get("ys"));
        System.out.println(redisClient.incr("yy"));
        redisClient.close();
    }
}

运行上面的代码,运行之前的Redis如下所示:

redis exporter的指标有哪些_jedis_05

运行后的结果如下所示:

redis exporter的指标有哪些_jedis_06

可以看到我们的设置值都设置进去了。

redis exporter的指标有哪些_客户端_07