#websocket-server #client-server #bevy-networking #networking #tls-server #websocket-client #gamedev

bevy_simplenet

在WebSocket上实现的支持Bevy的简单服务器/客户端通道

24个版本 (11个破坏性更新)

0.12.0 2024年7月5日
0.11.0 2024年5月7日
0.9.2 2024年3月8日
0.5.3 2023年12月19日
0.5.1 2023年11月29日

#83 in 游戏开发


bevy_simplenet_events 中使用

MIT/Apache

140KB
2K SLoC

Bevy Simplenet

提供通过WebSocket实现的双向服务器/客户端通道。此Crate适用于用户认证、与匹配服务通信、微服务之间的通信、对延迟要求不严格的游戏等。

  • 客户端/服务器通道包括一次性消息和请求/响应API。
  • 可以跟踪客户端消息的状态。
  • 客户端自动在原生和WASM目标上工作。
  • 服务器可以验证客户端(进行中)。
  • 提供可选的TLS服务器。

查看示例,了解如何使用此Crate构建Bevy客户端。

查看 bevy_simplenet_events 了解在此Crate基础上构建的基于事件的网络框架。

功能

  • default: 包含 bevyclientserver 功能
  • bevy: 在 ClientServer 上派生 Resource
  • client: 启用客户端(原生和WASM目标)
  • server: 启用服务器(仅原生目标)
  • tls-rustls: 通过 rustls 启用服务器TLS
  • tls-openssl: 通过 OpenSSL 启用服务器TLS

WASM

在WASM目标上,当其他任务正在运行时,客户端后端不会更新。您必须构建一个以IO为导向的应用程序,该应用程序自然花费大量时间轮询任务,或者定期手动释放主线程(例如,使用web_sys::Window::set_timeout_with_callback_and_timeout_and_arguments_0())。对于Bevy应用程序,后者在每次应用程序更新/计时结束时自动发生(参见bevy::app::ScheduleRunnerPlugin 实现)。

使用说明

  • 服务器和客户端必须使用enfync运行时创建。后端是ezsockets
  • 客户端的AuthRequest类型必须与相应服务器的Authenticator类型匹配。
  • 客户端ID由客户端在连接到服务器时通过其AuthRequest定义。如果ID已经连接,连接将被拒绝。
  • 客户端连接消息将在所有重连尝试中被克隆,因此应将其视为静态数据。
  • 如果底层连接中断,服务器或客户端的消息可能无法发送。客户端可以使用Client::send()Client::request()返回的信号来跟踪消息的状态。客户端请求结果将始终由Client::next()发出。消息跟踪对于服务器不可用。
  • 跟踪级别假定服务器是可信的,而客户端不可信。

示例

设置

公共

定义一个通道。

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestConnectMsg(pub String);

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerMsg(pub u64);

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerResponse(pub u64);

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientMsg(pub u64);

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientRequest(pub u64);

#[derive(Debug, Clone)]
pub struct TestChannel;
impl ChannelPack for TestChannel
{
    type ConnectMsg = TestConnectMsg;
    type ServerMsg = TestServerMsg;
    type ServerResponse = TestServerResponse;
    type ClientMsg = TestClientMsg;
    type ClientRequest = TestClientRequest;
}

服务器

准备创建服务器。

type TestServerEvent = ServerEventFrom<TestChannel>;

fn server_factory() -> ServerFactory<TestChannel>
{
    // It is recommended to make server/client factories with baked-in protocol versions (e.g.
    //   with env!("CARGO_PKG_VERSION")).
    ServerFactory::<TestChannel>::new("test")
}

创建一个服务器并将其插入到应用程序中。

fn setup_server(mut commands: Commands)
{
    let server = server_factory().new_server(
            enfync::builtin::native::TokioHandle::default(),
            "127.0.0.1:0",
            AcceptorConfig::Default,
            Authenticator::None,
            ServerConfig::default(),
        );
    commands.insert_resource(server);
}

客户端

准备创建客户端。

type TestClientEvent = ClientEventFrom<TestChannel>;

fn client_factory() -> ClientFactory<TestChannel>
{
    // You must use the same protocol version string as the server factory.
    ClientFactory::<TestChannel>::new("test")
}

创建一个客户端并将其插入到应用程序中。

fn setup_client(mut commands: Commands)
{
    let client_id = 0u128;
    let client = client_factory().new_client(
            enfync::builtin::Handle::default(),  //automatically selects native/WASM runtime
            server.url(),
            AuthRequest::None{ client_id },
            ClientConfig::default(),
            TestConnectMsg(String::from("hello"))
        );
    commands.insert_resource(client);
}

从客户端发送

发送一条消息。

fn send_client_message(client: Client<TestChannel>)
{
    let message_signal = client.send(TestClientMsg(42));
}

发送一个请求。

fn send_client_request(client: Client<TestChannel>)
{
    let request_signal = client.request(TestClientRequest(24));
}

从服务器发送

发送一条消息。

fn send_server_message(server: Server<TestChannel>)
{
    server.send(0u128, TestServerMsg(111));
}

发送响应。

fn send_server_response(In(token): In<RequestToken>, server: Server<TestChannel>)
{
    server.respond(token, TestServerResponse(1));
}

客户端读取

fn read_on_client(client: &mut Client<TestChannel>)
{
    while let Some(client_event) = client.next()
    {
        match client_event
        {
            TestClientEvent::Report(connection_report) => match connection_report
            {
                ClientReport::Connected                => todo!(),
                ClientReport::Disconnected             => todo!(),
                ClientReport::ClosedByServer(reason)   => todo!(),
                ClientReport::ClosedBySelf             => todo!(),
                ClientReport::IsDead(pending_requests) => todo!(),
            }
            TestClientEvent::Msg(message)                   => todo!(),
            TestClientEvent::Response(response, request_id) => todo!(),
            TestClientEvent::Ack(request_id)                => todo!(),
            TestClientEvent::Reject(request_id)             => todo!(),
            TestClientEvent::SendFailed(request_id)         => todo!(),
            TestClientEvent::ResponseLost(request_id)       => todo!(),
        }
    }
}

服务器读取

fn read_on_server(server: &mut Server<TestChannel>)
{
    while let Some((client_id, server_event)) = server.next()
    {
        match server_event
        {
            TestServerEvent::Report(connection_report) => match connection_report
            {
                ServerReport::Connected(env, message) => todo!(),
                ServerReport::Disconnected            => todo!(),
            }
            TestServerEvent::Msg(message)            => todo!(),
            TestServerEvent::Request(token, request) => todo!(),
        }
    }
}

待办事项

  • 实现AuthToken以进行客户端/服务器身份验证。
  • 添加服务器关闭程序。
  • 使用const generics将协议版本直接烘焙到ServerClient中,而不是依赖于工厂(目前由于缺乏健壮的编译器支持而受阻)。

Bevy兼容性

bevy bevy_simplenet
0.14 v0.12 - master
0.13 v0.9 - v0.11
0.12 v0.5 - v0.8
0.11 v0 - v0.4

依赖关系

~5–20MB
~313K SLoC