No description
- Fix base_client.dart close() method - Update communicator.dart for close() pattern - Update protobuf_server.dart and ws_protobuf_server.dart - Update communicator_test.dart for close() API - skills: rename implement/SKILL.md to plan/SKILL.md - tasks: add dart_close_fix task files |
||
|---|---|---|
| .claude | ||
| .vscode | ||
| dart | ||
| go | ||
| skills | ||
| tasks/dart_close_fix | ||
| .codex | ||
| .gitignore | ||
| PORTING_GUIDE.md | ||
| PROTOCOL.md | ||
| README.md | ||
Toki Socket
Binary socket protocol library for bidirectional, heterogeneous communication across languages and platforms.
Built on Protocol Buffers with TCP length-prefixed framing, WebSocket binary frames, type-based message routing, request-response correlation, and built-in heartbeat.
Design Principles
- Keep the core transport layer thin and stable
- Provide only the minimum common foundation for cross-language communication
- Standardize framing, serialization, routing, request-response correlation, and heartbeat
- Do not embed application semantics into the protocol core
- Domain-specific concerns such as auth, session, agent workflow, chat features, and game logic belong in upper-layer implementations
Protocol
See PROTOCOL.md for the full wire format specification.
[4-byte big-endian length] [PacketBase protobuf bytes]
For WebSocket/WSS transports, each binary frame contains one PacketBase protobuf payload without the TCP length header.
Implementations
| Language | Status | Path | Use case |
|---|---|---|---|
| Dart | Available | dart/ | Flutter, Dart server |
| C# | Planned | csharp/ |
Unity, .NET |
| Kotlin | Planned | kotlin/ |
Android |
| Swift | Planned | swift/ |
iOS, macOS |
| Go | Available | go/ | Server, tooling, scripting |
| Python | Planned | python/ |
Server, tooling, scripting |
| Rust | Planned | rust/ |
High-performance server, embedded |
Quick Start (Dart)
import 'package:toki_socket/toki_socket.dart';
// 1. Define your message in message_common.proto, generate with protoc
// 2. Implement a client
class MyClient extends ProtobufClient {
MyClient(Socket socket) : super(socket, 30, 10, {
MyMessage.getDefault().info_.qualifiedMessageName: MyMessage.fromBuffer,
});
}
// 3. Implement a server
class MyServer extends ProtobufServer {
MyServer() : super('0.0.0.0', 9090, (socket) => MyClient(socket));
@override
void onClientConnected(ProtobufClient client) {
client.addListener<MyMessage>((msg) => print('Received: ${msg}'));
}
}
// 4. Start
final server = MyServer();
await server.start();
// 5. Connect and send
final socket = await Socket.connect('localhost', 9090);
final client = MyClient(socket);
await client.send(MyMessage()..text = 'hello');
Adding Message Types
Edit dart/lib/src/packets/message_common.proto and regenerate:
cd dart
dart pub global activate protoc_plugin
protoc --dart_out=lib/src/packets lib/src/packets/message_common.proto
For Go, keep go/packets/message_common.proto in sync with the canonical Dart proto. The Go copy includes option go_package, then regenerate from the Go module root:
cd go
protoc --go_out=. --go_opt=paths=source_relative packets/message_common.proto
Quick Start (Go)
package main
import (
"context"
"net"
"time"
"google.golang.org/protobuf/proto"
toki "toki-labs.com/toki_socket/go"
"toki-labs.com/toki_socket/go/packets"
)
func parserMap() toki.ParserMap {
return toki.ParserMap{
toki.TypeNameOf(&packets.TestData{}): func(b []byte) (proto.Message, error) {
m := &packets.TestData{}
return m, proto.Unmarshal(b, m)
},
}
}
func main() {
ctx := context.Background()
server := toki.NewTcpServer("127.0.0.1", 9090, func(conn net.Conn) *toki.TcpClient {
return toki.NewTcpClient(conn, 30, 10, parserMap())
})
server.OnClientConnected = func(client *toki.TcpClient) {
toki.AddRequestListenerTyped[*packets.TestData, *packets.TestData](
&client.Communicator,
func(req *packets.TestData) (*packets.TestData, error) {
return &packets.TestData{Index: req.GetIndex(), Message: "echo: " + req.GetMessage()}, nil
},
)
}
if err := server.Start(ctx); err != nil {
panic(err)
}
defer server.Stop()
client, err := toki.DialTcp(ctx, "127.0.0.1", 9090, 30, 10, parserMap())
if err != nil {
panic(err)
}
defer client.Close()
res, err := toki.SendRequestTyped[*packets.TestData, *packets.TestData](
&client.Communicator,
&packets.TestData{Index: 1, Message: "hello"},
2*time.Second,
)
if err != nil {
panic(err)
}
println(res.GetMessage())
}
Go also provides:
- TCP:
NewTcpServer,DialTcp,NewTcpServerTLS,DialTcpTLS - WebSocket:
NewWsServer,DialWs,NewWsServerTLS,DialWss - Shared helpers:
Send,SendRequest,AddListenerTyped,AddRequestListenerTyped,Broadcast
Running Tests
cd dart
dart pub get
dart test
cd go
go test ./...