Skip to content

Instantly share code, notes, and snippets.

@mpatraw
Created October 22, 2014 18:10
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mpatraw/c950c32f0a29a73f0d35 to your computer and use it in GitHub Desktop.
Save mpatraw/c950c32f0a29a73f0d35 to your computer and use it in GitHub Desktop.
Simple P2P example using ENet.
/*
* p2p.c
*/
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <enet/enet.h>
/* Convenience hack for old ENet support. */
#if !defined(ENET_VERSION_MAJOR) || (ENET_VERSION_MAJOR == 1 && ENET_VERSION_MINOR == 2)
#define OLD_ENET
#endif
#define NAME "p2p test"
#define VERSION "0.1"
#define DEFAULT_PORT 8378 /* TEST on a keypad. */
#define MAX_PEERS 8
enum {
CHUNK_IM_NEW,
CHUNK_NEW_PEERS
};
struct network {
ENetHost *server;
/* The peer we connect to as a client. Can be NULL. */
ENetPeer *server_peer;
/*
* This is a list of peers that were connected to using connect(),
* `server` holds it's own list, where the peers connect to us.
*/
ENetPeer *peers[MAX_PEERS];
size_t npeers;
};
static void die(const char *errstr, ...)
{
va_list args;
va_start(args, errstr);
vfprintf(stderr, errstr, args);
va_end(args);
exit(EXIT_FAILURE);
}
static void *xmalloc(size_t len)
{
void *p = malloc(len);
if(!p) {
die("Out of memory.\n");
}
return p;
}
static void write8(uint8_t *buf, size_t *pos, uint8_t val)
{
*(uint8_t *)(&buf[*pos]) = val;
(*pos) += 1;
}
static void write16(uint8_t *buf, size_t *pos, uint16_t val)
{
*(uint16_t *)(&buf[*pos]) = htons(val);
(*pos) += 2;
}
static void write32(uint8_t *buf, size_t *pos, uint32_t val)
{
*(uint32_t *)(&buf[*pos]) = htonl(val);
(*pos) += 4;
}
static uint8_t read8(const uint8_t *buf, size_t *pos)
{
uint8_t val = *(char *)(&buf[*pos]);
(*pos) += 1;
return val;
}
static uint16_t read16(const uint8_t *buf, size_t *pos)
{
uint16_t val = ntohs(*(uint16_t *)(&buf[*pos]));
(*pos) += 2;
return val;
}
static uint32_t read32(const uint8_t *buf, size_t *pos)
{
uint32_t val = ntohl(*(uint32_t *)(&buf[*pos]));
(*pos) += 4;
return val;
}
static void broadcast_chunk_or_die(ENetHost *host, void *data, size_t len)
{
ENetPacket *p;
p = enet_packet_create(data, len, ENET_PACKET_FLAG_RELIABLE);
if (!p) {
die("Out of memory.\n");
}
enet_host_broadcast(host, 0, p);
}
static void send_chunk_or_die(ENetPeer *peer, void *data, size_t len)
{
ENetPacket *p;
p = enet_packet_create(data, len, ENET_PACKET_FLAG_RELIABLE);
if (!p) {
die("Out of memory.\n");
}
enet_peer_send(peer, 0, p);
}
static int same_address(ENetAddress *a, ENetAddress *b)
{
int same_host = a->host == b->host;
int same_port = a->port == b->port;
return same_host && same_port;
}
static int find_addr_slot(struct network *n, ENetAddress *address)
{
int i;
for (i = 0; i < n->npeers; ++i) {
if (same_address(&n->peers[i]->address, address)) {
return i;
}
}
return -1;
}
static int find_peer_slot(struct network *n, ENetPeer *peer)
{
int i;
for (i = 0; i < n->npeers; ++i) {
if (n->peers[i] == peer) {
return i;
}
}
return -1;
}
static void listen_or_die(struct network *n, short port)
{
ENetAddress address;
if (enet_initialize() != 0) {
die("An error occurred while initializing ENet.\n");
}
atexit(enet_deinitialize);
address.host = ENET_HOST_ANY;
address.port = port;
#ifdef OLD_ENET
n->server = enet_host_create(&address, MAX_PEERS, 0, 0);
#else
n->server = enet_host_create(&address, MAX_PEERS, 2, 0, 0);
#endif
if (!n->server) {
die("Could not create ENet host.\n");
}
}
static void connect_or_die(struct network *n, ENetAddress *address)
{
int slot;
if (n->npeers >= MAX_PEERS) {
die("No available local peers.\n");
}
slot = find_addr_slot(n, address);
if (slot >= 0) {
return;
}
#ifdef OLD_ENET
n->peers[n->npeers] = enet_host_connect(n->server, address, 2);
#else
n->peers[n->npeers] = enet_host_connect(n->server, address, 2, 0);
#endif
if (!n->peers[n->npeers]) {
die("No available remote peers.\n");
}
++n->npeers;
}
static void wait_for_connection_or_die(struct network *n, ENetPeer *peer, int ms)
{
ENetEvent event;
if (enet_host_service(n->server, &event, ms) > 0 &&
event.type == ENET_EVENT_TYPE_CONNECT) {
} else {
enet_peer_reset(peer);
die("Connection timed out.");
}
}
static int tell_peer_im_new(ENetPeer *peer)
{
uint8_t type = CHUNK_IM_NEW;
send_chunk_or_die(peer, &type, 1);
return 0;
}
static int announce_new_peer(struct network *n, ENetPeer *peer)
{
uint8_t *chunk;
size_t pos = 0;
int i;
ENetPacket *packet;
/* ENetAddress is 6 bytes, 4 for host, 2 for port. */
chunk = xmalloc(2 + n->npeers * 6);
/* Tell the new peer about us. */
write8(chunk, &pos, CHUNK_NEW_PEERS);
write8(chunk, &pos, n->npeers - 1);
for (i = 0; i < n->npeers; ++i) {
if (same_address(&n->peers[i]->address, &peer->address)) {
continue;
}
write32(chunk, &pos, n->peers[i]->address.host);
write16(chunk, &pos, n->peers[i]->address.port);
}
packet = enet_packet_create(chunk, pos,
ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(peer, 0, packet);
/* Tell everyone about the new peer. */
pos = 1;
write8(chunk, &pos, 1);
write32(chunk, &pos, peer->address.host);
write16(chunk, &pos, peer->address.port);
for (i = 0; i < n->npeers; ++i) {
if (same_address(&n->peers[i]->address, &peer->address)) {
continue;
}
packet = enet_packet_create(chunk, pos,
ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(n->peers[i], 0, packet);
}
free(chunk);
return 0;
}
static int new_peers(struct network *n, ENetPeer *peer, void *data, size_t len)
{
uint8_t peer_count;
size_t pos = 0;
int i;
ENetAddress addr;
peer_count = read8(data, &pos);
printf("Attempting to connect to %d peers... ", peer_count);
for (i = 0; i < peer_count; ++i) {
addr.host = read32(data, &pos);
addr.port = read16(data, &pos);
connect_or_die(n, &addr);
}
printf("done!\n");
return 0;
}
static int on_peer_connect(struct network *n, ENetEvent *ev)
{
int slot;
slot = find_peer_slot(n, ev->peer);
if (slot < 0) {
return 0;
}
if (n->server_peer == ev->peer) {
tell_peer_im_new(ev->peer);
}
printf("New peer %x:%d\n", ev->peer->address.host, ev->peer->address.port);
return 0;
}
static int on_peer_receive(struct network *n, ENetEvent *ev)
{
uint8_t type;
size_t pos = 0;
void *data;
size_t len;
type = read8(ev->packet->data, &pos);
data = ((uint8_t *)ev->packet->data) + 1;
len = ev->packet->dataLength - 1;
switch (type) {
case CHUNK_IM_NEW:
connect_or_die(n, &ev->peer->address);
return announce_new_peer(n, ev->peer);
case CHUNK_NEW_PEERS:
return new_peers(n, ev->peer, data, len);
default:
break;
}
return 0;
}
static int on_peer_disconnect(struct network *n, ENetEvent *ev)
{
int slot;
int i;
slot = find_peer_slot(n, ev->peer);
if (slot > 0) {
enet_peer_reset(n->peers[slot]);
n->peers[slot] = NULL;
/* Use memmove? */
for (i = slot; i < MAX_PEERS - 1; ++i) {
n->peers[i] = n->peers[i + 1];
}
}
return 0;
}
static int process_peers(struct network *n)
{
ENetEvent event;
int rv = 0;
while (enet_host_service(n->server, &event, 0) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:
rv = on_peer_connect(n, &event);
break;
case ENET_EVENT_TYPE_RECEIVE:
rv = on_peer_receive(n, &event);
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
rv = on_peer_disconnect(n, &event);
break;
default:
break;
}
if (rv < 0) {
break;
}
}
return rv;
}
static void uninit_enet(struct network *n)
{
int i;
for (i = 0; i < MAX_PEERS; ++i) {
if (n->peers[i]) {
enet_peer_disconnect(n->peers[i], 0);
n->peers[i] = NULL;
}
}
enet_host_destroy(n->server);
n->server = NULL;
}
static void usage(void)
{
die("%s %s \nusage: %s [this port] [host] [host port]\n", NAME, VERSION, NAME);
}
int main(int argc, char *argv[])
{
short lport = DEFAULT_PORT;
short rport = DEFAULT_PORT;
const char *host = NULL;
struct network nstate;
ENetAddress address;
memset(&nstate, 0, sizeof(nstate));
argc--;
argv++;
while (argc > 0) {
if (!strcmp(*argv, "--help")) {
argc--;
argv++;
usage();
} else {
break;
}
}
if (argc > 0) {
lport = atoi(argv[0]);
}
if (argc > 1) {
host = argv[1];
}
if (argc > 2) {
rport = atoi(argv[2]);
}
listen_or_die(&nstate, lport);
if (host) {
enet_address_set_host(&address, host);
address.port = rport;
connect_or_die(&nstate, &address);
/* The first slot will contain the new peer. */
nstate.server_peer = nstate.peers[0];
}
for (;;) {
if (process_peers(&nstate) < 0) {
break;
}
}
uninit_enet(&nstate);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment