Getting Started with Bitchat: A Netty‑Based Instant Messaging Framework
This guide introduces Bitchat, a Netty‑based IM framework, showing how to start the server and client, use built‑in commands for login, user listing and private messaging, explains its architecture, custom protocol, health‑check mechanisms, and how to handle business logic with asynchronous thread pools.
Quick Start
The bitchat-example module provides a sample server and client implementation that can be used as a reference for custom business logic.
Start Server
Obtain a Server instance via SimpleServerFactory by specifying a port.
public class StandaloneServerApplication {
public static void main(String[] args) {
Server server = SimpleServerFactory.getInstance()
.newServer(8864);
server.start();
}
}After the server starts, it prints a startup message.
Start Client
Use SimpleClientFactory with a ServerAttr to create a client and connect to the server.
public class DirectConnectServerClientApplication {
public static void main(String[] args) {
Client client = SimpleClientFactory.getInstance()
.newClient(ServerAttr.getLocalServer(8864));
client.connect();
doClientBiz(client);
}
}Upon successful connection, the client displays a connection message.
Client Functions
The client supports three commands: login, list online users, and send a private message.
Login
Execute -lo username password to log in. The demo uses a mock user service, so any credentials succeed and a user ID is generated.
List Online Users
After logging in with another client, run -lu to retrieve the online user list, which is stored in memory.
Send Private Message
Use -pc 1 hello,houyi where the second argument is the target user ID and the third is the message content.
Client Reconnection
The client and server maintain a heartbeat using Netty's IdleStateHandler . The client sends a PingPacket every 5 seconds; the server replies with a PongPacket . If the server does not receive a ping, it closes the channel, and the client attempts reconnection.
Overall Architecture
Standalone Mode
In standalone mode the architecture consists of a server, a client, and a protocol layer.
Message Center – stores messages, history, and offline queries.
User Center – handles user and group services.
Connection Center – maintains client connections for message push.
Cluster Mode
Cluster mode adds a routing layer so clients can discover available server addresses. When a server receives a message, it checks the Connection Center to determine whether the target user is attached to the same server or another; if the latter, the request is forwarded to the appropriate server.
Custom Protocol
The protocol defines several fields:
*
*
The structure of a Packet is like below:
* +----------+----------+----------------------------+
* | size | value | intro |
* +----------+----------+----------------------------+
* | 1 bytes | 0xBC | magic number |
* | 1 bytes | | serialize algorithm |
* | 4 bytes | | packet symbol |
* | 4 bytes | | content length |
* | ? bytes | | content |
* +----------+----------+----------------------------+
*Fields include magic number, serialization algorithm, packet type, content length, and the actual content. A sync field indicates whether the packet should be processed asynchronously.
Health Check
Both server and client use Netty's IdleStateHandler to detect idle connections. When a read idle event occurs, the channel is closed:
public class IdleStateChecker extends IdleStateHandler {
private static final int DEFAULT_READER_IDLE_TIME = 15;
private int readerTime;
public IdleStateChecker(int readerIdleTime) {
super(readerIdleTime == 0 ? DEFAULT_READER_IDLE_TIME : readerIdleTime, 0, 0, TimeUnit.SECONDS);
readerTime = readerIdleTime == 0 ? DEFAULT_READER_IDLE_TIME : readerIdleTime;
}
@Override
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
log.warn("[{}] Hasn't read data after {} seconds, will close the channel:{}",
IdleStateChecker.class.getSimpleName(), readerTime, ctx.channel());
ctx.channel().close();
}
}The client also runs a HealthyChecker that periodically sends PingPacket s and attempts reconnection on channel inactivity.
public class HealthyChecker extends ChannelInboundHandlerAdapter {
private static final int DEFAULT_PING_INTERVAL = 5;
private Client client;
private int pingInterval;
public HealthyChecker(Client client, int pingInterval) {
Assert.notNull(client, "client can not be null");
this.client = client;
this.pingInterval = pingInterval <= 0 ? DEFAULT_PING_INTERVAL : pingInterval;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
schedulePing(ctx);
}
private void schedulePing(ChannelHandlerContext ctx) {
ctx.executor().schedule(() -> {
Channel channel = ctx.channel();
if (channel.isActive()) {
log.debug("[{}] Send a PingPacket", HealthyChecker.class.getSimpleName());
channel.writeAndFlush(new PingPacket());
schedulePing(ctx);
}
}, pingInterval, TimeUnit.SECONDS);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.executor().schedule(() -> {
log.info("[{}] Try to reconnect...", HealthyChecker.class.getSimpleName());
client.connect();
}, 5, TimeUnit.SECONDS);
ctx.fireChannelInactive();
}
}Business Thread Pool
Netty provides boss and worker IO threads; heavy business logic (e.g., DB queries, file I/O) should be offloaded to a separate business thread pool. Packets contain a sync flag that determines whether they are processed synchronously on the IO thread or asynchronously via the business pool.
public class ServerPacketDispatcher extends SimpleChannelInboundHandler
{
@Override
public void channelRead0(ChannelHandlerContext ctx, Packet request) {
if (request.getAsync() == AsyncHandle.ASYNC) {
EventExecutor channelExecutor = ctx.executor();
Promise
promise = new DefaultPromise<>(channelExecutor);
Future
future = executor.asyncExecute(promise, ctx, request);
future.addListener(new GenericFutureListener
>() {
@Override
public void operationComplete(Future
f) throws Exception {
if (f.isSuccess()) {
Packet response = f.get();
writeResponse(ctx, response);
}
}
});
} else {
Packet response = executor.execute(ctx, request);
writeResponse(ctx, response);
}
}
}Beyond an IM Framework
Bitchat can also serve as a generic communication framework. By extending AbstractPacket and implementing custom PacketHandler s, developers can create various business protocols on top of the same transport layer.
For more details, visit the GitHub repository: https://github.com/all4you/bitchat
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.