Redis是最常見的緩存服務中間件,在java開發中,一般使用 jedis 來實現。
如果不想依賴第三方組件,自己實現一個簡單的redis客戶端工具,該如何實現呢?本文就是介紹這樣一種方法。
Redis的協議非常簡單,而且輸入數據和輸出數據都遵循統一的協議,具體規則參考這里:
http://redisdoc.com/topic/protocol.html
Redis的命令協議:
$參數數量n
$參數1的值的字節數組長度
$參數1的值的字符串表示
$參數2的值的字節數組長度
$參數2的值的字符串表示
...
$參數n的值的字節數組長度
$參數n的值的字符串表示
Redis的返回協議:
1、狀態回復(status reply)的第一個字節是 "+",單行字符串;
2、錯誤回復(error reply)的第一個字節是 "-";
3、整數回復(integer reply)的第一個字節是 ":";
4、批量回復(bulk reply)的第一個字節是 "$";
5、多條批量回復(multi bulk reply)的第一個字節是 "*";
6、所有的命令都是以 \r\n 結尾。
Java代碼說明
針對上述規則,我們用兩個類來實現:
1、SimpleRedisClient類,主要用于發送請求,并讀取響應結果(字符串);
整體比較簡單,稍微復雜點的地方就是讀取流數據,遇到兩種情況就該結束循環,一是返回長度為-1,二是返回字符串以 \r\n 結尾。
如果處理不當,可能會導致 read 阻塞,Socket卡住。
2、SimpleRedisData類,用于解析響應結果,把redis統一協議的字符串,解析為具體的對象。
這部分代碼完全是按照協議規則來實現的,通過一個游標 pos 來向前移動,在移動過程中識別不同格式的數據。
最復雜的是 list 類型的數據,以 * 開頭,后面跟著一個整數,表示列表中所有元素的數量,然后就是每一個列表元素的值,循環解析即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
package demo; 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( 8 * 1000 ); //8秒 } 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 ; } String res = new SimpleRedisData(response).getString(); 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); return new SimpleRedisData(response).getString(); } 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); return new SimpleRedisData(response).getString(); } 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); return new SimpleRedisData(response).getString(); } public List<String> getKeys(String pattern) throws IOException { if (socket == null || pattern == null || pattern.isEmpty()) { return null ; } String response = execute( "KEYS" , pattern); return new SimpleRedisData(response).getStringList(); } 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; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package demo; 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 String getString() { if (rawData == null || rawData.length() <= 0 ) { return null ; } int i = rawData.indexOf( "\r\n" , pos); if (i <= 0 ) { return null ; } char c = rawData.charAt(pos); if (c == '+' ) { int from = pos + 1 ; int to = i; String v = rawData.substring(from, to); pos = to + 2 ; return v; } else if (c == '-' ) { int from = pos + 1 ; int to = i; String v = rawData.substring(from, to); pos = to + 2 ; return v; } else if (c == ':' ) { int from = pos + 1 ; int to = i; String v = rawData.substring(from, to); pos = to + 2 ; return v; } else if (c == '$' ) { 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 ; } } else { return null ; } } public List<String> getStringList() { if (rawData == null || rawData.length() <= 0 ) { return null ; } int i = rawData.indexOf( "\r\n" , pos); if (i <= 0 ) { return null ; } char c = rawData.charAt(pos); if (c == '*' ) { 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(getString()); } return values; } else { return null ; } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package demo; import org.junit.jupiter.api.Test; import java.util.List; public class RedisTest { @Test public void test() { SimpleRedisClient client = null ; try { client = new SimpleRedisClient( "127.0.0.1" , 6379 , "123456" ); 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 Redis客戶端的資料請關注服務器之家其它相關文章!
原文鏈接:https://www.cnblogs.com/lavezhang/archive/2021/05/26/14810078.html