Created
October 22, 2014 18:10
Simple P2P example using ENet.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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