SuperSocket.ProtoBuf is an extension library for SuperSocket that provides seamless integration with Google Protocol Buffers (protobuf). This library enables efficient binary serialization and deserialization of network messages with built-in support for message type identification, making it ideal for high-performance network applications requiring cross-platform and cross-language compatibility.
- Efficient binary serialization and deserialization using Google Protocol Buffers
- Built-in message type identification and routing
- Support for strongly-typed message handling with generic APIs
- Easy integration with existing SuperSocket applications
- High-performance network communication
- Multi-platform support including Android, iOS, macOS, and tvOS
- Compatible with .NET 6.0, 7.0, 8.0, and 9.0
dotnet add package SuperSocket.ProtoBuf
<PackageReference Include="SuperSocket.ProtoBuf" Version="[version]" />
syntax = "proto3";
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
bool success = 1;
string message = 2;
}
// Define your package info class to hold the message data
public class MyProtobufPackageInfo
{
public IMessage Message { get; set; }
public Type MessageType { get; set; }
public int TypeId { get; set; }
}
// Create a custom decoder that implements ProtobufPackageDecoder<MyProtobufPackageInfo>
public class MyProtobufPackageDecoder : ProtobufPackageDecoder<MyProtobufPackageInfo>
{
public MyProtobufPackageDecoder(ProtobufTypeRegistry typeRegistry)
: base(typeRegistry) { }
protected override MyProtobufPackageInfo CreatePackageInfo(IMessage message, Type messageType, int typeId)
{
return new MyProtobufPackageInfo
{
Message = message,
MessageType = messageType,
TypeId = typeId
};
}
}
// Create a custom encoder that extends ProtobufPackageEncoder<MyProtobufPackageInfo>
public class MyProtobufPackageEncoder : ProtobufPackageEncoder<MyProtobufPackageInfo>
{
public MyProtobufPackageEncoder(ProtobufTypeRegistry typeRegistry)
: base(typeRegistry) { }
protected override IMessage GetProtoBufMessage(MyProtobufPackageInfo package)
{
return package.Message;
}
protected override int GetProtoBufMessageTypeId(MyProtobufPackageInfo package)
{
return package.TypeId;
}
}
// Create a registry for your message types
var typeRegistry = new ProtobufTypeRegistry();
// Register for both encoding and decoding
typeRegistry.RegisterMessageType(1, typeof(LoginRequest), LoginRequest.Parser);
typeRegistry.RegisterMessageType(2, typeof(LoginResponse), LoginResponse.Parser);
// Configure the SuperSocket host
var host = SuperSocketHostBuilder.Create<MyProtobufPackageInfo>()
.UsePackageDecoder<MyProtobufPackageDecoder>()
.UsePackageEncoder<MyProtobufPackageEncoder>()
.UsePipelineFilter<ProtobufPipelineFilter<MyProtobufPackageInfo>>()
.UsePackageHandler(async (session, package) =>
{
// Handle your protobuf messages based on their types
if (package.MessageType == typeof(LoginRequest))
{
var request = package.Message as LoginRequest;
// Process login request
// Create a response
var response = new LoginResponse
{
Success = true,
Message = $"Welcome {request.Username}!"
};
// Send the response using the encoder
await session.SendAsync(new MyProtobufPackageInfo
{
Message = response,
TypeId = 2
});
}
})
.ConfigureServices(services =>
{
services.AddSingleton(registry);
})
.Build();
await host.RunAsync();
// Create a custom encoder that extends ProtobufPackageEncoder<MyProtobufPackageInfo>
public class MyProtobufPackageEncoder : ProtobufPackageEncoder<MyProtobufPackageInfo>
{
public MyProtobufPackageEncoder(ProtobufTypeRegistry typeRegistry)
: base(typeRegistry) { }
protected override IMessage GetProtoBufMessage(MyProtobufPackageInfo package)
{
return package.Message;
}
protected override int GetProtoBufMessageTypeId(MyProtobufPackageInfo package)
{
return package.TypeId;
}
}
var typeRegistry = new ProtobufTypeRegistry();
typeRegistry.RegisterMessageType(1, typeof(LoginRequest), LoginRequest.Parser);
typeRegistry.RegisterMessageType(2, typeof(LoginResponse), LoginResponse.Parser);
var encoder = new ProtobufPackageEncoder(typeRegistry);
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(serverEndPoint);
var loginRequest = new LoginRequest
{
Username = "username",
Password = "password"
};
var package = new MyProtobufPackageInfo
{
Message = loginRequest,
TypeId = 1
};
// Encode and send the message
var buffer = new ArrayBufferWriter<byte>();
encoder.Encode(buffer, package);
await client.SendAsync(buffer.WrittenMemory, SocketFlags.None);
Registry for mapping between message types and their identifiers:
public class ProtobufTypeRegistry
{
// Register a message type with parser for decoding and encoding
public void RegisterMessageType(int typeId, Type messageType, MessageParser parser);
// Try to get the parser for a given type ID
public bool TryGetParser(int typeId, out MessageParser parser);
// Try to get the message type for a given type ID
public bool TryGetMessageType(int typeId, out Type messageType);
// Try to get the type ID for a given message type
public bool TryGetTypeId(Type messageType, out int typeId);
// Get the type ID for a message type
public int GetTypeId(Type messageType);
// Get the message type for a type ID
public Type GetMessageType(int typeId);
// Get the message parser for a type ID
public MessageParser GetParser(int typeId);
}
Processes incoming network data into structured protobuf messages:
public class ProtobufPipelineFilter<TPackageInfo> : FixedHeaderPipelineFilter<TPackageInfo>
where TPackageInfo : class
{
// Initialize the filter with a decoder
public ProtobufPipelineFilter(IPackageDecoder<TPackageInfo> decoder);
}
Decodes binary data into structured package information:
public abstract class ProtobufPackageDecoder<TPackageInfo> : IPackageDecoder<TPackageInfo>
{
// Initialize with a type registry
public ProtobufPackageDecoder(ProtobufTypeRegistry typeRegistry);
// Decode a binary buffer into a package
public TPackageInfo Decode(ref ReadOnlySequence<byte> buffer, object context);
// Abstract method to create package info from decoded message
protected abstract TPackageInfo CreatePackageInfo(IMessage message, Type messageType, int typeId);
}
Encodes protobuf messages for network transmission:
public abstract class ProtobufPackageEncoder<TPackageInfo> : IPackageEncoder<TPackageInfo>
{
// Initialize with a type registry
public ProtobufPackageEncoder(ProtobufTypeRegistry typeRegistry);
// Encode a package into binary format
public int Encode(IBufferWriter<byte> writer, TPackageInfo package);
// Abstract method to extract the protobuf message from a package
protected abstract IMessage GetProtoBufMessage(TPackageInfo package);
// Virtual method to get message type ID from package
protected virtual int GetProtoBufMessageTypeId(TPackageInfo package);
}
Messages are sent with an 8-byte header followed by the protobuf message payload:
- First 4 bytes: Message size in big-endian format
- Next 4 bytes: Message type ID in big-endian format
- Remaining bytes: The serialized protobuf message
SuperSocket.ProtoBuf provides concrete implementations of ProtobufPipelineFilter
, ProtobufPackageDecoder
, and ProtobufPackageEncoder
that work directly with IMessage
as the package type.
// Create a registry for your message types
var typeRegistry = new ProtobufTypeRegistry();
// Register for both encoding and decoding
typeRegistry.RegisterMessageType(1, typeof(LoginRequest), LoginRequest.Parser);
typeRegistry.RegisterMessageType(2, typeof(LoginResponse), LoginResponse.Parser);
// Configure the SuperSocket host using IMessage as the package type
var host = SuperSocketHostBuilder.Create<IMessage>()
.UsePackageDecoder<MyProtobufPackageDecoder>()
.UsePackageEncoder<MyProtobufPackageEncoder>()
.UsePipelineFilter<ProtobufPipelineFilter<MyProtobufPackageInfo>>()
.UsePackageHandler(async (session, message) =>
{
// Handle messages based on their type
if (message is LoginRequest loginRequest)
{
// Process login request
Console.WriteLine($"Login request received for: {loginRequest.Username}");
// Create a response
var response = new LoginResponse
{
Success = true,
Message = $"Welcome {loginRequest.Username}!"
};
// Send the response directly
await session.SendAsync(response);
}
})
.ConfigureServices(services =>
{
services.AddSingleton(registry);
})
.Build();
await host.RunAsync();
// Create a registry for your message types
var typeRegistry = new ProtobufTypeRegistry();
typeRegistry.RegisterMessageType(1, typeof(LoginRequest), LoginRequest.Parser);
typeRegistry.RegisterMessageType(2, typeof(LoginResponse), LoginResponse.Parser);
// Use the concrete ProtobufPackageEncoder that works directly with IMessage
var encoder = new ProtobufPackageEncoder(typeRegistry);
// Connect to the server
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(serverEndPoint);
// Create a login request message
var loginRequest = new LoginRequest
{
Username = "username",
Password = "password"
};
// Encode and send the message directly
var buffer = new ArrayBufferWriter<byte>();
encoder.Encode(buffer, loginRequest);
await client.SendAsync(buffer.WrittenMemory, SocketFlags.None);
// For receiving responses, you would typically set up a client using the ProtobufPipelineFilter
// with the concrete ProtobufPackageDecoder and handle messages accordingly
This project is licensed under the terms of the LICENSE file included in the repository.
Contributions are welcome! Please feel free to submit a Pull Request.