Redis高级篇(1):Java通过Socket与redis服务器建立通信并执行命令

一、Redis命令协议

  1. Redis客户端和服务器之间通过套接字(socket)进行通信
  2. 比如客户端向服务器发出 set name www.codecoord.com 命令,将会被转换成以下命令格式发送
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$17\r\nwww.codecoord.com\r\n
  1. 下面拆解以下命令格式
  • *3:表示命令去掉空格后的命令+参数的数量,比如set name www.codecoord.com就是由set、name、www.codecoord.com三个参数,所以格式为:*3
  • \r\n:换行符,从一个参数后每个命令单位都需要使用换行符分隔
  • $3:表示即将执行的命令或者参数的长度,此处为set,长度为3,故为$3
  • set:set命令
  • $4:name参数的长度
  • name:set命令需要的key名称
  • $17:key参数值的长度
  • www.codecoord.com:key参数
  • 如果显示换行符,效果会是下面的样子
*3	
$3
set
$4
name
$17
www.codecoord.com
  1. 如果打开持久化文件appendonly.aof,该文件就是保存的该命令协议格式
    在这里插入图片描述
  2. 如果现在执行get name命令,协议格式如下
*2\r\n$3\r\nGET\r\n$4\r\nname\r\n
  1. 符合以上命令协议的命令将会被redis服务器解析,所以我们可以这样做别的事情,比如自己做一个redis客户端等,第三节命令行工具实战将可以实现redis-cli工具的功能
  2. 测试使用redis命令
127.0.0.1:6379> set name www.codecoord.com
OK
127.0.0.1:6379> get name
"www.codecoord.com"

二、Java Socket连接

  1. 可以利用Java Socket与Redis服务器连接,如果对Socket使用还不太熟悉的朋友可以先了解一下Socket的相关内容
  2. Socket伪代码如下
// 1、创建连接
Socket socket  = new Socket(IP, Port);
// 2、按照命令协议拼接命令cmd,比如get name
// 3、定义存储数据数组
byte[] bytes;
int read;
// 4、获取socket的输出流,写入到输出流
socket.getOutputStream().write(cmd.toString().getBytes(StandardCharsets.UTF_8));
// 5、获取输入流,用于获取结果
InputStream stream = socket.getInputStream();
bytes = new byte[Short.MAX_VALUE];
read = stream.read(bytes);
// 6、显示结果
String response = new String(bytes, 0 , read, StandardCharsets.UTF_8);
System.out.println("执行命令:\r\n" + cmd.toString() + " response = " + response);
// 7、关闭流
stream.close();
socket.close();
  1. 示例代码
package com.codecoord;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

/**
 * Redis服务器命令协议
 *
 * @author tianxincode@163.com
 * @date 2020/4/5
 */
public class RedisSocketTest {
    private static final String SEPARATOR = "\r\n";

    public static void main(String[] args) {
        socketConnect();
    }

    private static void socketConnect() {
        Socket socket;
        try {
            socket = new Socket("127.0.0.1", 6379);
        } catch (IOException e) {
            System.err.println("创建连接失败~");
            return;
        }
        try {
        	// 设置超时,避免命令错误或者别的原因长时间等待
            socket.setSoTimeout(3000);
        } catch (SocketException e) {
            System.err.println("设置超时时间失败~");
            return;
        }
        StringBuilder cmd = new StringBuilder();
        // 命令加参数个数
        cmd.append("*2").append(SEPARATOR);
        // 当前命令长度
        cmd.append("$3").append(SEPARATOR);
        cmd.append("get").append(SEPARATOR);
        // 命令参数长度
        cmd.append("$4").append(SEPARATOR);
        cmd.append("name").append(SEPARATOR);

        byte[] bytes;
        int read;
        // 需要捕获异常,如果命令错误将会进入死等待,可以响应设置的超时时间
        try {
            socket.getOutputStream().write(cmd.toString().getBytes(StandardCharsets.UTF_8));
            InputStream stream = socket.getInputStream();
            bytes = new byte[Short.MAX_VALUE];
            read = stream.read(bytes);
            stream.close();
        } catch (Exception e) {
            System.err.println("执行命令超时~");
            return;
        }
        String response = new String(bytes, 0 , read, StandardCharsets.UTF_8);
        System.out.println("执行命令:\r\n" + cmd.toString());
        System.out.println("===========================");
        System.out.println("response = " + response);
        try {
            socket.close();
        } catch (IOException e) {
            System.err.println("关闭连接异常~");
        }
    }
}

