我在从netcat或我的客户端向收听广播UDP数据包的UDP服务器发送UDP数据包时遇到了一些问题。问题是我无法重新启动socket.receive(packet);
的缓冲区。当您检查我的控制台输出时,您将看到数据包被发送或接收两次甚至更多次并且最烦人的一切都是当我发送一个长度较大的数据包时,下一个较小的数据包是前一个的一部分! (控制台输出上的HERE标记了问题)我的客户端和服务器位于同一LAN上。
客户代码:
DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true);
byte[] buf = ("Hello from Client").getBytes();
byte[] buf2 = ("omg").getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, getBroadcastAddress(UDPConnection.context), Server.SERVERPORT);
DatagramPacket packet2 = new DatagramPacket(buf2, buf2.length, getBroadcastAddress(UDPConnection.context), Server.SERVERPORT);
Log.d("UDP", "C: Sending: '" + new String(buf) + "'");
socket.send(packet);
socket.send(packet2);
服务器代码:
void run(){
MulticastSocket socket = new MulticastSocket(SERVERPORT);
socket.setBroadcast(true);
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while(true){
Log.d("UDP", "S: Receiving...");
socket.receive(packet);
//socket.setReceiveBufferSize(buf.length);
packet.setData(buf);
Log.i("BUFFER_packet",packet.getLength()+"");
Log.i("BUFFER_socket",socket.getReceiveBufferSize()+"");
Log.d("UDP", "S: From: " + packet.getAddress().getHostAddress());
Log.d("UDP", "S: Received: "+getRidOfAnnoyingChar(packet));
Log.d("UDP", "S: Done.");
}
}
//this method is getting rid of the "questionmark in a black diamond" character
public String getRidOfAnnoyingChar(DatagramPacket packet){
Log.i("UDP","Inside getridofannoyingchar method.");
String result = new String(packet.getData());
char[] annoyingchar = new char[1];
char[] charresult = result.toCharArray();
result = "";
for(int i=0;i<charresult.length;i++){
if(charresult[i]==annoyingchar[0]){
break;
}
result+=charresult[i];
}
return result;
}
控制台:
11-27 18:15:27.515: D/UDP(15242): S: Connecting...
11-27 18:15:27.519: I/ServerIP(15242): ::
11-27 18:15:27.519: I/LocalIP(15242): 192.168.0.4
11-27 18:15:27.523: D/UDP(15242): S: Receiving...
11-27 18:15:28.031: D/UDP(15242): C: Connecting...
11-27 18:15:28.039: I/BroadcastIP(15242): 192.168.0.255
11-27 18:15:28.042: I/BroadcastIP(15242): 192.168.0.255
11-27 18:15:28.070: D/UDP(15242): C: Sending: 'Hello from Client'
11-27 18:15:28.074: I/BUFFER_packet(15242): 1024
11-27 18:15:28.074: I/BUFFER_socket(15242): 110592
11-27 18:15:28.074: D/UDP(15242): S: From: 192.168.0.4
11-27 18:15:28.074: I/UDP(15242): Inside getridofannoyingchar method.
11-27 18:15:28.078: I/BUFFER_packet(15242): 1024
11-27 18:15:28.078: I/BUFFER_socket(15242): 110592
11-27 18:15:28.078: D/UDP(15242): S: From: 192.168.0.4
11-27 18:15:28.078: I/UDP(15242): Inside getridofannoyingchar method.
11-27 18:15:28.085: D/UDP(15242): S: Received: Hello from Client <------------HERE
11-27 18:15:28.085: D/UDP(15242): S: Done.
11-27 18:15:28.085: D/UDP(15242): S: Receiving...
11-27 18:15:28.085: D/UDP(15242): S: Received: Hello from Client <------------HERE
11-27 18:15:28.085: D/UDP(15242): S: Done.
11-27 18:15:28.085: D/UDP(15242): S: Receiving...
11-27 18:15:28.085: I/BUFFER_packet(15242): 1024
11-27 18:15:28.085: I/BUFFER_socket(15242): 110592
11-27 18:15:28.085: D/UDP(15242): S: From: 192.168.0.4
11-27 18:15:28.085: I/UDP(15242): Inside getridofannoyingchar method.
11-27 18:15:28.089: D/UDP(15242): S: Received: omglo from Client <------------HERE
11-27 18:15:28.089: D/UDP(15242): S: Done.
11-27 18:15:28.089: D/UDP(15242): S: Receiving...
11-27 18:15:28.089: I/BUFFER_packet(15242): 1024
11-27 18:15:28.089: I/BUFFER_socket(15242): 110592
11-27 18:15:28.089: D/UDP(15242): S: From: 192.168.0.4
11-27 18:15:28.089: I/UDP(15242): Inside getridofannoyingchar method.
11-27 18:15:28.089: D/UDP(15242): S: Received: omglo from Client <------------HERE
11-27 18:15:28.089: D/UDP(15242): S: Done.
11-27 18:15:28.089: D/UDP(15242): S: Receiving...
11-27 18:15:28.089: D/UDP(15242): C: Sent.
11-27 18:15:28.089: D/UDP(15242): C: Done.
任何帮助将受到高度赞赏! :)
PS。控制台中可能有一些输出,如完成/发送/连接/接收,但未添加到我的示例代码中,但所有已接收:/ BUFFER_packet / _socket / From都存在。
答案 0 :(得分:5)
你不需要重新初始化数据包中的缓冲区,你只需要将缓冲区的内容重置为最初的内容(即你需要用零填充接收数组)。
致电:
Arrays.fill(BUF,(字节)0);
服务器端的会将数组重置为零,因为Java中的数组是按引用传递而不是按值传递(即,您对数组内容的引用与DatagramPacket具有的引用相同,所以你可以修改它而无需通过DatagramPacket方法。)
已经说过你对数据进行序列化/反序列化的方式并不理想。最好使用ByteArrayOutputStream和ByteArrayInputStream包裹发送和接收缓冲区,然后使用DataOutputStream / DataInputStream。这些将允许您以明确定义的格式写入和读取字符串,这可能存储字符串的长度,以便无论如何都会忽略缓冲区上的任何剩余数据。以这种方式正确地序列化/反序列化也将消除剥去“黑钻石”角色的需要。
如果你对这背后的原因感兴趣,那就是使用java.lang.String的默认序列化(getBytes()和new String(byte []))以及如何填充UDP数据包。我会尝试把它归结为关键点:
Java的String对象的内部表示不是字节数组 - 它是一个char数组。 Java字符与字节不同 - 一个字符串实际上是2个字节,因为它需要能够表示的不仅仅是拉丁字母表(acbd ...),它需要支持来自其他语言/文化的其他字符。西里尔文,汉字等和一个字节是不够的(一个字节可以获得256种可能性,2个字节可以获得65536种可能性)。
因此,当您调用getBytes()时,Java必须使用一些“scheme”(编码)将该字符数组转换为字节数组(序列化)。其中的细节并不重要,但是当你发送第一个字节块时(比如说它的10个字节长)你将数据包读入更大的缓冲区(1024字节)。然后,您要求Java String对整个缓冲区进行反序列化,而不仅仅是10个字节。
方案(编码)不知道只处理前10个字节,所以它试图解码整个1024字节,然后你的字符串就像黑色钻石那样得到奇怪的字符,或者(你在哪里)通过发送'hello'将10个字节后面的一些其他数据放入)你将之前接收的字符混合到你的字符串中。
使用write / readUTF将写入字节数组的长度以及数据,因此当您再次读取它时,它将知道它只需要读取前10个字符(或者许多是有效的)。