53 次发布

0.18.2 2024年7月11日
0.18.1 2024年2月2日
0.18.0 2023年9月13日
0.17.0 2023年5月17日
0.2.0 2020年7月15日

#12 in WebSocket

Download history 490/week @ 2024-05-04 314/week @ 2024-05-11 365/week @ 2024-05-18 202/week @ 2024-05-25 289/week @ 2024-06-01 234/week @ 2024-06-08 329/week @ 2024-06-15 509/week @ 2024-06-22 352/week @ 2024-06-29 682/week @ 2024-07-06 351/week @ 2024-07-13 389/week @ 2024-07-20 424/week @ 2024-07-27 242/week @ 2024-08-03 267/week @ 2024-08-10 148/week @ 2024-08-17

1,140 下载/月
用于 5 crates

Apache-2.0 许可

205KB
3.5K SLoC

message-io 是一个快速易用的基于事件的网络库。该库内部处理操作系统套接字,并为用户提供简单的消息事件 API。它还允许您根据一些 规则 为自己的传输协议创建适配器,将繁琐的异步性和线程管理委托给库。

如果您在使用库时发现问题或您有改进它的想法,请毫不犹豫地提出问题。 任何贡献都受欢迎! 并且请记住:更多的 咖啡因,更有效率!

动机

管理套接字很困难,因为你需要与线程、并发、全双工、编码、来自操作系统的 IO 错误(在某些情况下这些错误非常难以理解)等进行斗争。如果您使用非阻塞套接字,它还会增加一个新的复杂性层:同步来自操作系统的异步事件。

message-io 提供了一种轻松处理所有这些问题的方法,使这些问题对您(想要使用其自身问题创建应用程序的程序员)来说变得透明。为此,库为您提供了一个简单的 API,其中包含两个易于理解的概念:**消息**(您发送和接收的数据)和**端点**(数据的接收者)。这种抽象还提供了使用相同 API 独立于使用的传输协议的可能性。您可以在 literally 一行内更改应用程序的传输方式。