在这里插入图片描述

三、命令行工具实战

  1. 可以使用Java Socket和Scanner实现redis-cli(客户端命令行工具)
  2. 实例代码如下
package com.codecoord;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
 * Redis服务器命令协议
 *
 * @author tianxincode@163.com
 * @date 2020/4/5
 */
public class RedisSocketTest {
    private static final String SEPARATOR = "\r\n";
    private static final String IP = "127.0.0.1";
    private static final int PORT = 6379;

    public static void main(String[] args) {
        // socketConnect();
        commandInput();
    }

    private static void commandInput() {
        Scanner scanner = new Scanner(System.in);

        String cmd;
        // bye作为退出的判断
        String exit = "bye";
        do {
            System.out.print(IP + PORT + "> ");
            cmd = scanner.nextLine();
            if (!exit.equalsIgnoreCase(cmd)) {
                parseCommand(cmd);
            }
        } while (!exit.equalsIgnoreCase(cmd));

        System.out.println("退出客户端~");
        scanner.close();
    }

    /**
     * 命令行解析, 这里只是演示,各种异常情况根据需要进行处理
     *
     * @param command 需要执行的命令
     */
    private static void parseCommand(String command) {
        Socket socket;
        try {
            socket = new Socket(IP, PORT);
        } catch (IOException e) {
            System.err.println("创建连接失败~");
            return;
        }
        try {
            socket.setSoTimeout(3000);
        } catch (SocketException e) {
            System.err.println("设置超时时间失败~");
            return;
        }
        // 空格或者制表符作为分隔依据
        String[] split = command.split("\\s+");

        // 命令解析
        StringBuilder cmd = new StringBuilder();
        cmd.append("*").append(split.length).append(SEPARATOR);
        for (String arg : split) {
            cmd.append("$").append(arg.length()).append(SEPARATOR);
            cmd.append(arg).append(SEPARATOR);
        }

        InputStream stream;
        String[] result;
        try {
            socket.getOutputStream().write(cmd.toString().getBytes(StandardCharsets.UTF_8));
            stream = socket.getInputStream();
            byte[] bytes = new byte[Short.MAX_VALUE];
            int read = stream.read(bytes);

            String response = new String(bytes, 0 , read, StandardCharsets.UTF_8);
            result = response.split(SEPARATOR);
        } catch (IOException e) {
            System.err.println("执行命令超时~");
            return;
        }

        // 简单命令如果没有错误最后一个是结果,实际情况中需要根据对应情况进行处理
        System.out.println(result[result.length - 1]);

        try {
            stream.close();
            socket.close();
        } catch (IOException e) {
            System.err.println("关闭连接异常~");
        }
    }

    /**
     * socket连接测试
     */
    private static void socketConnect() {
        Socket socket;
        try {
            socket = new Socket("127.0.0.1", 6379);
        } catch (IOException e) {
            System.err.println("创建连接失败~");
            return;
        }
        try {
            socket.setSoTimeout(3000);
        } catch (SocketException e) {
            System.err.println("设置超时时间失败~");
            return;
        }
        StringBuilder cmd = new StringBuilder();
        // 命令加参数个数
        cmd.append("*2").append(SEPARATOR);
        // 当前命令长度
        cmd.append("$3").append(SEPARATOR);
        cmd.append("get").append(SEPARATOR);
        // 命令参数长度
        cmd.append("$4").append(SEPARATOR);
        cmd.append("name").append(SEPARATOR);

        byte[] bytes;
        int read;
        try {
            socket.getOutputStream().write(cmd.toString().getBytes(StandardCharsets.UTF_8));

            InputStream stream = socket.getInputStream();
            bytes = new byte[Short.MAX_VALUE];
            read = stream.read(bytes);
            stream.close();
        } catch (IOException e) {
            System.err.println("执行命令超时~");
            return;
        }
        String response = new String(bytes, 0 , read, StandardCharsets.UTF_8);
        System.out.println("执行命令:\r\n" + cmd.toString());
        System.out.println("===========================");
        System.out.println("response = " + response);
        try {
            socket.close();
        } catch (IOException e) {
            System.err.println("关闭连接异常~");
        }
    }
}

  1. 执行结果
  • 127.0.0.16379中间忘记加个:,如果美观点加一下
  • 有任何问题可以留言相互交流
    在这里插入图片描述
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值