在tcp通讯中,如果一次要接收大量数据,可能出现接收不完整的情况,数据被分割了,有的开发人员会通过分批读取,例如每次读取1k数据,然后进行拼接。这种方法并不保险,不好判断是否真正读取完整的次数,因为实际中可能出现当次未读满1k数据而实际上报文仍未接收完成的情况,这时根据未读满1k来判断接收完成就会出问题了。
更好的做法是服务端将数据总长度也加入报文,比如固定4个字节长度存放数据总长度,每次发送前,先将4个字节的长度数据发出,再发送正式数据。客户端同样先接收4个字节,并转换成int得到数据总长度,然后一次完整读取。
以下提供一个客户端tcp工具类:
服务端发送数据前会先发送4个字节总长度数据,按小端模式存放。
public class TcpClient {
private static TcpClient instance;
private final String host = "127.0.0.1";
private final int port = 31001;
private Socket socket;
private DataOutputStream out;
private DataInputStream dataInputStream;
private PrintWriter pw;
private String revMsg = "";
private static final String TAG = "TCP";
private static long oldPongTime = System.currentTimeMillis();
private byte[] dataBytes;
private byte[] lenbuff = new byte[4];
private HeartThread heartThread = null;
private TcpClient() {
}
public static TcpClient getInstance() {
if (instance == null) {
synchronized (TcpClient.class) {
if (instance == null) {
instance = new TcpClient();
}
}
}
return instance;
}
public void connect(){
new Thread(){
@Override
public void run() {
super.run();
try {
socket = new Socket(host, port);
out = new DataOutputStream(socket.getOutputStream());
pw = new PrintWriter(out,true);
dataInputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
sendMessage(JsonUtils.getInstance().formtGetJsonCommon("login"));
SystemClock.sleep(1000);
sendMessage(JsonUtils.getInstance().formtGetJsonCommon("ping"));
sendHeart();
int totalLen = 0;
ByteBuffer buffer;
while (true){
if(dataInputStream == null){
Thread.interrupted();
LogUtil.showLog("tcp","==soket in is null");
break;
}
dataInputStream.read(lenbuff);
buffer = ByteBuffer.wrap(lenbuff);
buffer.order(ByteOrder.LITTLE_ENDIAN);//小端
totalLen = buffer.getInt();
LogUtil.showLog("tcp","==totalLen=="+Arrays.toString(lenbuff)+"==len=="+totalLen);
if(totalLen == 0){
reconnect();
break;
}
dataBytes = new byte[totalLen];
dataInputStream.readFully(dataBytes);
revMsg = new String(dataBytes);
Arrays.fill(dataBytes, (byte) 0); //清空缓存区
if (revMsg.contains("pong")) {
oldPongTime = System.currentTimeMillis();
} else {
JsonUtils.getInstance().parseResultJson(revMsg);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
public synchronized void sendMessage(String message) {
LogUtil.showLog(TAG,"==send msg=="+message);
new Thread(){
@Override
public void run() {
super.run();
try {
pw.println(message);
// out.writeBytes(message + "\n");
// out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
public void sendHeart(){
if(heartThread != null && !heartThread.isInterrupted()){
heartThread.interrupt();
heartThread = null;
}
heartThread = new HeartThread();
heartThread.start();
}
class HeartThread extends Thread{
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(20*1000);
if(pw == null) {
LogUtil.showLog("tcp","==send heart pw is null,so break");
break;
}
pw.println(JsonUtils.getInstance().formtGetJsonCommon("ping"));
if(System.currentTimeMillis() - oldPongTime > 40*1000){
//TCP 重连
TcpClient.getInstance().reconnect();
// Thread.interrupted();
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void reconnect(){
LogUtil.showLog("tcp","===reconnect==");
if(dataInputStream != null){
try {
dataInputStream.close();
dataInputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
out = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw != null){
pw.close();
pw = null;
}
connect();
}
该工具类采用单例模式实现,包含消息长度大小端计算、定时发送心跳包、断线重连、消息发送、消息接收。
关于大小端计算方式:android 有标准方法将字节数组转成int:
ByteBuffer buffer = ByteBuffer.wrap(lenbuff);
buffer.order(ByteOrder.LITTLE_ENDIAN);//小端
int totalLen = buffer.getInt();
计算原理如下:
要将一个byte数组(例如 `[91, 1, 0, 0]`)按照小端模式(Little-endian)转换为十进制整数,需要对每个字节进行处理,然后将它们组合成一个整数。具体步骤如下:
1. 将每个字节转换为无符号整数。在此例中,byte数组已经表示为无符号整数,所以无需进行转换。
2. 将每个字节乘以对应的权重(1个字节=8bit=2的8次幂;即256的幂次方)。权重从0开始,逐渐增加。小端模式表示低位字节在前,高位字节在后。
3. 将所有结果求和,得到最终的十进制整数。
对于给定的byte数组 `[91, 1, 0, 0]`,按照小端模式,可以得到以下计算过程:
```
(91 * 256^0) + (1 * 256^1) + (0 * 256^2) + (0 * 256^3)
```
计算得到:
```
(91 * 1) + (1 * 256) + (0 * 65536) + (0 * 16777216)
```
进一步计算:
```
91 + 256 + 0 + 0
```
最终结果为:
```
347
```
所以,按照小端模式,byte数组 `[91, 1, 0, 0]` 转换为十进制整数为 `347`。