redis 从服务器给主服务器发送ACK时,主服务器长时间不读取socket,则会出现错误。

在调试Redis源码过程中,遇到从服务器不断向主服务器发送ACK命令直至socket写缓冲区满的问题。当主服务器尝试读取这些命令时,由于接收缓冲区中的数据未对齐,导致读取不完整命令并引发错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一直在重写redis 源码,在调试时发现一个错误:

错误发生的场景如下:

从服务器不下断点,而主服务器的断点一直卡在,readQueryFromClient函数中的read函数之前。


在这种情况下,从服务器会一直向主服务器发送ACK,直到socket写缓冲区被写满,才停止。而主服务器由于一直被卡在read函数之前,所以socket接收缓冲区(我这缓冲区大小是335KB)的中数据一直没有被读取。这时如果让主服务器执行,它会读取socket接收缓冲区中的数据,而redis默认一次读取16K的数据。

ACK命令的全写是:

*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r\n1\r\n

 一共是34个字节,如果接收缓冲区中的数据大于16K时,它在读取最后一条命令时会剩余4个字节没读(34-16*1024/34=4)

如下解释:
在接收缓冲区中的内存内容是这样的:
*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r\n1\r\n*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r\n1\r\n*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r\n1\r\n*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r\n1\r\n............*3\r\n$8\r\nREPLCONF\r\n$3\r\nACK\r\n$1\r (到这里是16*1024个字节,后面的会在下次被读) \n1\r\n

而下次读的时候读到\n1\r\n,这对于redis来说是一条错误的命令,所以redis会报错说找不到指令。
这种错误只会发生在调试阶段,而正式版之所以不会出现这种错误,应该是对主服务器有超时判断。

