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

使用Flask+gRPC+betterproto遇socket.gaierror错误的解决方法

问题描述

我正在用gRPC构建微服务,采用betterproto替代grpcio-tools从.proto文件生成代码。当用Flask作为客户端调用gRPC服务时,触发了socket.gaierror: [Errno 8] nodename nor servname provided, or not known错误。环境为MacOS、Python 3.9.12、预发布版protobuf,相关代码文件如下:

db.proto

syntax = "proto3";

package db;

import "google/protobuf/timestamp.proto";

message Box {
  string name = 1;
  int32 id = 2;
  int32 price = 3;
  string description = 4;
  string category = 5;
  int32 quantity = 6;
  google.protobuf.Timestamp created_at = 7;
}

enum RequestStatus
{
    OK = 0;
    ERROR = 1;
}


message GetAllBoxesRequest {
}

message GetBoxesResponse {
  repeated Box box = 1;
  RequestStatus status = 2;
}

service DatabaseService {
  rpc GetBoxes(GetAllBoxesRequest) returns (GetBoxesResponse) {}
}

db/init.py

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# sources: db.proto
# plugin: python-betterproto
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, List

import betterproto
from betterproto.grpc.grpclib_server import ServiceBase
import grpclib


class RequestStatus(betterproto.Enum):
    OK = 0
    ERROR = 1


@dataclass(eq=False, repr=False)
class Box(betterproto.Message):
    name: str = betterproto.string_field(1)
    id: int = betterproto.int32_field(2)
    price: int = betterproto.int32_field(3)
    description: str = betterproto.string_field(4)
    category: str = betterproto.string_field(5)
    quantity: int = betterproto.int32_field(6)
    created_at: datetime = betterproto.message_field(7)


@dataclass(eq=False, repr=False)
class GetAllBoxesRequest(betterproto.Message):
    pass


@dataclass(eq=False, repr=False)
class GetBoxesResponse(betterproto.Message):
    box: List["Box"] = betterproto.message_field(1)
    status: "RequestStatus" = betterproto.enum_field(2)


class DatabaseServiceStub(betterproto.ServiceStub):
    
    async def get_boxes(self) -> "GetBoxesResponse":

        request = GetAllBoxesRequest()

        return await self._unary_unary(
            "/db.DatabaseService/GetBoxes", request, GetBoxesResponse
        )

    


class DatabaseServiceBase(ServiceBase):
    
    async def get_boxes(self) -> "GetBoxesResponse":
        raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)

    async def __rpc_get_boxes(self, stream: grpclib.server.Stream) -> None:
        request = await stream.recv_message()

        request_kwargs = {}

        response = await self.get_boxes(**request_kwargs)
        await stream.send_message(response)

    def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
        return {
            "/db.DatabaseService/GetBoxes": grpclib.const.Handler(
                self.__rpc_get_boxes,
                grpclib.const.Cardinality.UNARY_UNARY,
                GetAllBoxesRequest,
                GetBoxesResponse,
            ),
        }

server.py

import asyncio
import logging
from db import (
    DatabaseServiceBase,
    Box,
    RequestStatus,
    GetBoxesResponse,
)
from grpclib.server import Server
from pymongo import MongoClient

log = logging.getLogger(__name__)


def get_database():
    CONNECTION_STRING = f"mongodb://{DB_USERNAME}:{DB_USER_PASSWORD}@{DB_HOST}:{DB_PORT}"

    # Create a connection using MongoClient
    client = MongoClient(CONNECTION_STRING)

    # Create the database and return it
    return client.boxes

boxes_db = get_database()


def dict_to_box(data):
    if "_id" in data:
        data["id"] = data.pop("_id")
    return Box(**data)


class DatabaseService(DatabaseServiceBase):

    async def get_boxes(self) -> "GetBoxesResponse":
        boxes = boxes_db.boxes.find()
        list_of_boxes = [dict_to_box(box) for box in boxes]
        return GetBoxesResponse(box=list_of_boxes, status=RequestStatus.OK)


async def main():
    server = Server([DatabaseService()])
    await server.start("127.0.0.1", 50051)
    await server.wait_closed()


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

app.py(Flask客户端)

import os
import asyncio

from flask import Flask, render_template
from grpclib.client import Channel

import db

app = Flask(__name__)

SERVICE_HOST = os.getenv("SERVICE_HOST", "127.0.0.1")
SERVICE_PORT = os.getenv("SERVICE_PORT", 50051)


@app.route("/")
async def render_homepage():
    service_channel = Channel(f"{SERVICE_HOST}:{SERVICE_PORT}")
    service = db.DatabaseServiceStub(service_channel)

    get_boxes_response = await service.get_boxes() # <-- Error is throwing here

    service_channel.close()
    return render_template(
        "index.html",
        boxes=get_boxes_response.box,
    )

if __name__ == '__main__':
    app.run(debug=True)
解决方案

核心问题修复

这个错误是因为grpclib的Channel构造参数使用错误导致的。你当前把host:port拼接成单个字符串传入Channel,但grpclib要求分别传入host和port两个独立参数,而非拼接字符串。

修改app.py中的Channel初始化代码:

# 错误写法
service_channel = Channel(f"{SERVICE_HOST}:{SERVICE_PORT}")

# 正确写法
service_channel = Channel(SERVICE_HOST, SERVICE_PORT)

额外注意事项

  1. Flask异步路由兼容性:Flask 2.0+支持异步路由,但默认运行环境是同步的,建议安装flask-async扩展或确保运行环境配置正确,避免异步代码执行异常。
  2. 服务端环境变量缺失:server.py中使用的DB_USERNAME、DB_USER_PASSWORD、DB_HOST、DB_PORT未定义,需要提前通过环境变量设置,或直接硬编码测试值,否则服务启动会报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 17:54:32