Automatically generated narration. Code examples are available in the article.
Why I built a networking framework
NeoSocket grew out of Jingi and my experiments with multiplayer scenes. Coming from web development, I wanted the organization I liked in Spring Boot and ASP.NET: handlers, services, dependency injection, and clear places for shared logic.
The challenge was to keep that structure useful for stateful real-time applications. Besides games and metaverse prototypes, I used the framework in Cypher, a chat app.
Connections, packets, and middleware
NeoSocket keeps connection state and routes packets to handlers. A middleware layer provides places to add authentication, authorization, and traffic controls. Those hooks let the application define its policies; they do not by themselves make a server immune to attacks.
Neo-Protocol identifies packets by number and name. MessagePack is the default serialization format, with Protobuf also considered for data exchange. Binary serialization and packet batching reduce repeated metadata and send overhead. The amount saved depends on the payload, so I do not treat the earlier 50–90% estimate as a universal result.
The framework supports request-response calls and one-way commands. The goal is to choose the communication pattern that fits the operation.
Handlers and services
A handler delegates application logic to a service. This example shows the shape of a chat handler:
[NeoHandler]
public class ChatHandler
{
[Autowired] private ChatService _chatService { get; set; }
[HandleMethod]
public void HandleChat(MessageDTO packet)
{
_chatService.HandleChat(packet);
}
}
The service can use the current connection context, publish a message to the room, and save it:
[Service]
public class ChatService
{
[Autowired] private IHandleContext _context { get; set; }
[Autowired] private IChatDB _chatDB { get; set; }
[Autowired] private ChatManager _chatManager { get; set; }
public void HandleChat(MessageDTO message)
{
//Send message to all user in chatroom
var chatRoom = _chatManager.GetUser(_context.Sender.State.IdentityId).ChatRoom;
chatRoom.HandleChat(message);
//Save message to database
_chatDB.AddMessage(new MessageEntity
{
MessageId = Guid.NewGuid().ToString(),
RoomName = chatRoom.RoomName,
UserName = message.UserName,
Content = message.Content,
CreatedAt = DateTime.Now
});
}
}
The handler model is intended to work across TCP, UDP, WebSocket, HTTP, and Unix sockets, while each transport still has its own behavior.
Packet definitions and clients
Packets carry IDs and names, with MessagePack keys defining their fields:
[NeoPacket]
public class MessageDTO : NeoPacket
{
[Key(0)] public string UserName { get; set; }
[Key(1)] public string Content { get; set; }
[Key(2)] public DateTime CreatedAt { get; set; }
}
The TypeScript SDK provides a corresponding definition for web clients:
@DNeoPacket(0, 'MessageDTO')
export class MessageDTO extends NeoPacket {
@DKey(0)
public UserName: string = '';
@DKey(1)
public Content: string = '';
@DKey(2)
public CreatedAt: Date = new Date();
}
The server implementation started on .NET 7, with a .NET Standard 2.0 client for Unity. The TypeScript SDK lets web apps use the same packet-oriented approach. A stateful connection is useful for these workloads, but it is not necessary for every website.
Service lifetimes
The IoC container includes Scoped, Transient, Singleton, and a Parasitic lifetime that follows a NeoClient. Qualifiers help select an implementation when several are available.
[Service]
[Scope(ServiceLifeTime.Parasitic)]
public class ConnectService : IConnectService
{
[Autowired] private ChatManager _chatManager { get; set; }
[Autowired] private IHandleContext _context { get; set; }
[Autowired] private IChatDB _chatDB { get; set; }
}
NeoSocket can run in a console application or as a background service within a .NET application.
Architecture and test notes


One early test reported up to 400,000 requests per second on a GCP VM with one CPU core and two threads, while other Docker services were also running. The chart below records that test. Request handling in this setup is not equivalent to full game logic or a production capacity guarantee.

I also used a simulated scene with 1,000 players to explore dense updates:

Work still to do
The project includes Prometheus and Grafana middleware for metrics, and I started a React monitoring dashboard. Further work includes reducing convenient but overused singletons, improving concurrency and caching, and making the APIs easier to understand.
The main areas I studied were async programming, thread-safe collections, context switching, serialization, reflection, and service lifetimes. Message queues and distributed game servers are possible next uses, rather than completed products.
Stack
| Area | Technology |
|---|---|
| Server and clients | C#, .NET 7, .NET Standard 2.0, TypeScript |
| Transports | TCP, UDP, WebSocket, HTTP, Unix sockets |
| Serialization | MessagePack |
| Connection utilities | UPnP, UDP hole punching |
| Metrics | Prometheus, Grafana |
| Environments | Windows, macOS, Linux; Unity and web clients |