KCP and Wirehair are both used in networking to improve data transmission, but they serve different purposes and are suited for different scenarios.
Before diving into details, here is a quick overview:
| KCP | Wirehair | |
|---|---|---|
| type | reliable ARQ | Fountain code (FEC) |
| best for | real-time, low latency apps | high packet loss, one-to-many broadcasts |
| latency | optimized | encoding/decoding latency |
| packet loss | retransmission (ARQ) | forward error correction (FEC) |
| use case | gaming, live streaming | multicast streaming |
| overhead | 10-20% bandwidth | depends on FEC redundancy |
| ordering | preserved | no inherent ordering |
| feedback | requires ACKS for reliability | no feedback needed |
KCP Link to heading
KCP is a fast ans reliable ARQ (Automatic Repeat-reQuest) protocol typically implemented over UDP. It is designed to reduce latency and improve performance compared to TCP, especially in high-latency or lossy networks.
KCP achieves this through aggressive retransmission, stream multiplexing, and customizable congestion control, at the cose of slightly increased bandwidth usage (10-20% more than TCP).
Its library consists of only two files (ikcp.h and ikcp.c) which is easy
to integrate or to read.
When to use KCP Link to heading
- When using unreliable networks: KCP performs well on networks where packet loss or jitter is common (e.g. wireless or mobile networks are prone to these issues because of interference/signal degradation)
- When using High latency sensitivity applications: KCP is good for handling situation where low latency is critical (e.g. gaming, video or voice streaming) as it retransmits packets quickly and anticipates packet loss
When not to use KCP Link to heading
- When bandwidth efficiency is critical: KCP trades bandwidth for lower latency
- When you need truly unreliable communication: KCP might not fit in your application if packet drops are acceptable (e.g. position updates in a video game).
Echo server Link to heading
We’ll first implement the server side. It will be accepting a single client on port UDP/8080 and upgrade that UDP session to KCP.
Whenever UDP data is received, it is forwarded to KCP from ikcp_input.
Whenever KCP data is received from ikcp_recv, it is forwarded back to the
client through ikcp_send which will ultimately call udp_output.
1#define _GNU_SOURCE
2
3#include <netinet/in.h>
4#include <stdio.h>
5#include <string.h>
6#include <time.h>
7#include <unistd.h>
8
9#include "kcp-1.7/ikcp.h"
10
11typedef struct {
12 int fd;
13 struct sockaddr_in server_addr;
14 struct sockaddr_in client_addr;
15} server_t;
16
17int udp_output(const char *buf, int len, [[maybe_unused]] ikcpcb *kcp,
18 void *user) {
19 server_t *s = user;
20 const int tx = sendto(s->fd, buf, len, MSG_CONFIRM,
21 (const struct sockaddr *)&s->client_addr, len);
22 printf("udp_output: tx = %d\n", tx);
23 return 0; // unused by kcp_output
24}
25
26static unsigned int ms_timestamp(void) {
27 struct timespec ts;
28 if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) == -1)
29 exit(1);
30
31 return ts.tv_sec * 1000 + ts.tv_nsec / 1e6;
32}
33
34static void wait(ikcpcb *kcp) {
35 const int current = ms_timestamp();
36 ikcp_update(kcp, current);
37 int ts = ikcp_check(kcp, current);
38 usleep((ts - current) * 1000);
39}
40
41int main(void) {
42 server_t s;
43
44 if ((s.fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
45 perror("socket creation failed");
46 exit(1);
47 }
48
49 memset(&s.server_addr, 0, sizeof(s.server_addr));
50 memset(&s.client_addr, 0, sizeof(s.client_addr));
51
52 s.server_addr.sin_family = AF_INET;
53 s.server_addr.sin_addr.s_addr = INADDR_ANY;
54 s.server_addr.sin_port = htons(8080);
55
56 if (bind(s.fd, (const struct sockaddr *)&s.server_addr,
57 sizeof(s.server_addr)) < 0) {
58 perror("bind failed");
59 exit(1);
60 }
61
62 ikcpcb *kcp = ikcp_create(0x11223344, &s);
63 ikcp_setoutput(kcp, udp_output);
64
65 socklen_t len = sizeof(s.client_addr);
66 char buffer[BUFSIZ];
67 while (1) {
68 wait(kcp);
69
70 // Receive data from low level layer (UDP)
71 const int rx_low = recvfrom(s.fd, buffer, sizeof buffer, MSG_WAITALL,
72 (struct sockaddr *)&s.client_addr, &len);
73 if (rx_low < 0) {
74 printf("[-] recvfrom rx_low %d (continue)\n", rx_low);
75 continue;
76 } else {
77 printf("[+] recvfrom rx_low %d\n", rx_low);
78 }
79
80 // Forward low level data (UDP) to higher level layer (KCP)
81 const int tx_low = ikcp_input(kcp, buffer, rx_low);
82 if (tx_low < 0) {
83 printf("[-] ikcp_input tx_low = %d (continue)\n", tx_low);
84 continue;
85 } else {
86 printf("[+] ikcp_input tx_low = %d\n", tx_low);
87 }
88
89 // Try to echo data at the high level layer (KCP)
90 while (1) {
91 // Read data from high level layer
92 const int rx_high = ikcp_recv(kcp, buffer, rx_low);
93 if (rx_high < 0) {
94 printf("[-] ikcp_recv rx_high = %d (break)\n", rx_high);
95 break;
96 } else {
97 printf("[+] ikcp_recv rx_high = %d\n", rx_high);
98 printf("[+] ikcp_recv %.*s\n", rx_high, buffer);
99 }
100
101 // Write data to high level layer
102 const int tx_high = ikcp_send(kcp, buffer, rx_high);
103 if (tx_high < 0) {
104 printf("[-] ikcp_send tx_high = %d (continue)\n", tx_high);
105 continue;
106 } else {
107 printf("[+] ikcp_send tx_high = %d\n", tx_high);
108 }
109 }
110 }
111
112 ikcp_release(kcp);
113 return 0;
114}
Client Link to heading
The client reads data from stdin (fd 0), and forwards it to the client with
ikcp_send which under the hood is using udp_output.
Then we read KCP echoed data from the server with recvfrom. That data is
forwarded to ikcp_input and we can then retrieve the raw echo data by calling
ikcp_recv.
1#define _GNU_SOURCE
2
3#include <netinet/in.h>
4#include <stdio.h>
5#include <string.h>
6#include <time.h>
7#include <unistd.h>
8
9#include "../thirdparty/kcp-1.7/ikcp.h"
10
11typedef struct {
12 int fd;
13 struct sockaddr_in server_addr;
14} client_t;
15
16static int udp_output(const char *buf, int len, [[maybe_unused]] ikcpcb *kcp,
17 void *user) {
18 client_t *c = user;
19 const int tx = sendto(c->fd, buf, len, MSG_CONFIRM,
20 (const struct sockaddr *)&c->server_addr, len);
21 printf("udp_output: tx = %d\n", tx);
22 return 0; // unused by kcp_output
23}
24
25static unsigned int ms_timestamp(void) {
26 struct timespec ts;
27 if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) == -1)
28 exit(1);
29
30 return ts.tv_sec * 1000 + ts.tv_nsec / 1e6;
31}
32
33static void wait(ikcpcb *kcp) {
34 const int current = ms_timestamp();
35 ikcp_update(kcp, current);
36 int ts = ikcp_check(kcp, current);
37 usleep((ts - current) * 1000);
38}
39
40int main(void) {
41 client_t c;
42
43 if ((c.fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
44 perror("socket creation failed");
45 exit(1);
46 }
47
48 memset(&c.server_addr, 0, sizeof(c.server_addr));
49
50 c.server_addr.sin_family = AF_INET;
51 c.server_addr.sin_port = htons(8080);
52 c.server_addr.sin_addr.s_addr = INADDR_ANY;
53
54 socklen_t len;
55
56 ikcpcb *kcp = ikcp_create(0x11223344, &c);
57 ikcp_setoutput(kcp, udp_output);
58
59 char buffer[BUFSIZ];
60 while (1) {
61 wait(kcp);
62
63 // Read user input on stdin
64 printf("input> ");
65 fflush(stdout);
66 const int rx_user = read(0, buffer, sizeof buffer);
67 if (rx_user < 0) {
68 printf("[-] read rx_user = %d (break)\n", rx_user);
69 break;
70 } else if (rx_user == 0) {
71 printf("[-] read rx_user == 0 (break)\n");
72 break;
73 } else {
74 printf("[+] read rx_user = %d\n", rx_user);
75 }
76
77 buffer[strcspn(buffer, "\n")] = '\0';
78
79 // Forward user data to high level layer (KCP)
80 const int tx_user = ikcp_send(kcp, buffer, rx_user);
81 if (tx_user < 0) {
82 printf("[-] ikcp_send tx_low = %d (continue)\n", tx_user);
83 continue;
84 } else {
85 printf("[+] ikcp_send tx_low = %d\n", tx_user);
86 }
87
88 ikcp_update(kcp, ms_timestamp());
89
90 // Receive server data from low level layer (UDP)
91 const int rx_low = recvfrom(c.fd, buffer, sizeof buffer, MSG_WAITALL,
92 (struct sockaddr *)&c.server_addr, &len);
93 if (rx_low < 0) {
94 printf("[-] recvfrom rx_low = %d (break)\n", rx_low);
95 break;
96 } else {
97 printf("[+] recvfrom rx_low = %d\n", rx_low);
98 }
99
100 // Forward low level server data (UDP) to higher level layer (KCP)
101 const int tx_low = ikcp_input(kcp, buffer, rx_low);
102 if (tx_low < 0) {
103 printf("[-] ikcp_input tx_low = %d (continue)\n", tx_low);
104 continue;
105 } else {
106 printf("[+] ikcp_input tx_low = %d\n", tx_low);
107 }
108
109 // Receive server data from high level layer (KCP)
110 while (1) {
111 int rx_high = ikcp_recv(kcp, buffer, rx_low);
112 if (rx_high < 0) {
113 printf("[-] ikcp_recv rx_high = %d (break)\n", rx_high);
114 break;
115 } else if (rx_high == 0) {
116 printf("[+] ikcp_recv rx_high == 0 (break)\n");
117 break;
118 } else {
119 printf("[+] ikcp_recv rx_high = %d\n", rx_high);
120 printf("[+] ikcp_recv %.*s\n", rx_high, buffer);
121 }
122 }
123 }
124
125 ikcp_release(kcp);
126 close(c.fd);
127 return 0;
128}
Wirehair Link to heading
Wirehair is an implementation of a Fountain Code designed for forward error correction (FEC). It encodes data in a potentially infinite stream of encoded packets, allowing the receiver to reconstruct the original data from any subset of packets.
It is efficient for recovering from packet loss without retransmission. Unlike KCP, Wirehair is not a full transport protocol but a coding scheme typically used with UDP (as for KCP).
When to use Wirehair Link to heading
- When prone to packet loss: Wirehair is designed for environnments where packet loss is common (e.g. mobile/satellite links, congested networks, etc) where retransmission is costly or impractical. It allows data reconstruction as long as enought encoded packets are received, reducing the need for retransmission.
- When using one-to-many broadcasts: Wirehair is ideal for multicast or broadcast application where retransmission of lost packets to many receivers can be problematic (e.g. software updates, IoT broadcasts, etc).
- When you cannot acknowledge / have a feedback channel: unline ARQ-based protocols like KCP, Wirehair does not rely on acknowledgments nor retransmissions, making it useful in situations with high latency or when no reliable feedback channel is available (e.g. diodes, deep-space communications)
When not to use Wirehair Link to heading
- When requiring a strict ordering of packets: Wirehair focuses on data reconstruction rather than preserving data order. You might need an additional layer to handle ordering.
- When dealing with real-time applications: Wirehair does not shine when encoding/decoding latency can be your bottleneck. KCP or even raw UDP would be better for such latency-sensitive cases.
Final thoughts Link to heading
Use KCP when you need a reliable, ordered transport protocol, especially over unrealiable networks. It’s agood alternative to TCP when latency is critical.
Use Wirehair when dealing with high packet loss or when retransmission is impractical. It’s ideal for ensuring data delivery without a reliable feedback channel.
Keep in mind that you’ll might want to combine both.