antizona

antizona - Minimalist 3D software-rendered multiplayer engine for GTA: San Andreas in pure C99
Log | Files | Refs | README

server.c (6378B)


      1 #include <sys/types.h>
      2 #include <sys/socket.h>
      3 #include <netinet/in.h>
      4 #include <arpa/inet.h>
      5 
      6 #include <err.h>
      7 #include <fcntl.h>
      8 #include <poll.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <time.h>
     13 #include <unistd.h>
     14 
     15 #include "net.h"
     16 
     17 struct server_slot {
     18 	struct sockaddr_in	addr;
     19 	socklen_t		addr_len;
     20 	time_t			last_seen;
     21 	char			name[24];
     22 	uint16_t		skin;
     23 	float			x, y, z;
     24 	float			yaw;
     25 	int			active;
     26 };
     27 
     28 static struct server_slot	clients[MAX_PLAYERS];
     29 static int			server_fd;
     30 static int			max_clients = MAX_PLAYERS;
     31 
     32 static int
     33 find_slot_by_addr(const struct sockaddr_in *addr)
     34 {
     35 	int i;
     36 
     37 	for (i = 0; i < max_clients; i++) {
     38 		if (clients[i].active &&
     39 		    clients[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr &&
     40 		    clients[i].addr.sin_port == addr->sin_port) {
     41 			return (i);
     42 		}
     43 	}
     44 	return (-1);
     45 }
     46 
     47 static int
     48 find_free_slot(void)
     49 {
     50 	int i;
     51 
     52 	for (i = 0; i < max_clients; i++) {
     53 		if (!clients[i].active) {
     54 			return (i);
     55 		}
     56 	}
     57 	return (-1);
     58 }
     59 
     60 static void
     61 broadcast_world_state(void)
     62 {
     63 	struct pkt_world_state pkt;
     64 	int i, count;
     65 
     66 	memset(&pkt, 0, sizeof(pkt));
     67 	pkt.hdr.magic = NET_MAGIC;
     68 	pkt.hdr.type = PKT_WORLD_STATE;
     69 
     70 	count = 0;
     71 	for (i = 0; i < max_clients; i++) {
     72 		if (clients[i].active) {
     73 			pkt.players[count].id = (uint8_t)i;
     74 			pkt.players[count].skin = clients[i].skin;
     75 			pkt.players[count].x = clients[i].x;
     76 			pkt.players[count].y = clients[i].y;
     77 			pkt.players[count].z = clients[i].z;
     78 			pkt.players[count].yaw = clients[i].yaw;
     79 			count++;
     80 		}
     81 	}
     82 	pkt.player_count = (uint8_t)count;
     83 
     84 	for (i = 0; i < max_clients; i++) {
     85 		if (clients[i].active) {
     86 			sendto(server_fd, &pkt,
     87 			    sizeof(struct pkt_header) + 1 + count * sizeof(struct player_state),
     88 			    0, (struct sockaddr *)&clients[i].addr, clients[i].addr_len);
     89 		}
     90 	}
     91 }
     92 
     93 static void
     94 handle_packet(const uint8_t *buf, ssize_t len, const struct sockaddr_in *src_addr, socklen_t addr_len)
     95 {
     96 	const struct pkt_header *hdr;
     97 	struct pkt_join_ack ack;
     98 	int slot, i;
     99 	time_t now;
    100 
    101 	if (len < (ssize_t)sizeof(struct pkt_header)) {
    102 		return;
    103 	}
    104 	hdr = (const struct pkt_header *)buf;
    105 	if (hdr->magic != NET_MAGIC) {
    106 		return;
    107 	}
    108 
    109 	time(&now);
    110 	slot = find_slot_by_addr(src_addr);
    111 
    112 	if (hdr->type == PKT_JOIN) {
    113 		const struct pkt_join *pj;
    114 
    115 		if (len < (ssize_t)sizeof(struct pkt_join)) {
    116 			return;
    117 		}
    118 		pj = (const struct pkt_join *)buf;
    119 
    120 		if (slot < 0) {
    121 			slot = find_free_slot();
    122 			if (slot < 0) {
    123 				return;
    124 			}
    125 		}
    126 
    127 		clients[slot].addr = *src_addr;
    128 		clients[slot].addr_len = addr_len;
    129 		clients[slot].last_seen = now;
    130 		clients[slot].skin = pj->skin;
    131 		strncpy(clients[slot].name, pj->name, sizeof(clients[slot].name) - 1);
    132 		clients[slot].name[sizeof(clients[slot].name) - 1] = '\0';
    133 		clients[slot].active = 1;
    134 
    135 		memset(&ack, 0, sizeof(ack));
    136 		ack.hdr.magic = NET_MAGIC;
    137 		ack.hdr.type = PKT_JOIN_ACK;
    138 		ack.hdr.player_id = (uint8_t)slot;
    139 		ack.your_id = (uint8_t)slot;
    140 		ack.game_time = 12.0f;
    141 		ack.game_weather = 0;
    142 
    143 		sendto(server_fd, &ack, sizeof(ack), 0,
    144 		    (struct sockaddr *)src_addr, addr_len);
    145 		printf("server: player joined [id=%d, nick=%s]\n", slot, clients[slot].name);
    146 		return;
    147 	}
    148 
    149 	if (slot < 0) {
    150 		return;
    151 	}
    152 	clients[slot].last_seen = now;
    153 
    154 	if (hdr->type == PKT_SYNC) {
    155 		const struct pkt_sync *ps;
    156 
    157 		if (len < (ssize_t)sizeof(struct pkt_sync)) {
    158 			return;
    159 		}
    160 		ps = (const struct pkt_sync *)buf;
    161 		clients[slot].x = ps->x;
    162 		clients[slot].y = ps->y;
    163 		clients[slot].z = ps->z;
    164 		clients[slot].yaw = ps->yaw;
    165 		clients[slot].skin = ps->skin;
    166 	} else if (hdr->type == PKT_CHAT) {
    167 		const struct pkt_chat *pc;
    168 		struct pkt_chat out_chat;
    169 
    170 		if (len < (ssize_t)sizeof(struct pkt_chat)) {
    171 			return;
    172 		}
    173 		pc = (const struct pkt_chat *)buf;
    174 		printf("[%s]: %s\n", clients[slot].name, pc->msg);
    175 
    176 		memset(&out_chat, 0, sizeof(out_chat));
    177 		out_chat.hdr.magic = NET_MAGIC;
    178 		out_chat.hdr.type = PKT_CHAT;
    179 		out_chat.hdr.player_id = (uint8_t)slot;
    180 		strncpy(out_chat.name, clients[slot].name, sizeof(out_chat.name) - 1);
    181 		strncpy(out_chat.msg, pc->msg, sizeof(out_chat.msg) - 1);
    182 
    183 		for (i = 0; i < max_clients; i++) {
    184 			if (clients[i].active) {
    185 				sendto(server_fd, &out_chat, sizeof(out_chat), 0,
    186 				    (struct sockaddr *)&clients[i].addr, clients[i].addr_len);
    187 			}
    188 		}
    189 	} else if (hdr->type == PKT_LEAVE) {
    190 		clients[slot].active = 0;
    191 		printf("server: player left [id=%d, nick=%s]\n", slot, clients[slot].name);
    192 	}
    193 }
    194 
    195 int
    196 main(int argc, char *argv[])
    197 {
    198 	struct sockaddr_in srv_addr, cli_addr;
    199 	struct pollfd pfd;
    200 	uint8_t buf[2048];
    201 	ssize_t n;
    202 	socklen_t addr_len;
    203 	struct timespec t_last, t_now;
    204 	double elapsed;
    205 	int port, i;
    206 	time_t now;
    207 
    208 	if (argc < 3) {
    209 		fprintf(stderr, "usage: %s <port> <max_players>\n", argv[0]);
    210 		return (1);
    211 	}
    212 
    213 	port = atoi(argv[1]);
    214 	max_clients = atoi(argv[2]);
    215 
    216 	if (port <= 0 || port > 65535) {
    217 		errx(1, "invalid port: %s", argv[1]);
    218 	}
    219 	if (max_clients <= 0 || max_clients > MAX_PLAYERS) {
    220 		errx(1, "invalid max_players (1-%d): %s", MAX_PLAYERS, argv[2]);
    221 	}
    222 
    223 	memset(clients, 0, sizeof(clients));
    224 
    225 	server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    226 	if (server_fd < 0) {
    227 		err(1, "socket");
    228 	}
    229 
    230 	fcntl(server_fd, F_SETFL, O_NONBLOCK);
    231 
    232 	memset(&srv_addr, 0, sizeof(srv_addr));
    233 	srv_addr.sin_family = AF_INET;
    234 	srv_addr.sin_addr.s_addr = INADDR_ANY;
    235 	srv_addr.sin_port = htons(port);
    236 
    237 	if (bind(server_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
    238 		err(1, "bind");
    239 	}
    240 
    241 	printf("antizona server listening on port %d (max players: %d)...\n", port, max_clients);
    242 
    243 	pfd.fd = server_fd;
    244 	pfd.events = POLLIN;
    245 
    246 	clock_gettime(CLOCK_MONOTONIC, &t_last);
    247 
    248 	while (1) {
    249 		if (poll(&pfd, 1, 5) > 0) {
    250 			if (pfd.revents & POLLIN) {
    251 				while (1) {
    252 					addr_len = sizeof(cli_addr);
    253 					n = recvfrom(server_fd, buf, sizeof(buf), 0,
    254 					    (struct sockaddr *)&cli_addr, &addr_len);
    255 					if (n < 0) {
    256 						break;
    257 					}
    258 					handle_packet(buf, n, &cli_addr, addr_len);
    259 				}
    260 			}
    261 		}
    262 
    263 		clock_gettime(CLOCK_MONOTONIC, &t_now);
    264 		elapsed = (t_now.tv_sec - t_last.tv_sec) +
    265 		    (t_now.tv_nsec - t_last.tv_nsec) * 1e-9;
    266 
    267 		if (elapsed >= 0.033) {
    268 			time(&now);
    269 			for (i = 0; i < max_clients; i++) {
    270 				if (clients[i].active && (now - clients[i].last_seen) > 5) {
    271 					clients[i].active = 0;
    272 					printf("server: timeout player [id=%d, nick=%s]\n", i, clients[i].name);
    273 				}
    274 			}
    275 			broadcast_world_state();
    276 			t_last = t_now;
    277 		}
    278 	}
    279 
    280 	close(server_fd);
    281 	return (0);
    282 }