SocketException: 主机查找失败:‘...com’ (操作系统错误:未提供节点名或服务名,或不可知的错误,errno = 8)

16

我们遇到了生产应用程序面临以下套接字异常的情况,此后无法执行任何其他网络操作。

DioError [DioErrorType.DEFAULT]: SocketException: Failed host lookup: ‘xyz.abc.com’ (OS Error: nodename nor servname provided, or not known, errno = 8)

注意:遇到一个使用iPhone X,iOS 14.4的用户反复出现。
我们正在使用Dio作为网络客户端,与Retrofit一起使用,它在内部使用来自dart的HttpClient。使用Dio无法在模拟环境中重现异常,但是直接使用HttpClient时,在iOS模拟器中可以使用以下代码重现相同的异常。
HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);
      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
    }
  }

一旦抛出异常,HttpClient 就无法从那种陈旧的状态中恢复,所有其他 API 请求都会以相同的错误失败。

enter image description here

我们通过强制关闭所有先前的连接并打开一个新的HttpClient来恢复那个陈旧状态。

  HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);

      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
      userAgent.close(force: true);
      print('Force closing previous connections');
      userAgent = HttpClient();
      print('Creating new HttpClient instance');
    }
  }

enter image description here

一个有趣的事实是,每236个请求后都会引发异常。这可能是由于文件描述符过度使用,但iOS有256的限制。
在稳定的互联网连接下,在iOS模拟器中每次都可以重现此问题。
虽然我无法通过Dio客户端重现此问题,但在生产中它确实发生了。因此,我正在寻求帮助来了解此问题的根本原因,以及如何预防它?
任何遇到这种情况并且知道如何克服它的人,请帮忙一下。
提前感谢您。

请确保您的 互联网连接 已经 打开 - Shubham Narkhede
3
@ShubhamNarkhede 我的网络连接稳定。 - Tapas Pal
好的,请添加您的Android清单文件。 - Shubham Narkhede
1
我正在运行在 iOS 模拟器上。你需要从那里获取什么吗? - Tapas Pal
@novas1r1,问题出在基础设施设置上,据我记得他们做了一些代理设置。这不是我们的Flutter代码库引起的。 - Tapas Pal
显示剩余5条评论
4个回答

1

这是一个奇怪的错误。

这可能不是你问题的答案,但可以帮助我们找出问题所在。

代码片段(从问题中复制)每次调用.getUrl()将打开一个新的stream不会关闭它们。(我假设这是有意为之,以创建套接字异常?)

HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);
      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
    }
  }

在某个时刻,会达到流(打开的流)的限制。我猜测在你的情况下这个数字是236。
因此,在那个时候,你才看到“nodename or servname provided”异常?
(顺便说一句,我认为该错误来自底层主机操作系统的DNS服务,尽管我不确定它是由于请求垃圾邮件、打开连接的数量等原因引起的。这可能与信息无关。)
因此,如果您以典型方式使用HttpClient,发出请求并关闭那些打开的流,例如:
      var request = await userAgent.getUrl(Uri.parse('http://example.com/'));
      var response = await request.close(); // ← close the stream
      var body = await response.transform(utf8.decoder).join();
      // ↑ convert results to text
      // rinse, repeat... 

你是否仍然看到相同的“nodename or servname provided”错误弹出?
使用上述“典型用法”代码,可以重复使用userAgent,直到调用userAgent.close()(并且HttpClient被永久关闭)。尝试再次使用会抛出Bad State异常。
我很想听听改动后的代码是否仍然出现nodename错误。

关于问题中的第二个代码片段。

在捕获块中,HttpClient被关闭,然后创建了一个新的HttpClient。这有效地关闭了在try块中打开的所有流(我假设,重置了打开流的限制)。

如果您将第二个代码示例调整为使用:

      var req = await userAgent.getUrl(Uri.parse('https://www.google.com'));
      userAgent.close(force: true);
      userAgent = HttpClient();
      print('Number of api executed');

你能无限期地运行它吗?


即使在请求关闭后,我们仍然收到相同的异常。 - Tapas Pal
while (run) 块... 它是同时创建一堆连接还是等待每个请求/响应完成后再执行下一个循环? - Baker
那个测试是否能很好地代表客户端应用程序的实际情况?如果该循环被处理得足够快,也许仍然会达到DNS请求或类似问题的限制。我猜想在生产应用程序中出现了原始错误,因为连接从未被关闭。如果在每个循环中放置两秒的延迟(在“while(run)”测试中,连接正在被关闭),它能一直运行吗? - Baker
刚刚测试了一下,在 while 循环中加入了短暂的延迟(2 秒)。进行了 300 次迭代,没有出现任何问题。我认为这对于无限次迭代来说是可行的。 - Baker
发现一个事实,使用相同的代码库和Dart HttpClient以及Dio运行互联网工具。对于httpClient,它会为每个调用打开连接,因此在达到限制后会抛出套接字异常。另一方面,Dio聪明地处理连接并仅重复使用3-4个连接。这就是为什么我们无法使用Dio重现相同异常的原因。 - Tapas Pal
从这个观察中,我们可以省略打开多个连接的可能性之一。 - Tapas Pal

1

我遇到了相同的问题,并用以下代码进行了解决:

示例

//Add This Class
    class MyHttpOverrides extends HttpOverrides{
      @override
      HttpClient createHttpClient(SecurityContext? context){
        return super.createHttpClient(context)
          ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
      }
    }
    
    Future<void> main() async {
      HttpOverrides.global = MyHttpOverrides();      //call here
      runApp(const MyApp());
    }

1
尝试过了,没有解决问题。仍然会收到数百个“SocketException: Failed host lookup”错误。 - Pierre

1

1:获取文件描述符的当前限制

ulimit -n 示例输出:“256”或“10032”。

提示:在MacOS上,可以指定的最大数字是12288。

获取进程的当前限制

ulimit -u 示例输出:“1418”。

sudo launchctl limit maxfiles 65536 200000


1
Mac OS计算机有256个请求限制。如果您运行上述代码,它很可能会起作用。如果它起作用了。 - Bahadır KALAY
这个可行。我花了好几天的时间在这上面。谢谢!在调试模式下,ulimit设置为200k左右,在发布模式下,ulimit设置为256,因此更多的连接没有通过。 - novas1r1

0

我在生产环境中也遇到了完全相同的错误,而且是间歇性的。就像Baker所说的那样,关闭连接:

import 'package:http/http.dart' as http;

Future<http.Response> get(String url) async {
    var httpClient = http.Client() as http.BaseClient;
    
    Map<String, String> headers = {};
    headers['Content-Type'] = 'application/json; charset=UTF-8';

    var result = await httpClient
        .get(Uri.parse(url), headers: headers)
        .timeout(
            const Duration(seconds: 60),
            onTimeout: () => http.Response('Request Timeout', 408),
        );
    
    httpClient.close();
    
    return result;
}

我做了10个Future.Delayeds,每个都同时进行了一个循环,其中包含300个get请求,没有发现任何问题。

实现Future Delayeds的方法如下:

Future.delayed(const Duration(milliseconds: 10), () async {
  for (var i = 0; i < 300; i++) {
    var pingResult = await Api.instance.ping();
    print('Delayed 1 Result (${i}): ${pingResult.success}');
  }
});

Future.delayed(const Duration(milliseconds: 10), () async {
  for (var i = 0; i < 300; i++) {
    var pingResult = await Api.instance.ping();
    print('Delayed 2 Result (${i}): ${pingResult.success}');
  }
});

//..

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接