如何用Java Socket实现一个简单的Redis客户端工具

Posted 卡卡西村长的小灶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用Java Socket实现一个简单的Redis客户端工具相关的知识,希望对你有一定的参考价值。

Redis是最常见的缓存服务中间件,在java开发中,一般使用 jredis 来实现。

如果不想依赖第三方组件,自己实现一个简单的redis客户端工具,该如何实现呢?本文就是介绍这样一种方法。

 

Redis的协议非常简单,而且输入数据和输出数据都遵循统一的协议,具体规则参考这里:

http://redisdoc.com/topic/protocol.html

 

最重要的规则有如下几点:

状态回复(status reply)的第一个字节是 "+"
错误回复(error reply)的第一个字节是 "-"
整数回复(integer reply)的第一个字节是 ":"
批量回复(bulk reply)的第一个字节是 "$"
多条批量回复(multi bulk reply)的第一个字节是 "*"

 

针对上述规则,我们用两个类来实现:

1、SimpleRedisClient类,主要用于发送请求,并读取响应结果(字符串);

2、SimpleRedisData类,用于解析响应结果,把redis统一协议的字符串,解析为具体的对象。

package tebon.kjcrm;

import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.util.List;

public class SimpleRedisClient implements Closeable {

    private String host;
    private int port;
    private String auth;
    private Socket socket = null;

    public SimpleRedisClient(String host, int port, String auth) {
        this.host = host;
        this.port = port;
        this.auth = auth;

        try {
            socket = new Socket(this.host, this.port);
            socket.setSoTimeout(60 * 1000);
            socket.setSendBufferSize(102400);
            socket.setReceiveBufferSize(102400);
        } catch (Exception ex) {
            socket = null;
            ex.printStackTrace();
        }
    }

    public boolean connect() throws IOException {
        if (socket == null || auth == null || auth.length() <= 0) {
            return false;
        }
        String response = execute("AUTH", auth);
        if (response == null || response.length() <= 0) {
            return false;
        }
        Object data = new SimpleRedisData(response).getObject();
        String res = data != null ? data.toString() : null;
        return "OK".compareTo(res) == 0;
    }

