如何通过Socket向DNS服务器发起请求?解决请求无响应问题
正确向DNS服务器发起Socket请求的方法
你的代码存在几个关键错误,导致无法和DNS服务器正常通信:
- 协议类型错误:DNS默认使用UDP协议(53端口),你使用的是TCP Socket,虽然DNS也支持TCP,但普通查询用UDP更合适,且你的代码逻辑完全不符合TCP DNS的交互流程。
- 连接逻辑混乱:你创建Socket时指定了Google DNS的地址和53端口,但随后又调用
connect连接到www.youtube.com:80,这会覆盖之前的目标,完全偏离了DNS查询的方向。 - 请求格式错误:DNS查询需要发送符合DNS协议规范的二进制报文,而不是直接发送
"www.youtube.com"字符串,服务器无法识别这种文本请求。 - 响应读取方式错误:DNS响应是二进制数据,用
InputStreamReader和BufferedReader按文本行读取会破坏数据结构,无法解析有效内容。
正确实现步骤
1. 使用UDP DatagramSocket
DNS默认使用UDP协议,因此需要用DatagramSocket来发送和接收数据包。
2. 构造符合DNS协议的请求报文
DNS请求报文核心结构(简化版):
- 2字节随机ID:用于匹配请求与响应
- 2字节标志位:设置为标准查询标志(
0x0100) - 2字节问题数:设为1
- 问题部分:域名采用长度+标签的编码方式(比如
www.youtube.com编码为0x03 0x77 0x77 0x77 0x07 0x79 0x6F 0x75 0x74 0x75 0x62 0x65 0x03 0x63 0x6F 0x6D 0x00),加上查询类型(A记录,0x0001)和查询类(IN类,0x0001)。
3. 发送请求并解析响应
将构造好的二进制报文封装为DatagramPacket发送到DNS服务器,接收响应后解析二进制数据提取IP。
示例代码
Network network = ...; String dnsServer = "8.8.8.8"; int dnsPort = 53; String domain = "www.youtube.com"; try { // 创建UDP Socket并设置超时 DatagramSocket socket = network.getSocketFactory().createDatagramSocket(); socket.setSoTimeout(3000); // 构造DNS请求报文 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 生成随机ID Random random = new Random(); int id = random.nextInt(65536); baos.write(id >> 8); baos.write(id & 0xFF); // 标志位:标准查询 baos.write(0x01); baos.write(0x00); // 问题数:1,其余计数为0 baos.write(0x00); baos.write(0x01); baos.write(0x00); baos.write(0x00); baos.write(0x00); baos.write(0x00); baos.write(0x00); baos.write(0x00); // 编码域名 String[] domainParts = domain.split("\\."); for (String part : domainParts) { baos.write(part.length()); baos.write(part.getBytes(StandardCharsets.UTF_8)); } baos.write(0x00); // 域名结束标记 // 查询类型(A记录)和查询类(IN类) baos.write(0x00); baos.write(0x01); baos.write(0x00); baos.write(0x01); byte[] requestData = baos.toByteArray(); // 发送请求数据包 InetAddress dnsAddr = InetAddress.getByName(dnsServer); DatagramPacket requestPacket = new DatagramPacket( requestData, requestData.length, dnsAddr, dnsPort); socket.send(requestPacket); // 接收响应数据包 byte[] responseBuffer = new byte[512]; DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length); socket.receive(responsePacket); // 解析响应,提取A记录IP byte[] responseData = responsePacket.getData(); int offset = 12; // 跳过ID、标志位、计数字段 // 跳过域名部分 while (responseData[offset] != 0x00) { offset += responseData[offset] + 1; } offset += 5; // 跳过0x00、查询类型、查询类 // 验证是否为A记录 int type = ((responseData[offset] & 0xFF) << 8) | (responseData[offset+1] & 0xFF); int dataLen = ((responseData[offset+8] & 0xFF) << 8) | (responseData[offset+9] & 0xFF); offset += 10; if (type == 0x0001 && dataLen == 4) { String ip = String.format("%d.%d.%d.%d", responseData[offset] & 0xFF, responseData[offset+1] & 0xFF, responseData[offset+2] & 0xFF, responseData[offset+3] & 0xFF); Log.e("DNS_RESULT", "www.youtube.com的IP:" + ip); } socket.close(); } catch (IOException e) { String message = context.getText(R.string.nointernet) + ": " + e.getLocalizedMessage(); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); context.getVariables().setNetwork(null); }
补充说明
- 若需使用TCP查询DNS(比如响应超过512字节),需先发送2字节的请求长度,再发送请求报文,读取时先读取响应长度再读取完整数据,逻辑比UDP复杂。
- 上述示例仅实现基础A记录查询,如需支持更多记录类型,可参考DNS协议规范(RFC 1035)扩展解析逻辑。
内容的提问来源于stack exchange,提问作者Bret Joseph
相关产品推荐
相关产品推荐