特性

  • 高度可扩展:允许管理数千个活动连接的**非阻塞套接字**。
  • 多平台:查看 mio 平台支持
  • 多种传输协议 (文档)
    • TCP:流模式和帧模式(用于处理消息而非流)
    • UDP,带有多播选项
    • WebSocket:普通和#102选项,使用tungstenite-rs(不支持wasm,但计划中)。
  • 带有计时器和优先级的自定义FIFO事件
  • 简单、直观且一致的API
    • 遵循KISS原则
    • 从传输层抽象:不要考虑套接字,考虑消息和端点。
    • 仅使用两个主要实体
      • 一个NodeHandler来管理所有连接(连接、监听、删除、发送)和信号(计时器、优先级)。
      • 一个NodeListener来处理来自网络的全部信号和事件。
    • 忘记并发问题:从单个线程处理所有连接和监听器:“一个线程统治一切”。
    • 易于错误处理:在发送/接收网络数据时,无需处理暗内部的std::io::Error
  • 高性能(见基准
    • 零拷贝读写消息。你直接从内部操作系统套接字缓冲区写入和读取,无需库中间任何拷贝。
    • 全双工:在同一内部操作系统套接字上进行同时的读写操作。
  • 可定制:如果message-io没有您需要的传输?轻松添加一个适配器

文档

入门

将其添加到您的Cargo.toml(默认包含所有传输)

[dependencies]
message-io = "0.18"

如果您想使用可用传输子集,可以通过它们的相关功能tcpudpwebsocket来选择。例如,为了只包含TCPUDP,将以下内容添加到您的Cargo.toml

[dependencies]
message-io = { version = "0.18", default-features = false, features = ["tcp", "udp"] }

一站式:TCP、UDP和WebSocket回显服务器

以下示例是最简单的服务器,它从客户端读取消息并对它们进行响应。它能够同时为3种不同的协议提供“服务”。

use message_io::node::{self};
use message_io::network::{NetEvent, Transport};

fn main() {
    // Create a node, the main message-io entity. It is divided in 2 parts:
    // The 'handler', used to make actions (connect, send messages, signals, stop the node...)
    // The 'listener', used to read events from the network or signals.
    let (handler, listener) = node::split::<()>();

    // Listen for TCP, UDP and WebSocket messages at the same time.
    handler.network().listen(Transport::FramedTcp, "0.0.0.0:3042").unwrap();
    handler.network().listen(Transport::Udp, "0.0.0.0:3043").unwrap();
    handler.network().listen(Transport::Ws, "0.0.0.0:3044").unwrap();

    // Read incoming network events.
    listener.for_each(move |event| match event.network() {
        NetEvent::Connected(_, _) => unreachable!(), // Used for explicit connections.
        NetEvent::Accepted(_endpoint, _listener) => println!("Client connected"), // Tcp or Ws
        NetEvent::Message(endpoint, data) => {
            println!("Received: {}", String::from_utf8_lossy(data));
            handler.network().send(endpoint, data);
        },
        NetEvent::Disconnected(_endpoint) => println!("Client disconnected"), //Tcp or Ws
    });
}

回显客户端

以下示例显示了一个可以连接到上一个服务器的客户端。它每秒向服务器发送一条消息并监听其回显响应。将Transport::FramedTcp更改为UdpWs将更改底层使用的传输。

use message_io::node::{self, NodeEvent};
use message_io::network::{NetEvent, Transport};
use std::time::Duration;

enum Signal {
    Greet,
    // Any other app event here.
}

fn main() {
    let (handler, listener) = node::split();

    // You can change the transport to Udp or Ws (WebSocket).
    let (server, _) = handler.network().connect(Transport::FramedTcp, "127.0.0.1:3042").unwrap();

    listener.for_each(move |event| match event {
        NodeEvent::Network(net_event) => match net_event {
            NetEvent::Connected(_endpoint, _ok) => handler.signals().send(Signal::Greet),
            NetEvent::Accepted(_, _) => unreachable!(), // Only generated by listening
            NetEvent::Message(_endpoint, data) => {
                println!("Received: {}", String::from_utf8_lossy(data));
            },
            NetEvent::Disconnected(_endpoint) => (),
        }
        NodeEvent::Signal(signal) => match signal {
            Signal::Greet => { // computed every second
                handler.network().send(server, "Hello server!".as_bytes());
                handler.signals().send_with_timer(Signal::Greet, Duration::from_secs(1));
            }
        }
    });
}

亲自测试!

克隆存储库并测试Ping Pong示例(类似于README示例,但更丰富)。

运行服务器

cargo run --example ping-pong server tcp 3456

运行客户端

cargo run --example ping-pong client tcp 127.0.0.1:3456

您可以通过更改传输、运行多个客户端、断开连接等来玩耍。更多信息请查看这里

您需要message-io没有的传输协议吗?添加一个适配器!

message-io 提供两种 类型 的 API。一种是与 message-io 本身进行交互的 用户 API,另一种是为那些想将它们的协议适配器添加到库中的人提供的内部 适配器 API

如果一种传输协议可以在 mio(大多数现有的协议库都可以)之上构建,那么您可以非常 容易地 将其添加到 message-io

  1. src/adapters/<my-transport-protocol>.rs 中添加您的 适配器 文件,该文件实现了您在这里找到的特质 (此处)。它只需要实现 8 个必需的函数(参见 模板),实现一个适配器大约需要 150 行代码。

  2. src/network/transport.rs 中添加一个新的字段到 Transport 枚举,以注册您的新适配器。

这就完成了。您可以使用新的传输与 message-io API 一样使用。

哦,还有一个步骤:提交一个 Pull Request,这样每个人都可以使用它 :)

使用 message-io 的开源项目

  • Termchat 通过局域网进行视频流和文件传输的终端聊天。
  • Egregoria 沉思社会模拟。
  • Project-Midas 基于分布式网络的并行计算系统。
  • AsciiArena 终端多人死亡比赛游戏(alpha)。
  • LanChat LanChat flutter + rust 示例。

您的出色项目是否使用 message-io?提交一个 Pull Request 并将其添加到列表中!

message-io 适合我吗?

message-io 的主要目标是保持简单。这很好,但有时这种观点可能会使本来已经复杂的事情变得更复杂。

例如,message-io 允许在不需要 async/await 模式的情况下处理异步网络事件。这简化了处理来自网络的收入消息的复杂性,这是非常好的。尽管如此,读取异步消息的应用程序往往会在这类事件上执行异步任务。这种异步继承很容易传播到您的整个应用程序,在没有 async/await 模式的情况下很难维护或扩展。在这些情况下,也许 tokio 是更好的选择。您需要处理更多底层的网络事物,但您在组织和线程/资源管理方面获得了优势。

关于 message-io 的节点使用,也可能出现类似的问题。因为节点可以作为独立的客户端/服务器或两者同时使用,所以您可以很容易地开始制作对等网络应用程序。事实上,这是 message-io 的一个意图之一。尽管如此,如果您的目标需要扩展,将出现与这种模式相关的问题,而像 libp2p 这样的库提供了大量工具来帮助实现这一目标。

当然,这并非是对库使用的否认(我用它!),更多的是关于对其能力的诚实描述,以及根据您的需求引导您使用正确的工具。

总结如下:

  • 如果您有一个中等复杂度的网络问题:使用 message-io 使其更简单!
  • 如果您有一个真正复杂的问题:使用 tokiolibp2p 或其他工具,以获得更多控制。

依赖项

~2–10MB
~113K SLoC