การใช้งาน 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();
}
}
การทดสอบ
รันเซิร์ฟเวอร์ (
GameServer
)- เซิร์ฟเวอร์จะเริ่มทำงานและพร้อมให้ไคลเอนต์เชื่อมต่อ.
รันไคลเอนต์ (
GameClient
)- ผู้เล่น Alice จะเข้าร่วมเกมและสามารถส่งคำสั่งการเคลื่อนไหว.
- ไคลเอนต์จะรับข้อมูลสถานะของผู้เล่นคนอื่น เช่น การเคลื่อนไหวหรือการเข้าร่วมเกม.
ผลลัพธ์ที่คาดหวัง
ตัวอย่างข้อความบนไคลเอนต์:
Welcome, Alice!
Update: Bob joined the game
Update: Bob moved DOWN
Player Alice moved UP
ข้อสังเกต
- คุณสามารถเพิ่มการจัดการสถานะผู้เล่นในฐานข้อมูล เพื่อให้เซิร์ฟเวอร์สามารถจัดการสถานะได้แม้รีสตาร์ต.
- ใช้กลไก
onError
หรือonCompleted
เพื่อจัดการการเชื่อมต่อที่หลุด.
การออกแบบนี้สามารถขยายได้ง่ายสำหรับเกมที่ซับซ้อนขึ้น! 🎮
ความคิดเห็น
แสดงความคิดเห็น