การใช้งาน RPC (Remote Procedure Call) ด้วย Java พร้อมตัวอย่างเกมออนไลน์ (ต่อ)

เพื่อให้สามารถอัปเดตสถานะของผู้เล่นคนอื่นในเกมออนไลน์ผ่าน RPC ได้ คุณสามารถใช้ฟีเจอร์ Server Streaming RPC ของ gRPC เพื่อให้เซิร์ฟเวอร์ส่งข้อมูลสถานะของผู้เล่นแบบเรียลไทม์ไปยังไคลเอนต์ที่กำลังเชื่อมต่ออยู่ ตัวอย่างด้านล่างแสดงวิธีการเพิ่มฟีเจอร์นี้ในเกม:


อัปเดตไฟล์ game.proto

เพิ่มเมธอดสำหรับการสตรีมสถานะของผู้เล่น:

syntax = "proto3";

service GameService {
    rpc JoinGame (JoinRequest) returns (JoinResponse);
    rpc SendMove (MoveRequest) returns (MoveResponse);
    rpc StreamPlayerUpdates (PlayerUpdateRequest) returns (stream PlayerUpdateResponse);
}

message JoinRequest {
    string playerName = 1;
}

message JoinResponse {
    string welcomeMessage = 1;
}

message MoveRequest {
    string playerName = 1;
    string move = 2;
}

message MoveResponse {
    string result = 1;
}

message PlayerUpdateRequest {
    string playerName = 1; // ชื่อผู้เล่นที่ต้องการรับการอัปเดต
}

message PlayerUpdateResponse {
    string playerName = 1;
    string status = 2; // เช่น "moved UP", "joined the game", "left the game"
}

การอัปเดตเซิร์ฟเวอร์ (GameServer)

เพิ่มการจัดการสำหรับการส่งสถานะของผู้เล่นไปยังไคลเอนต์:

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class GameServer {
    private static final Map<String, String> playerStates = new HashMap<>();

    public static void main(String[] args) throws Exception {
        Server server = ServerBuilder.forPort(8080)
            .addService(new GameServiceImpl())
            .build();

        System.out.println("Server is starting...");
        server.start();
        server.awaitTermination();
    }

    static class GameServiceImpl extends GameServiceGrpc.GameServiceImplBase {
        private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        @Override
        public void joinGame(JoinRequest request, StreamObserver<JoinResponse> responseObserver) {
            String playerName = request.getPlayerName();
            playerStates.put(playerName, "joined the game");

            String welcomeMessage = "Welcome, " + playerName + "!";
            JoinResponse response = JoinResponse.newBuilder()
                .setWelcomeMessage(welcomeMessage)
                .build();

            responseObserver.onNext(response);
            responseObserver.onCompleted();

            System.out.println(playerName + " joined the game.");
        }

        @Override
        public void sendMove(MoveRequest request, StreamObserver<MoveResponse> responseObserver) {
            String playerName = request.getPlayerName();
            String move = request.getMove();
            playerStates.put(playerName, "moved " + move);

            MoveResponse response = MoveResponse.newBuilder()
                .setResult("Player " + playerName + " moved " + move)
                .build();

            responseObserver.onNext(response);
            responseObserver.onCompleted();

            System.out.println(playerName + " moved " + move);
        }

        @Override
        public void streamPlayerUpdates(PlayerUpdateRequest request, StreamObserver<PlayerUpdateResponse> responseObserver) {
            String subscribingPlayer = request.getPlayerName();

            scheduler.scheduleAtFixedRate(() -> {
                for (Map.Entry<String, String> entry : playerStates.entrySet()) {
                    if (!entry.getKey().equals(subscribingPlayer)) {
                        PlayerUpdateResponse update = PlayerUpdateResponse.newBuilder()
                            .setPlayerName(entry.getKey())
                            .setStatus(entry.getValue())
                            .build();

                        responseObserver.onNext(update);
                    }
                }
            }, 0, 1, TimeUnit.SECONDS);
        }
    }
}

การอัปเดตไคลเอนต์ (GameClient)

เพิ่มฟังก์ชันสำหรับการรับข้อมูลสถานะผู้เล่นแบบเรียลไทม์:

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

public class GameClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
            .usePlaintext()
            .build();

        GameServiceGrpc.GameServiceBlockingStub blockingStub = GameServiceGrpc.newBlockingStub(channel);
        GameServiceGrpc.GameServiceStub asyncStub = GameServiceGrpc.newStub(channel);

        // Join the game
        JoinRequest joinRequest = JoinRequest.newBuilder()
            .setPlayerName("Alice")
            .build();

        JoinResponse joinResponse = blockingStub.joinGame(joinRequest);
        System.out.println(joinResponse.getWelcomeMessage());

        // Start listening to player updates
        PlayerUpdateRequest updateRequest = PlayerUpdateRequest.newBuilder()
            .setPlayerName("Alice")
            .build();

        asyncStub.streamPlayerUpdates(updateRequest, new StreamObserver<PlayerUpdateResponse>() {
            @Override
            public void onNext(PlayerUpdateResponse response) {
                System.out.println("Update: " + response.getPlayerName() + " " + response.getStatus());
            }

            @Override
            public void onError(Throwable t) {
                System.err.println("Error receiving updates: " + t.getMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("Player updates completed.");
            }
        });

        // Simulate player move
        MoveRequest moveRequest = MoveRequest.newBuilder()
            .setPlayerName("Alice")
            .setMove("UP")
            .build();

        MoveResponse moveResponse = blockingStub.sendMove(moveRequest);
        System.out.println(moveResponse.getResult());

        // Keep client alive to receive updates
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        channel.shutdown();
    }
}

การทดสอบ

  1. รันเซิร์ฟเวอร์ (GameServer)

    • เซิร์ฟเวอร์จะเริ่มทำงานและพร้อมให้ไคลเอนต์เชื่อมต่อ.
  2. รันไคลเอนต์ (GameClient)

    • ผู้เล่น Alice จะเข้าร่วมเกมและสามารถส่งคำสั่งการเคลื่อนไหว.
    • ไคลเอนต์จะรับข้อมูลสถานะของผู้เล่นคนอื่น เช่น การเคลื่อนไหวหรือการเข้าร่วมเกม.

ผลลัพธ์ที่คาดหวัง

ตัวอย่างข้อความบนไคลเอนต์:

Welcome, Alice!
Update: Bob joined the game
Update: Bob moved DOWN
Player Alice moved UP

ข้อสังเกต

  • คุณสามารถเพิ่มการจัดการสถานะผู้เล่นในฐานข้อมูล เพื่อให้เซิร์ฟเวอร์สามารถจัดการสถานะได้แม้รีสตาร์ต.
  • ใช้กลไก onError หรือ onCompleted เพื่อจัดการการเชื่อมต่อที่หลุด.

การออกแบบนี้สามารถขยายได้ง่ายสำหรับเกมที่ซับซ้อนขึ้น! 🎮

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้

เริ่มต้นสร้าง Quiz Widgets แบบสอบถามบนเว็บกัน

การใช้งาน RPC (Remote Procedure Call) ด้วย Java พร้อมตัวอย่างเกมออนไลน์อย่างง่าย