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

Flutter/.NET Core/SignalR双客户端同步页面跳转问题排查

问题描述

已正确配置后端Program.cs及相关URL信息,本地使用两个不同浏览器通过HTTP/Ws的WebSocket测试时,无法实现两个客户端同步跳转至对战页面。目前仅一个客户端能完成跳转,该跳转来自按钮点击的导航逻辑,而非SignalR的MatchFound事件触发的导航。希望通过监听MatchFound事件实现双客户端同步跳转。

MatchMakingController.cs

[HttpPost("joinQueue")]
public async Task<IActionResult> JoinQueue([FromBody] JoinQueueModel model)
{
    Console.WriteLine("1: JoinQueue called");
    int playerCharacterId = model.PlayerCharacterId;
    // Check if player is already in queue to prevent duplicates
    if (!matchmakingQueue.Contains(playerCharacterId))
    {
        matchmakingQueue.Add(playerCharacterId);
    }

    // Try to match players if there are at least 2 in the queue
    if (matchmakingQueue.Count >= 2)
    {
        Console.WriteLine("2: Matchmaking logic processing");
        int player1Id = matchmakingQueue[0];
        int player2Id = matchmakingQueue[1];
        matchmakingQueue.RemoveRange(0, 2); // Remove matched players from the queue

        var match = new Match
        {
            Player1ID = player1Id,
            Player2ID = player2Id,
            Status = "in progress"
        };

        _context.Matches.Add(match);
        await _context.SaveChangesAsync();
        // Inside your JoinQueue method, after saving the match
        await _hubContext.Clients.All.SendAsync("MatchFound", new { matchId = match.MatchID, player1Id, player2Id });

        return Ok(new { matchId = match.MatchID, player1Id, player2Id });
    }

    return Ok(new { message = "Waiting for an opponent..." });
}

前端SignalR服务代码

late HubConnection _hubConnection;
final String serverUrl = "http://localhost:5111/battleHub";
final void Function(int matchId, int player1Id, int player2Id) onMatchFound;

SignalRService({required this.onMatchFound}) {
    // Now a named parameter
    _hubConnection = HubConnectionBuilder().withUrl(serverUrl).build();
    initConnection();
}

Future<void> initConnection() async {
    if (_hubConnection.state == HubConnectionState.Disconnected) {
        try {
            await _hubConnection.start();
            print("Connection Started");
            _setupListeners();
        } catch (e) {
            print("Error starting connection: ${e.toString()}");
        }
    }
}

void _setupListeners() {
    _hubConnection.on('MatchFound', (arguments) {
        print('MatchFound event received');
        if (arguments != null && arguments.length >= 3) {
            final int matchId = arguments[0] as int;
            final int player1Id = arguments[1] as int;
            final int player2Id = arguments[2] as int;
            onMatchFound(matchId, player1Id, player2Id);
        }
    });
}

Api服务代码

Future<MatchmakingResponse?> joinMatchmakingQueue(
    int playerCharacterId) async {
    print('Joining queue with playerCharacterId: $playerCharacterId');
    final response = await http.post(
        Uri.parse('http://localhost:5111/api/matchmaking/joinQueue'),
        headers: <String, String>{
            'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode({'playerCharacterId': playerCharacterId}),
    );

    print('Response body1: ${response.body}');

    if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        // Adjusted to return a MatchmakingResponse object
        return MatchmakingResponse.fromJson(data);
    } else {
        print(
            'Failed to join matchmaking queue. Status code: ${response.statusCode}');
        return null;
    }
}

Future<MatchDetails> fetchMatchDetails(
    int matchId, int requesterCharacterId) async {
    final response = await http.get(
        Uri.parse(
            'http://localhost:5111/api/matchmaking/match/$matchId/details/$requesterCharacterId'),
        headers: <String, String>{
            'Content-Type': 'application/json; charset=UTF-8',
        },
    );

    if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return MatchDetails.fromJson(data);
    } else {
        throw Exception('Failed to load match details');
    }
}

Lobby页面(按钮所在页面)代码

@override
void initState() {
    super.initState();
    _signalRService =
        SignalRService(onMatchFound: (matchId, player1Id, player2Id) {
      _navigateToBattleScreen(matchId, player1Id, player2Id);
    });
}

@override
void dispose() {
    _signalRService.closeConnection();
    super.dispose();
}

void _fetchCharacterDetails() async {
    try {
        final fetchedCharacter = await ApiService()
            .fetchPlayerCharacterDetails(widget.playerCharacterId);
        setState(() {
            _latestCharacterDetails =
                fetchedCharacter; // Update state with the latest character details
        });
    } catch (e) {
        print("Error fetching character details: $e");
    }
}

void _navigateToBattleScreen(int matchId, int player1Id, int player2Id) {
    print('Am i trying to navigate?');
    int opponentCharacterId =
        (widget.playerCharacterId == player1Id) ? player2Id : player1Id;
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => BattlePvPScreen(
                  playerCharacter: widget.character,
                  matchId: matchId,
                  opponentCharacterId: opponentCharacterId,
                )));
}


ElevatedButton.icon(
    icon: Icon(Icons.people),
    label: Text('Battle PvP'),
    onPressed: () async {
        final matchmakingResponse = await ApiService()
            .joinMatchmakingQueue(
                widget.character.playerCharacterId);

        if (matchmakingResponse != null &&
            matchmakingResponse.matchId != null) {
            // Determine the opponent's character ID
            int opponentCharacterId =
                matchmakingResponse.player1Id ==
                        widget.character.playerCharacterId
                    ? matchmakingResponse.player2Id!
                    : matchmakingResponse.player1Id!;

            // Navigate to BattlePvPScreen
            Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => BattlePvPScreen(
                          playerCharacter: widget.character,
                          matchId: matchmakingResponse.matchId!,
                          opponentCharacterId: opponentCharacterId,
                        )),
            );
        } else {
            // Handle waiting or error
            print("Waiting for an opponent or an error occurred.");
        }
    },
)

调试输出

Response Body: {"playerCharacterId":58,"playerId":27,"characterId":1,"health":105,"damage":25,"armour":10,"level":6,"experience":10,"wins":6,"losses":0,"player":null,"character":{"characterId":1,"name":"Mage","damage":20,"health":80,"level":1,"experience":0,"armour":5}}
Connection Started
Joining queue with playerCharacterId: 58
MatchFound event received
Response body1: {"matchId":65,"player1Id":61,"player2Id":58}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:19:50