You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Dart Shelf容器访问MySQL Docker容器时遇SocketException问题

背景

我正在使用Shelf库构建一个Dart服务器,希望它具备一定的可扩展性,以便项目就绪后能投入生产环境。为了简化生产部署,我决定使用Docker。


Dart Shelf服务器

以下是我的Dockerfile:

# Use latest stable channel SDK.
FROM dart:stable AS build

# Resolve app dependencies.
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

# Copy app source code (except anything in .dockerignore) and AOT compile app.
COPY . .
RUN dart compile exe bin/server.dart -o bin/server

# Build minimal serving image from AOT-compiled `/server`
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/bin/

# Start server.
EXPOSE 8080
CMD ["/app/bin/server"]

(这是Shelf官方文档提供的基础Dockerfile)


带数据库的Docker-compose文件

我需要一个数据库,将其放在独立容器中。以下是我的docker-compose.yaml文件:

version: "3.7"
services:
  db:
    restart: always
    image: mysql
    environment:
      MYSQL_USER: admin
      MYSQL_PASS: secret
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_PASSWORD: secret
      MYSQL_DATABASE: mydatabase
    # volumes:
    #   - toast-datavolume:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - mynetwork

  server:
    build: .
    ports:
      - "8000:8080"
    depends_on:
      - db
    environment:
      - MYSQL_HOST=db
    links:
      - db:mysql
    networks:
      - mynetwork

networks:
  mynetwork:
# volumes:
#   toast-datavolume:

(我注释了持久化部分,先确保功能正常)


Dart代码

以下是负责数据库连接的Dart代码:

try {
  print('try');
  var settings = ConnectionSettings(
    host: 'db',
    port: 3306,
    user: 'admin',
    password: 'secret',
    db: 'mydatabase',
    timeout: Duration(seconds: 30),
  );
  var conn = await MySqlConnection.connect(settings);
  var result = await conn.query('insert into users (name, email, age) values (?, ?, ?)', ['Bob', 'bob@bob.com', 25]);

  return 1;
} catch (exception) {
  print(exception);
  return -1;
}

错误信息

启动容器后一切正常,但触发这段代码时(通过curl请求),服务器容器日志中出现如下异常:

2023-06-07 11:45:14 2023-06-07T09:45:14.465441  0:00:00.011712 POST    [400] /register
2023-06-07 11:46:42 try
2023-06-07 11:46:42 SocketException: Broken pipe (OS Error: Broken pipe, errno = 32), address = db, port = 33206

POST行是我的curl请求,第二行是print('try')的输出。

我已尝试的解决方法:

  • 移除docker-compose.yaml中的network配置,使用默认网络
  • 在Dart代码中将host设为mysql://db:3306
  • 从本地机器登录MySQL容器(正常工作,说明问题出在Shelf服务器与MySQL容器的连接)
  • 还尝试了其他很多方法,但已记不清,为此花费了2天时间调试

恳请各位帮忙解决!


解决方案

1. 修复MySQL用户权限问题

默认MySQL创建的admin用户仅允许localhost连接,而Dart服务器在独立容器中属于远程连接,需要授予跨容器访问权限:

  • 进入MySQL容器:
docker exec -it <你的db容器名称> mysql -uroot -psecret
  • 执行授权SQL:
GRANT ALL PRIVILEGES ON mydatabase.* TO 'admin'@'%' IDENTIFIED BY 'secret';
FLUSH PRIVILEGES;

(若要更安全,可将%替换为服务器容器所在的子网IP段,比如172.20.0.%)

2. 处理服务启动时序问题

depends_on仅保证容器启动顺序,不确保MySQL服务已完成初始化。当Dart服务器尝试连接时,MySQL可能还在加载,导致连接中断。可以通过两种方式解决:

方法A:在代码中添加重试逻辑

修改数据库连接代码,增加重试机制:

Future<int> connectToDb() async {
  int retries = 5;
  while (retries > 0) {
    try {
      print('尝试连接数据库...');
      var settings = ConnectionSettings(
        host: 'db',
        port: 3306,
        user: 'admin',
        password: 'secret',
        db: 'mydatabase',
        timeout: Duration(seconds: 10),
      );
      var conn = await MySqlConnection.connect(settings);
      var result = await conn.query('insert into users (name, email, age) values (?, ?, ?)', ['Bob', 'bob@bob.com', 25]);
      await conn.close();
      return 1;
    } catch (exception) {
      print('连接失败,重试剩余次数:${retries-1}');
      print(exception);
      retries--;
      await Future.delayed(Duration(seconds: 2));
    }
  }
  return -1;
}

方法B:使用等待脚本确保MySQL就绪

暂时放弃scratch镜像,改用dart:stable作为运行镜像,并安装wait-for-it脚本等待MySQL服务启动完成:
修改Dockerfile:

# Use latest stable channel SDK.
FROM dart:stable AS build

# Resolve app dependencies.
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

# Copy app source code (except anything in .dockerignore) and AOT compile app.
COPY . .
RUN dart compile exe bin/server.dart -o bin/server

# 使用dart:stable作为运行镜像
FROM dart:stable
WORKDIR /app
COPY --from=build /app/bin/server /app/bin/

# 安装wait-for-it脚本
RUN apt-get update && apt-get install -y wait-for-it

# 等待MySQL服务就绪后启动服务器
EXPOSE 8080
CMD ["wait-for-it", "db:3306", "--timeout=30", "--", "/app/bin/server"]

3. 清理无效环境变量

docker-compose.yaml中的MYSQL_PASS是无效的,官方MySQL镜像仅识别MYSQL_PASSWORD,删除该冗余变量避免混淆。

4. 排查scratch镜像的潜在问题

scratch是极简空镜像,虽然Dart AOT编译为静态链接,但部分网络底层依赖可能缺失。如果上述步骤解决问题后,再尝试换回scratch镜像,若问题复发,说明需要补充必要系统文件,或改用轻量镜像如alpine配合Dart运行时。


内容的提问来源于stack exchange,提问作者Mathieu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:07:05