    @Override
    public void close()  {
        try {
            if (socket != null) {
                socket.shutdownOutput();
                socket.close();
            }
            //System.out.println("closed");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public String getString(String key) {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        try {
            String response = execute("GET", key);
            Object data = new SimpleRedisData(response).getObject();
            return data != null ? data.toString() : null;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public String setString(String key, String value) {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        try {
            String response = execute("SET", key, value);
            if (response == null || response.length() <= 0) {
                return null;
            }
            Object data = new SimpleRedisData(response).getObject();
            return data != null ? data.toString() : null;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public String deleteKey(String key) throws IOException {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        String response = execute("DEL", key);
        if (response == null || response.length() <= 0) {
            return null;
        }
        Object data = new SimpleRedisData(response).getObject();
        return data != null ? data.toString() : null;
    }

    public List<String> getKeys(String pattern) throws IOException {
        if (socket == null || pattern == null || pattern.isEmpty()) {
            return null;
        }

        String response = execute("KEYS", pattern);
        if (response == null || response.length() <= 0) {
            return null;
        }

        Object data = new SimpleRedisData(response).getObject();
        return data != null ? (List<String>)data : null;
    }

    public String execute(String... args) throws IOException {
        if (socket == null || args == null || args.length <= 0) {
            return null;
        }

        //System.out.println(StringUtil.join(args, " "));

        StringBuilder request = new StringBuilder();
        request.append("*" + args.length).append("\\r\\n");//参数的数量

        for (int i = 0; i < args.length; i++) {
            request.append("$" + args[i].getBytes("utf8").length).append("\\r\\n");//参数的长度
            request.append(args[i]).append("\\r\\n");//参数的内容
        }

        socket.getOutputStream().write(request.toString().getBytes());
        socket.getOutputStream().flush();

        StringBuilder reply = new StringBuilder();
        int bufSize = 1024;
        while (true) {
            byte[] buf = new byte[bufSize];
            int len = socket.getInputStream().read(buf);
            if (len < 0) {
                break;
            }
            String str = new String(buf, 0, len);
            reply.append(str);
            if (str.endsWith("\\r\\n")) {
                break;
            }
        }

        String response = reply.toString();
        //System.out.println("response: " + response);
        return response;
    }


}

 

package tebon.kjcrm;

import java.util.ArrayList;
import java.util.List;

public class SimpleRedisData {

    public SimpleRedisData(String rawData) {
        this.rawData = rawData;
        //System.out.println(rawData);
    }

    private int pos;
    private String rawData;

    public Object getObject() {
        if (rawData == null || rawData.length() <= 0) {
            return null;
        }
        char c = rawData.charAt(pos);
        if (c == \'+\') {
            return getSingleLineString();
        } else if (c == \'-\') {
            return getErrorString();
        } else if (c == \':\') {
            return getNumberString();
        } else if (c == \'$\') {
            return getBlukString();
        } else if (c == \'*\') {
            return getMultiBulkStringList();
        } else {
            return null;
        }
    }

    private String getSingleLineString() {
        int i = rawData.indexOf("\\r\\n", pos);
        if (i > 0) {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        }
        return null;
    }

    private String getErrorString() {
        int i = rawData.indexOf("\\r\\n", pos);
        if (i > 0) {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        }
        return null;
    }

    private String getNumberString() {
        int i = rawData.indexOf("\\r\\n", pos);
        if (i > 0) {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        }
        return null;
    }

    private String getBlukString() {
        int i = rawData.indexOf("\\r\\n", pos);
        if (i > 0) {
            int from = pos + 1;
            int to = i;
            int bulkSize = Integer.parseInt(rawData.substring(from, to));
            pos = to + 2;

            from = pos;
            to = pos + bulkSize;
            try {
                //$符号后面的数值是指内容的字节长度,而不是字符数量,所以要转换为二进制字节数组,再取指定长度的数据
                byte[] buf = rawData.substring(from).getBytes("utf-8");
                String v = new String(buf, 0, bulkSize);
                pos = to + 2;
                return v;
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        }
        return null;
    }

    private List<String> getMultiBulkStringList() {
        int i = rawData.indexOf("\\r\\n", pos);
        if (i > 0) {
            List<String> values = new ArrayList<>();
            int from = pos + 1;
            int to = i;
            int multSize = Integer.parseInt(rawData.substring(from, to));
            pos = to + 2;
            for (int index = 0; index < multSize; index++) {
                values.add(getBlukString());
            }
            return values;
        }
        return null;
    }

}

 

package tebon.kjcrm;

import org.junit.jupiter.api.Test;

import java.util.List;

public class RedisTest {

    @Test
    public void test() {
        SimpleRedisClient client = null;
        try {
            client = new SimpleRedisClient("172.16.101.43", 6379, "Tebon@2019");
            System.out.println("connected: " + client.connect());

            List<String> keyList = client.getKeys("api_*");

            for (int i = 0; i < keyList.size(); i++) {
                System.out.println((i + 1) + "\\t" + keyList.get(i));
            }

           System.out.println("keys: " + keyList != null ? keyList.size() : "null");

            System.out.println(client.getString("api_getCustomerName"));

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (client != null) {
                client.close();
            }
        }
    }

}

 

优点:

1、不依赖任何第三方组件,可以顺利编译通过;

2、代码及其简单。

 

不足之处:

1、未考虑并发访问;

2、未提供更多的数据类型,以及读写方法。

 

以上是关于如何用Java Socket实现一个简单的Redis客户端工具的主要内容,如果未能解决你的问题,请参考以下文章

java的socket通信

在java中如何用Socket 进行 服务器端和客户端交互的,具体一点。

如何用socket构建一个简单的Web Server

socket如何用多线程实现同时能接收和发送消息

如何用WPF实现一个最简单的Mvvm示例

如何用c语言编写socket套接字