1. 服务器端 - ChatServer.java import java.io.*; import java.net.*; import java.util.*; public class ChatServer { private static final int PORT = 8080; private static Set<PrintWriter> clientWriters = new HashSet<>(); public static void main(String[] args) { System.out.println("聊天服务器启动中..."); try (ServerSocket serverSocket = new ServerSocket(PORT)) { System.out.println("服务器已启动,监听端口: " + PORT); while (true) { new ClientHandler(serverSocket.accept()).start(); } } catch (IOException e) { System.out.println("服务器异常: " + e.getMessage()); } } private static class ClientHandler extends Thread { private Socket socket; private PrintWriter out; private BufferedReader in; private String nickname; // 存储实际昵称 public ClientHandler(Socket socket) { this.socket = socket; } public void run() { try { in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); // 读取客户端发送的第一条消息作为昵称 nickname = in.readLine(); if (nickname == null || nickname.trim().isEmpty()) { nickname = "匿名用户"; } synchronized (clientWriters) { clientWriters.add(out); } // 使用实际昵称广播加入消息 System.out.println(nickname + " 加入了聊天室"); broadcastSystemMessage(nickname + " 加入了聊天室"); String message; while ((message = in.readLine()) != null) { System.out.println("收到消息: " + message); broadcastMessage(message, out); } } catch (IOException e) { System.out.println("客户端断开: " + e.getMessage()); } finally { try { socket.close(); } catch (IOException e) { // 忽略 } synchronized (clientWriters) { clientWriters.remove(out); } // 使用实际昵称广播退出消息 if (nickname != null) { System.out.println(nickname + " 离开了聊天室"); broadcastSystemMessage(nickname + " 离开了聊天室"); } } } private void broadcastMessage(String message, PrintWriter senderWriter) { synchronized (clientWriters) { for (PrintWriter writer : clientWriters) { if (writer != senderWriter) { writer.println(message); } } } } private void broadcastSystemMessage(String message) { long timestamp = System.currentTimeMillis(); String systemMessage = "系统|" + message + "|" + timestamp; synchronized (clientWriters) { for (PrintWriter writer : clientWriters) { writer.println(systemMessage); } } } } } 2.Android客户端AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="骆树涛群聊" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ChatActivity" /> <activity android:name=".SettingsActivity" /> </application> </manifest> arrays.xml代码 <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="color_entries"> <item>黑色</item> <item>红色</item> <item>黄色</item> <item>蓝色</item> <item>绿色</item> </string-array> <string-array name="color_values"> <item>#FF000000</item> <item>#FFFF0000</item> <item>#FFFFFF00</item> <item>#FF0000FF</item> <item>#FF00FF00</item> </string-array> <string-array name="bg_entries"> <item>科技</item> <item>书籍</item> <item>简约</item> </string-array> <string-array name="bg_values"> <item>bg_books</item> <item>bg_nature</item> <item>bg_simple</item> </string-array> </resources> Srimg.xml代码 <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <EditTextPreference android:key="font_size" android:title="字号设置" android:summary="输入文字大小(单位:sp)" android:dialogTitle="设置字号" android:defaultValue="15" android:inputType="number"/> <ListPreference android:key="text_color" android:title="文字颜色" android:summary="选择文字显示颜色" android:dialogTitle="选择颜色" android:defaultValue="#FF000000" android:entries="@array/color_entries" android:entryValues="@array/color_values"/> <ListPreference android:key="bg" android:title="聊天背景" android:summary="选择聊天背景图片" android:dialogTitle="选择背景" android:entries="@array/bg_entries" android:entryValues="@array/bg_values" android:defaultValue="bg_tech"/> </PreferenceScreen> ChatMessage.java package com.example.myapplication; public class ChatMessage { private String nickname; private String message; private long timestamp; private boolean isSelf; public ChatMessage(String nickname, String message, long timestamp, boolean isSelf) { this.nickname = nickname; this.message = message; this.timestamp = timestamp; this.isSelf = isSelf; } public String getNickname() { return nickname; } public String getMessage() { return message; } public long getTimestamp() { return timestamp; } public boolean isSelf() { return isSelf; } } ChatAdapter.java package com.example.myapplication; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.MessageViewHolder> { private List<ChatMessage> messages; private Context context; public ChatAdapter(List<ChatMessage> messages, Context context) { this.messages = messages; this.context = context; } @Override public int getItemViewType(int position) { return messages.get(position).isSelf() ? 1 : 0; } @NonNull @Override public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view; if (viewType == 1) { view = inflater.inflate(R.layout.message_item_self, parent, false); } else { view = inflater.inflate(R.layout.message_item, parent, false); } return new MessageViewHolder(view); } @Override public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) { ChatMessage message = messages.get(position); holder.messageText.setText(message.getMessage()); holder.nicknameText.setText(message.getNickname()); // 格式化间 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); holder.timeText.setText(sdf.format(new Date(message.getTimestamp()))); // 应用设置 - 特别处理自己消息的颜色 applySettings(holder, message.isSelf()); } private void applySettings(MessageViewHolder holder, boolean isSelf) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); // 设置字号 float fontSize = Float.parseFloat(prefs.getString("font_size", "15")); holder.messageText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); holder.nicknameText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize - 2); holder.timeText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize - 4); // 设置文字颜色 - 自己消息使用特殊颜色 String colorValue; if (isSelf) { // 自己消息使用固定颜色,确保可读性 colorValue = "#FF000000"; // 黑色 } else { colorValue = prefs.getString("text_color", "#FF000000"); } holder.messageText.setTextColor(Color.parseColor(colorValue)); holder.nicknameText.setTextColor(Color.parseColor(colorValue)); holder.timeText.setTextColor(Color.parseColor(colorValue)); } @Override public int getItemCount() { return messages.size(); } static class MessageViewHolder extends RecyclerView.ViewHolder { TextView nicknameText, messageText, timeText; public MessageViewHolder(@NonNull View itemView) { super(itemView); nicknameText = itemView.findViewById(R.id.nickname); messageText = itemView.findViewById(R.id.message); timeText = itemView.findViewById(R.id.time); } } } MainActivity.java package com.example.myapplication; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.widget.Button; import android.widget.EditText; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private static final int SETTINGS_REQUEST = 1; private EditText nicknameEditText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); nicknameEditText = findViewById(R.id.nickname_edit_text); Button enterChatButton = findViewById(R.id.enter_chat_button); Button settingsButton = findViewById(R.id.settings_button); // 从SharedPreferences加载保存的昵称 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String savedNickname = prefs.getString("nickname", ""); nicknameEditText.setText(savedNickname); enterChatButton.setOnClickListener(v -> { // 保存昵称到SharedPreferences String nickname = nicknameEditText.getText().toString().trim(); if (nickname.isEmpty()) { nickname = "匿名用户"; } SharedPreferences.Editor editor = prefs.edit(); editor.putString("nickname", nickname); editor.apply(); // 启动聊天室 Intent intent = new Intent(MainActivity.this, ChatActivity.class); startActivity(intent); }); settingsButton.setOnClickListener(v -> startActivityForResult( new Intent(MainActivity.this, SettingsActivity.class), SETTINGS_REQUEST)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SETTINGS_REQUEST && resultCode == SettingsActivity.RESULT_SETTINGS_CHANGED) { // 设置已更改,更新界面背景 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String bgKey = prefs.getString("bg", "bg_tech"); int bgResId = getResources().getIdentifier(bgKey, "drawable", getPackageName()); if (bgResId != 0) { findViewById(android.R.id.content).setBackgroundResource(bgResId); } } } } SettingsActivity.java package com.example.myapplication; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class SettingsActivity extends AppCompatActivity { public static final int RESULT_SETTINGS_CHANGED = 1001; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); getSupportFragmentManager() .beginTransaction() .replace(R.id.settings_container, new SettingsFragment()) .commit(); } } SettingsFragment.java  package com.example.myapplication; import android.content.SharedPreferences; import android.os.Bundle; import androidx.preference.PreferenceFragmentCompat; public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.sring, rootKey); } @Override public void onResume() { super.onResume(); getPreferenceManager().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override public void onPause() { super.onPause(); getPreferenceManager().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // 设置变更通知ChatActivity刷新 if (getActivity() != null) { getActivity().setResult(SettingsActivity.RESULT_SETTINGS_CHANGED); } } } ChatActivity.java  package com.example.myapplication; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.io.*; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class ChatActivity extends AppCompatActivity { private static final String SERVER_IP = "192.168.199.1"; // 模拟器访问本机 private static final int SERVER_PORT = 8080; private RecyclerView chatRecyclerView; private EditText messageEditText; private Button sendButton; private ChatAdapter adapter; private List<ChatMessage> messages = new ArrayList<>(); private PrintWriter out; private BufferedReader in; private Socket socket; private String nickname; private SharedPreferences prefs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); // 从SharedPreferences获取昵称 prefs = PreferenceManager.getDefaultSharedPreferences(this); nickname = prefs.getString("nickname", "匿名用户"); setupUI(); loadChatHistory(); connectToServer(); } @Override protected void onResume() { super.onResume(); // 重新应用设置(从设置页面返回) applyBackgroundSettings(); if (adapter != null) { adapter.notifyDataSetChanged(); // 刷新消息显示 } } private void setupUI() { chatRecyclerView = findViewById(R.id.chat_recycler_view); messageEditText = findViewById(R.id.message_edit_text); sendButton = findViewById(R.id.send_button); adapter = new ChatAdapter(messages, this); chatRecyclerView.setAdapter(adapter); chatRecyclerView.setLayoutManager(new LinearLayoutManager(this)); applyBackgroundSettings(); // 初始应用背景设置 sendButton.setOnClickListener(v -> sendMessage()); } private void applyBackgroundSettings() { String bgKey = prefs.getString("bg", "bg_tech"); int bgResId = getResources().getIdentifier(bgKey, "drawable", getPackageName()); if (bgResId != 0) { chatRecyclerView.setBackgroundResource(bgResId); } } private void loadChatHistory() { File file = new File(getExternalFilesDir(null), "luoshutao.txt"); if (file.exists()) { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { String[] parts = line.split("\\|"); if (parts.length == 4) { boolean isSelf = Boolean.parseBoolean(parts[3]); messages.add(new ChatMessage(parts[0], parts[1], Long.parseLong(parts[2]), isSelf)); } } adapter.notifyDataSetChanged(); chatRecyclerView.scrollToPosition(messages.size() - 1); } catch (IOException e) { e.printStackTrace(); } } } private void saveChatMessage(ChatMessage message) { File file = new File(getExternalFilesDir(null), "luoshutao.txt"); try (FileWriter writer = new FileWriter(file, true)) { writer.write(String.format("%s|%s|%d|%b\n", message.getNickname(), message.getMessage(), message.getTimestamp(), message.isSelf())); } catch (IOException e) { e.printStackTrace(); } } private void connectToServer() { new Thread(() -> { try { socket = new Socket(SERVER_IP, SERVER_PORT); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out.println(nickname); // 监听服务器消息 String serverMessage; while ((serverMessage = in.readLine()) != null) { if (isSystemJoinLeaveMessage(serverMessage)) { continue; // 跳过显示 } // 解析服务器消息:格式为 "昵称|消息|间戳" String[] parts = serverMessage.split("\\|", 3); if (parts.length == 3) { final ChatMessage message = new ChatMessage( parts[0], parts[1], Long.parseLong(parts[2]), false ); runOnUiThread(() -> { messages.add(message); adapter.notifyItemInserted(messages.size() - 1); chatRecyclerView.scrollToPosition(messages.size() - 1); saveChatMessage(message); }); } } } catch (IOException e) { runOnUiThread(() -> Toast.makeText(ChatActivity.this, "服务器连接失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } }).start(); } private boolean isSystemJoinLeaveMessage(String message) { return message.contains("加入了聊天室") || message.contains("离开了聊天室"); } private void sendMessage() { String messageText = messageEditText.getText().toString().trim(); if (!messageText.isEmpty()) { long timestamp = System.currentTimeMillis(); final ChatMessage message = new ChatMessage(nickname, messageText, timestamp, true); // 添加到UI runOnUiThread(() -> { messages.add(message); adapter.notifyItemInserted(messages.size() - 1); chatRecyclerView.scrollToPosition(messages.size() - 1); saveChatMessage(message); }); // 发送服务器 new Thread(() -> { if (out != null) { out.println(nickname + "|" + messageText + "|" + timestamp); } }).start(); messageEditText.setText(""); } } @Override protected void onDestroy() { super.onDestroy(); try { if (socket != null) socket.close(); if (out != null) out.close(); if (in != null) in.close(); } catch (IOException e) { e.printStackTrace(); } } } activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:padding="16dp" android:background="@drawable/bg_tech"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="骆树涛聊天室" android:textSize="40sp" android:layout_gravity="center" android:layout_marginBottom="16dp" android:textStyle="bold" android:textColor="#2196F3"/> <!-- 昵称设置区域 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginBottom="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="昵称:" android:textSize="25sp" android:layout_marginEnd="8dp"/> <EditText android:id="@+id/nickname_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="请输入昵称" android:textSize="25sp" android:maxLines="1"/> </LinearLayout> <Button android:id="@+id/enter_chat_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="进入聊天室" android:layout_marginBottom="16dp"/> <Button android:id="@+id/settings_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="设置"/> </LinearLayout> activity_chat.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/chat_recycler_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:scrollbars="vertical"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="8dp"> <EditText android:id="@+id/message_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="输入消息" android:inputType="textMultiLine" android:maxLines="3"/> <Button android:id="@+id/send_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="发送"/> </LinearLayout> </LinearLayout> activity_settings.xml  <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/settings_container" android:layout_width="match_parent" android:layout_height="match_parent" /> message_item.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="60dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <TextView android:id="@+id/nickname" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp"/> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="10sp" android:layout_marginStart="8dp"/> </LinearLayout> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/bubble_other" android:padding="8dp"/> </LinearLayout> message_item_self.xml  <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp" android:layout_marginTop="4dp" android:layout_marginStart="60dp" android:gravity="end"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="10sp" android:layout_marginEnd="8dp"/> <TextView android:id="@+id/nickname" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp"/> </LinearLayout> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/bg_books" android:padding="8dp"/> </LinearLayout>
最新发布
06-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值