Java-解决音乐协议中的Socket通信字节传输问题

某音乐软件协议中,建立Socket长连接进行PCM音频数据的传递。

其中,TCP包的格式为:

QPlay-Protocol

其二进制数据是按照Header中定义的Length传输的,初步处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 头部信息
header = br.readLine();
System.out.println(header);
JSONObject headerObj = new JSONObject(header);
JSONObject pcmDataObj = headerObj.getJSONObject("PCMData");
int length = pcmDataObj.getInt("Length");
char[] pcmData = new char[length];
int remainLength = length;
int tmpLength = 0;
while (remainLength > 0) {
System.out.println(tmpLength);
int readLength = br.read(pcmData, tmpLength, remainLength);
if (readLength > 0) {
remainLength -= readLength;
tmpLength += readLength;
} else {
throw new IOException();
}
}

初看之下,是没有问题的,但是运行时发现,程序在语句:

int readLength = br.read(pcmData, tmpLength, remainLength);

挂起。怀疑是Socket没有发送完整的数据,设置3秒的超时时间,改进后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dataSocket.setSoTimeout(3000);
try{
int remainLength = length - lastPCMData.length();
int tmpLength = lastPCMData.length();
while (remainLength > 0) {
int readLength = br.read(pcmData, tmpLength, remainLength);
if (readLength > 0) {
remainLength -= readLength;
tmpLength += readLength;
} else {
throw new IOException();
}
}
}catch(SocketTimeoutException ex){
}

但是,随后又发现,后面的Tcp包的开头还可能包含上一个Tcp包遗留的一些二进制数据,导致Header解析失败,如下图:

QPlay-Result

其中,第一个红圈是上个包遗留的二进制数据,第二个红圈是当前TCP包获取到的二进制长度。

怀疑是,后面的Header还遗留了前面包的一些二进制数据,进行各种容错处理如下,最后,还是发现有问题。打印二进制数据,发现都是乱码,最后怀疑是编码问题。通过对二进制通过各种编码格式转字符串。参考Java-获取文件的编码

发现,字符串经过转码后,要么是空,要么还是乱码,最后怀疑是Socket的Read方面有问题,将原来的读取方式:

1
BufferedReader br = new BufferedReader(new InputStreamReader(dataSocket.getInputStream()));

替换成专门针对二进制的读取方式:

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
InputStream is = dataSocket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String header = null;
String lastPackPCMData = "";


while(true){
while(!loadPCMData)
Thread.sleep(100);

// 头部信息
boolean loadHeader = false;
while(!loadHeader){
try{
header = dis.readLine();
loadHeader = true;
lastPCMRequestObj = null;
}catch(SocketTimeoutException e){
if(null != lastPCMRequestObj){
onRequest(lastPCMRequestObj, QQMusicInterface.QQMusicTag.CMD);
Thread.sleep(1000);
}
}
}

System.out.println(header);
JSONObject headerObj = new JSONObject(header);
JSONObject pcmDataObj = headerObj.getJSONObject("PCMData");
int length = pcmDataObj.getInt("Length");
byte[] pcmData = new byte[length];
int remainLength = length;
int tmpLength = 0;

while (remainLength > 0) {
int readLength = dis.read(pcmData, tmpLength, remainLength);
if (readLength > 0) {
remainLength -= readLength;
tmpLength += readLength;
} else {
break;
}
}

loadPCMData = false;
onResponse(headerObj, pcmData, QQMusicInterface.QQMusicTag.DATA);
}

完美读取,一开始TCP包二进制数据长度不对,后面包的Header出现乱码,都是因为使用BufferedReader以及InputStreamReader导致的,这两者应该是内部对二进制数据进行了Java的默认编码进行转码,而一旦经过转码后,就无法转回原来的正确的二进制格式了。