Android与ESP8266 Socket通信单向失败问题求助
Android与ESP8266 Socket通信单向失败问题排查与解决
我搭了个Android和ESP8266的简易Socket通信原型,但只有一半能用——Android发消息到ESP8266完全正常,反过来ESP8266发消息到Android就直接失败了。Android端报java.io.EOFException,ESP8266那边还会触发超时。下面是相关代码片段,需要的话我可以补全完整代码:
Android端代码片段
while (true) { socket = serverSocket.accept(); dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream = new DataOutputStream(socket.getOutputStream()); String msgReply = "Android server is alive!"; dataOutputStream.writeUTF(msgReply); dataOutputStream.flush(); message = dataInputStream.readUTF(); MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { msg.setText(message); } }); }
ESP8266端代码片段
String cmd = "Hello from NodeMCU"; Serial.print("send to server: "); Serial.println(cmd); client.print(cmd); client.flush(); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println(">>>> Client Timeout !"); client.stop(); return; } } while(client.available()){ String line = client.readStringUntil('!'); Serial.print(line); }
我试了各种方法,比如改成client.write('h')发送单个字符,但都没用,恳请大佬指明可行的解决方向!
核心问题:两端数据读写格式不匹配
你的问题根源其实很明确——Android和ESP8266用了完全不兼容的消息编码/读取逻辑:
- Android这边用的是
DataOutputStream.writeUTF()和DataInputStream.readUTF(),这俩是Java的成对API:writeUTF()会先写一个2字节的长度前缀(标记后面字符串的字节数),再写实际的字符串内容;readUTF()必须先读到这个长度前缀,再按长度读取对应内容,否则直接抛出EOFException。 - 但ESP8266这边用
client.print(cmd)直接发普通字符串,根本没有加那个2字节的长度前缀,Android的readUTF()找不到预期的前缀,自然就报错了;反过来,ESP8266在等Android的回复,但Android发的是带长度前缀的writeUTF()内容,ESP8266用readStringUntil('!')去读,根本读不到正确的结束标记,最后超时退出。
可行的解决方向(二选一即可)
方向1:统一用Java标准的UTF格式(推荐,兼容性更好)
需要修改ESP8266的代码,模拟Java的writeUTF()和readUTF()逻辑:
先写个工具函数实现
writeUTF的行为:void writeUTF(WiFiClient &client, const String &str) { uint16_t strLen = str.length(); // 先写长度的高字节,再写低字节(大端序,和Java一致) client.write((strLen >> 8) & 0xFF); client.write(strLen & 0xFF); // 再发送字符串内容 client.print(str); client.flush(); }把原来的
client.print(cmd);改成writeUTF(client, cmd);就行。再写个
readUTF函数读取Android的回复,代替原来的readStringUntil('!'):String readUTF(WiFiClient &client) { // 先检查有没有足够的字节读长度前缀 if (client.available() < 2) return ""; // 读取2字节的长度(大端序) uint16_t strLen = (client.read() << 8) | client.read(); String result; // 按长度读取字符串内容 while (strLen-- > 0 && client.available()) { result += (char)client.read(); } return result; }然后把读取部分改成
String reply = readUTF(client); Serial.print(reply);。
Android端的代码不需要改,保持原来的writeUTF()和readUTF()就行。
方向2:统一用普通字符串+分隔符的方式
如果不想搞复杂的长度前缀,可以两端都用普通字符串,加个固定分隔符(比如换行符\n)来标记消息结束:
- Android端修改代码:
把DataInputStream/DataOutputStream换成普通的字符流读写:while (true) { socket = serverSocket.accept(); // 替换成字符流 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String msgReply = "Android server is alive!"; writer.write(msgReply + "\n"); // 加换行符当分隔符 writer.flush(); String message = reader.readLine(); // 按换行符读取完整消息 MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { msg.setText(message); } }); } - ESP8266端修改代码:
发送消息时加上换行符,读取时按换行符匹配:String cmd = "Hello from NodeMCU\n"; // 加换行符 Serial.print("send to server: "); Serial.println(cmd); client.print(cmd); client.flush(); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println(">>>> Client Timeout !"); client.stop(); return; } } // 按换行符读取回复 String line = client.readStringUntil('\n'); Serial.print(line);
额外提醒
- 确保两端的Socket是长连接,不要发完一次消息就关闭,除非你的业务逻辑就是单次通信的短连接。
- ESP8266的超时逻辑可以保留,但要注意读取到数据后及时退出循环,避免不必要的等待。
内容的提问来源于stack exchange,提问作者Ariam1_catalist_number_666
相关产品推荐
相关产品推荐

