Flutter游戏网络通信插件game_socket的使用
Flutter游戏网络通信插件game_socket的使用
该库是在早期访问阶段发布的,并且尚未稳定。由于它与其他解决方案并行开发,因此在某些方面可能还不成熟。英语不是我的母语,所以没有注释。目前,该库适用于那些希望理解源代码并以此为起点来构建自己的解决方案或帮助我的人。
特性
- 一个库同时包含服务器端和客户端部分。
- API通信库类似于
Socket.io
,但不兼容此解决方案。 - 包含内置二进制协议,因此无需在字节级别工作。
- 传输层使用
TCP
。计划实现与UDP
并行工作以发送游戏消息。 - 实现了多路复用的概念 - 通过单个通道与多个空间进行交互。
当前不计划支持WebSocket
(但社区的支持可能会改变这一点)。
示例
创建客户端
此客户端连接到服务器上的主要/
命名空间,然后连接到/home
命名空间。接着它发送请求进入lobby
房间,之后它会分发一个包含消息文本hello all
的hello
事件。
import 'package:game_socket/client.dart';
void main() {
var client = GameClientExample();
client.connect('localhost', 3103);
}
class GameClientExample extends GameSocketClient {
GameClientExample() {
on(Event.handshake, (packet) => _onHandshake(packet));
on(Event.roomPacket, (packet) => _onRoomPacket(packet));
}
void _onHandshake(Packet packet) {
if (packet.namespace == '/') {
sendMessage(ConnectRequest('/home'));
} else if (packet.namespace == '/home') {
sendMessage(JoinRoomRequest('lobby', namespace: '/home'));
}
}
void _onRoomPacket(RoomPacket packet) {
var roomName = packet.roomName;
if (packet.joinRoom && roomName == 'lobby') {
var msg = RoomEvent(roomName!, namespace: '/home', event: 'hello', message: 'hello all');
sendMessage(msg);
}
}
}
客户端日志
open InternetAddress('127.0.0.1', IPv4) ReadyState.open
handshake Packet{[0.0 /], bit:516, bool:16, int:[0, 0, 60, 0, 0, 0], string:{3: 15466abe2006464e99b6c8b36f7f4ed8}}
>> Message{[/home] boolMask:4, int:[0, 0, 0, 0, 0, 0], string:{} null}
handshake Packet{[0.0 /home], bit:516, bool:16, int:[0, 0, 60, 0, 0, 0], string:{3: 15466abe2006464e99b6c8b36f7f4ed8}}
>> Message{[/home] boolMask:16, int:[0, 0, 0], string:{0: lobby} null}
>> Message{[/home] boolMask:512, int:[0, 0, 0], string:{0: lobby, 5: hello, 1: hello all} null}
创建服务器
import 'package:game_socket/server.dart';
void main() {
var service = SocketServiceExample();
service.listen();
}
class SocketServiceExample {
late GameSocketServer server;
late Namespace home;
SocketServiceExample() {
server = GameSocketServer(options: ServerOptions.byDefault()..supportRawData = true);
home = server.of('/home');
home.on(ServerEvent.connect, (data) => _onHomeConnect(data));
home.on('hello', (packet) => _onHomeData(packet));
//
server.on(ServerEvent.connection, (socket) {
print('/: connection $socket');
socket.on(ServerEvent.connect, (data) => _onConnect(data[0], data[1]));
socket.on(Event.disconnecting, (data) => _onDisconnecting(data));
socket.on(Event.disconnect, (data) => _onDisconnect(data[0], data[1]));
socket.on(Event.error, (data) => _onError(data));
socket.on(Event.data, (data) => _onData(data));
socket.on(Event.close, (data) => {_onClose(data)});
});
server.on(ServerEvent.error, (data) => {print('/: eventError $data')});
server.on(ServerEvent.close, (data) => {print('/: serverClose $data')});
server.on(ServerEvent.raw, (data) => {print('/: raw $data')});
server.on(ServerEvent.createRoom, (data) => {print('/: createRoom $data')});
server.on(ServerEvent.joinRoom, (data) => {print('/: joinRoom $data')});
server.on(ServerEvent.leaveRoom, (data) => {print('/: leaveRoom $data')});
server.on(ServerEvent.deleteRoom, (data) => {print('/: deleteRoom $data')});
}
void listen() {
server.listen();
}
void _onHomeConnect(dynamic data) {
print('/home: connect $data');
}
void _onHomeData(dynamic data) {
print('/home: $data');
if (data is RoomPacket && data.roomName != null) {
home.broadcast(data, rooms: {data.roomName!});
}
}
void _onConnect(String namespace, String socketId) {
print('/: connect $socketId');
}
void _onDisconnecting(dynamic data) {
print('/: disconnecting $data');
}
void _onDisconnect(String namespace, String reason) {
print('$namespace: disconnect reason:$reason');
}
void _onError(dynamic data) {
print('/: error $data');
}
void _onData(dynamic data) {
print('/: $data');
}
void _onClose(dynamic data) {
print('/: close $data');
}
}
服务器日志
listen null Options{ port:3103 raw:true closeOnError:false }
/: connection GameClient{ 15466abe2006464e99b6c8b36f7f4ed8 ReadyState.open [137545126]}
/: createRoom 15466abe2006464e99b6c8b36f7f4ed8
/: joinRoom [15466abe2006464e99b6c8b36f7f4ed8, 15466abe2006464e99b6c8b36f7f4ed8]
/home: connect [/home, 15466abe2006464e99b6c8b36f7f4ed8]
协议
该协议基于模式化。这种方法允许减少传输的数据量,因为消息中不传递数据类型,且数字长度不会被序列化。
数据类型
类型 | 大小 | 范围 |
---|---|---|
bool | 1 bit | true 或 false |
int8 | 1 byte | 0 到 255 |
int16 | 2 bytes | 0 到 65535 |
int32 | 4 bytes | 0 到 4294967295 |
string | 1 + 值 | 0 到 255 字符 |
bytes | 2 + 值 | 0 到 65535 字节 |
操作时的消息数据类型
操作 | 模式类型 | Dart 类型 | 范围 |
---|---|---|---|
putBool | bool | bool | true 或 false |
putInt | int8 | int | -128 到 127 |
putUInt | int8 | int | 0 到 255 |
putInt | int16 | int | -32768 到 32767 |
putUInt | int16 | int | 0 到 65535 |
putInt | int32 | int | -2147483648 到 2147483647 |
putUInt | int32 | int | 0 到 4294967295 |
putString | string | String | 0 到 255 字符 |
putSingle | int8 | double | 0 到 1 步长 ~0.004 |
putRadians | int16 | double | 步长 ~0.0002 |
putPayload | bytes | Uint8List | 0 到 65535 字节 |
计划
- 初始化发送
UDP
图表。 - 自动连接和重新连接。
- 扩展房间工作的可能性。
- 进行压力测试。
初学者提示
- 如果你正在开发浏览器游戏,那么你需要一个
WebSocket
解决方案。 - 在设计实时通信的游戏时,应优先考虑
UDP
,因为TCP
会在数据包丢失时导致延迟。
创建架构
当你创建一个架构时,你要做两件事:取名为单元格数组的编号,并确定数组长度为五种架构数据类型之一。
import 'package:game_socket/protocol.dart';
typedef PS = PlayerStateSchema;
class PlayerStateSchema extends SimpleSchema {
@override
int get code => 10; // 独特的架构代码 10..255
@override
int get version => 1; // 版本 0..255 以支持具有不同版本的游戏客户端
// bool
static const int reserved = 0; // 保留
@override
int get boolCount => 1;
// int8
static const int speed = 0; // 0.000..1.000
static const int health = 1; // 最大值 (100)
@override
int get int8Count => 2;
// int16
static const int uid = 2; // 最大值 (65535)
static const int angle = 3; // 弧度
static const int score = 4; // 最大值 (65535)
@override
int get int16Count => 3;
// int32
static const int elapsedTime = 5; // 用于内部同步的时间
static const int x = 6; // x坐标
static const int y = 7; // y坐标
@override
int get int32Count => 3;
// strings
static const int name = 0; // 玩家名称
@override
int get stringsCount => 1;
}
消息
创建消息类
class PlayerStateMessage extends Message {
PlayerStateMessage(Player player, {required double elapsedTime}) : super(PS()) {
putUInt(PS.uid, player.uid);
putInt(PS.x, (player.positionBody.x * 1000).toInt()); // ~ -2000000.0000..+2000000.0000
putInt(PS.y, (player.positionBody.y * 1000).toInt());
putInt(PS.score, player.score);
putSingle(PS.speed, player.speed);
putRadians(PS.angle, player.rotationBody);
putUInt(PS.elapsedTime, (elapsedTime * 1000).toInt()); // double ms
}
}
更多关于Flutter游戏网络通信插件game_socket的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter游戏网络通信插件game_socket的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter游戏项目中使用game_socket
插件来实现网络通信的代码案例。game_socket
插件允许你通过WebSocket进行低延迟、高性能的网络通信,非常适合实时游戏场景。
首先,你需要在你的pubspec.yaml
文件中添加game_socket
依赖:
dependencies:
flutter:
sdk: flutter
game_socket: ^最新版本号 # 请替换为实际最新版本号
然后运行flutter pub get
来安装依赖。
接下来是一个简单的示例,展示如何使用game_socket
进行客户端和服务器之间的通信。
服务器端代码(Node.js示例)
假设你使用Node.js和ws
库来创建一个简单的WebSocket服务器:
// 安装ws库: npm install ws
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log(`Received: ${message}`);
// Echo the message back to the client
ws.send(`Server response: ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server is running on ws://localhost:8080');
Flutter客户端代码
在你的Flutter项目中,你可以按照以下方式使用game_socket
插件:
import 'package:flutter/material.dart';
import 'package:game_socket/game_socket.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Game Socket Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GameSocketDemo(),
);
}
}
class GameSocketDemo extends StatefulWidget {
@override
_GameSocketDemoState createState() => _GameSocketDemoState();
}
class _GameSocketDemoState extends State<GameSocketDemo> {
GameSocket? _socket;
TextEditingController _controller = TextEditingController();
String _response = '';
@override
void initState() {
super.initState();
initSocket();
}
void initSocket() {
_socket = GameSocket('ws://localhost:8080');
_socket!.onOpen = () {
print('WebSocket connection opened');
// Optionally send an initial message
_socket!.send('Hello Server!');
};
_socket!.onMessage = (String message) {
setState(() {
_response = message;
});
print('Received message: $message');
};
_socket!.onClose = () {
print('WebSocket connection closed');
};
_socket!.onError = (error) {
print('WebSocket error: $error');
};
}
void _sendMessage() {
if (_socket!.readyState == WebSocketReadyState.OPEN) {
_socket!.send(_controller.text);
_controller.clear();
} else {
print('WebSocket is not open');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Game Socket Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Message'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _sendMessage,
child: Text('Send Message'),
),
SizedBox(height: 16),
Text('Server Response: $_response'),
],
),
),
);
}
@override
void dispose() {
_socket?.close();
_controller.dispose();
super.dispose();
}
}
说明
-
服务器端:使用Node.js和
ws
库创建一个简单的WebSocket服务器,监听8080
端口。服务器会接收客户端的消息并回显。 -
Flutter客户端:
- 使用
GameSocket
类创建一个WebSocket客户端,并连接到ws://localhost:8080
。 - 设置
onOpen
、onMessage
、onClose
和onError
回调来处理连接状态、接收消息、连接关闭和错误。 - 提供一个简单的UI,让用户可以输入消息并发送到服务器,同时显示从服务器接收到的响应。
- 使用
这个示例展示了基本的WebSocket通信流程,你可以根据需要在游戏中扩展这些功能,比如处理更复杂的消息格式、添加心跳检测、处理重连逻辑等。