Backend Development 20 min read

Design and Plugin‑Based Refactoring of a Cross‑Platform Long Connection Component

This article describes the motivation, layered architecture, protocol implementation details, and a plug‑in refactoring approach for a cross‑platform long‑connection component that unifies iOS, Android, and WebSocket communication using libuv, mbedTLS, and C interfaces.

Architecture Digest
Architecture Digest
Architecture Digest
Design and Plugin‑Based Refactoring of a Cross‑Platform Long Connection Component

Background

Before developing a cross‑platform component, iOS and Android each used separate long‑connection modules, causing duplicated effort, maintenance overhead, and inconsistencies in product requirements; the Web side used WebSocket while the native clients used raw sockets, further increasing backend complexity. To address these issues a unified WebSocket‑based long‑connection component was created.

Architecture Overview

The component is divided into five layers from top to bottom:

Native Layer : encapsulates business requests, parses data, and interacts with native code.

Chat Layer : provides C‑level interfaces for connection, read/write, and close operations.

WebSocket Layer : implements the WebSocket protocol and maintains heartbeats.

TLS Layer : uses mbedTLS to provide TLS encryption and decryption.

TCP Layer : uses libuv to handle TCP connections and I/O.

The overall architecture is illustrated in the diagram below.

TCP Layer

The TCP layer is built on libuv, an asynchronous I/O library supporting Linux, Windows, and Darwin. It provides an event loop with six phases: timers, I/O callbacks, idle/prepare, poll, check, and close callbacks.

TLS Layer

mbedTLS (formerly PolarSSL) implements SSL/TLS protocols, providing confidentiality and integrity for network communication. It handles the handshake, mutual authentication, cipher suite negotiation, and key exchange.

WebSocket Layer

The WebSocket layer implements RFC 6455 (version 13), handling handshake, data framing, and connection closure. During the handshake the client sends an HTTP request with an Upgrade: websocket header and a Sec-WebSocket-Key . The server replies with 101 Switching Protocols and a Sec-WebSocket-Accept derived from the key and a GUID.

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Data frames consist of FIN, RSV1‑3, opcode, MASK, and payload length fields. The article details the bit layout, payload length handling (≤125, 126, 127), masking, and examples of constructing frames in C.

char *rev = (char *)malloc(4);
rev[0] = (char)(0x81 & 0xff);
rev[1] = 126 & 0x7f;
rev[2] = 1;
rev[3] = 0;

Connection Closure

Both server‑initiated and client‑initiated closures are handled by sending a close frame, parsing the opcode, and responding with a corresponding close frame.

Plug‑in Architecture Refactoring

To support flexible protocol stacks (e.g., optional TLS, custom protocols), the original tightly‑coupled four‑layer design (Worker → WebSocket → TLS → TCP) was refactored into a plug‑in system. Each plug‑in implements a common structure with function pointers for init, connect, read, write, close, destroy, reset, and callbacks, and they are linked via a doubly‑linked list.

typedef struct dul_node_s {
    dul_node_t *pre;
    dul_node_t *next;
    char *host;
    int port;
    map_t params;
    node_init init;
    node_conn conn;
    node_write_data write_data;
    node_read_data read_data;
    node_close close;
    node_destroy destroy;
    node_reset reset;
    node_conn_cb conn_cb;
    node_write_cb write_cb;
    node_recv_cb recv_cb;
    node_close_cb close_cb;
} dul_node_t;

Plugins are loaded from a string like "ws?path=/!tls!uv" , parsed by separate_loaders , and each plugin registers its functions via a dispatch table. Adding a new plugin (e.g., a logging layer) only requires defining its structure, implementing the interface functions, and adding it to the loader table.

#define LOADER_ALLOC(type, name) \
    void name##_alloc(dul_node_t **ctx) { \
        type##_t **loader = (type##_t **)ctx; \
        (*loader) = malloc(sizeof(type##_t)); \
        (*loader)->init = &name##_init; \
        (*loader)->next = NULL; \
        (*loader)->pre = NULL; \
    }
LOADER_ALLOC(websocket, ws);
LOADER_ALLOC(zim_tls, tls);
LOADER_ALLOC(zim_uv, uv);
LOADER_ALLOC(zim_log, log);

The refactored design achieves decoupling, extensibility, and cold‑plug‑and‑play capability, allowing developers to compose arbitrary protocol stacks without modifying core code.

Conclusion

The cross‑platform long‑connection component leverages libuv and mbedTLS for TCP and TLS, implements WebSocket handshakes and framing, and abstracts communication through a plug‑in architecture that uses function pointers and a doubly‑linked list to achieve modularity and flexibility.

plugin architectureWebSocketNetworkingC programmingTLSlibuv
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.