KCP and Wirehair

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.

Resources Link to heading