-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.c
More file actions
349 lines (298 loc) · 10.8 KB
/
Copy pathserver.c
File metadata and controls
349 lines (298 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#define MAX_CLIENTS 2
#define BUFFER_SIZE 4096
#define RECONNECT_TIMEOUT 60
#define WAITING_OPPONENT_SIGNAL "WAIT\n"
#define NO_WAITING_OPPONENT_SIGNAL "NOWA\n"
#define START_GAME_SIGNAL "STRT\n"
#define RECONNECT_SIGNAL "RECN\n"
#define RESTART_SIGNAL "RSTR\n"
#define ROOM_FULL_SIGNAL "FULL\n"
#define MAX_CONNECTION_ATTEMPTS 512
#define RESTART_GAME_SIGNAL "RSTG\n"
typedef struct {
int conn;
char ip[INET_ADDRSTRLEN];
int port;
char *id;
char color[8];
time_t last_active;
} Client;
typedef struct {
ssize_t size;
char *data;
} BackupStorage;
int server_running = 1;
int server_socket;
Client clients[MAX_CLIENTS];
BackupStorage *progress = NULL;
int client_count = 0;
int connection_attempts = 0; // stress protection
pthread_mutex_t client_lock;
void cleanup();
void signal_handler(int signum) {
server_running = 0;
cleanup();
exit(0);
}
// Read from client until newline character
ssize_t read_until_nl(int socket, char *buffer, size_t count) {
size_t total_bytes_read = 0;
size_t bytes_read;
if (count <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
while (1) {
char curr_char;
bytes_read = read(socket, &curr_char, 1);
if (bytes_read < 0) {
if (errno == EINTR) {
continue;
} else {
return -1;
}
} else if (bytes_read == 0) {
if (total_bytes_read == 0) return 0;
return total_bytes_read;
}
if (total_bytes_read < count - 1) {
total_bytes_read++;
*buffer++ = curr_char;
}
if (curr_char == '\n') break;
}
return total_bytes_read;
}
int find_client_by_id(const char *id) {
for (int i = 0; i < client_count; i++) {
if (strcmp(clients[i].id, id) == 0) {
return i;
}
}
return -1;
}
void cleanup() {
for (int i = 0; i < client_count; i++) {
close(clients[i].conn);
free(clients[i].id);
}
if (progress != NULL) {
if (progress->data != NULL) free(progress->data);
free(progress);
}
pthread_mutex_destroy(&client_lock);
close(server_socket);
printf("Server cleaned up and shutting down.\n");
}
void *handle_client(void *arg) {
int conn = *(int *)arg;
free(arg); // Free the dynamically allocated memory after extracting conn
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
getpeername(conn, (struct sockaddr *)&addr, &addr_len);
char *client_ip = inet_ntoa(addr.sin_addr);
int client_port = ntohs(addr.sin_port);
char id[BUFFER_SIZE];
read_until_nl(conn, id, sizeof(id)); // read the client id
id[strlen(id) - 1] = '\0'; // remove the newline character
printf("Connected to %s:%d, id: %s\n", client_ip, client_port, id);
// Check if the client is connected before
pthread_mutex_lock(&client_lock);
int client_index = find_client_by_id(id);
if (client_index == -1) { // New connection
client_index = client_count++;
strcpy(clients[client_index].ip, client_ip);
clients[client_index].port = client_port;
clients[client_index].conn = conn;
clients[client_index].id = strdup(id);
// Notify the client if there is another client waiting
if (client_count == 1) send(conn, WAITING_OPPONENT_SIGNAL, 5, 0);
else if (client_count == 2) {
// or start the game if there are two clients connected
send(clients[1].conn, NO_WAITING_OPPONENT_SIGNAL, 5, 0);
send(clients[0].conn, START_GAME_SIGNAL, 5, 0);
send(clients[1].conn, START_GAME_SIGNAL, 5, 0);
const char *color[2];
if (rand() % 2 == 0) {
color[0] = "WHITE\n";
color[1] = "BLACK\n";
} else {
color[0] = "BLACK\n";
color[1] = "WHITE\n";
}
// Send the assigned color to the client
printf("Assigning and sending color to clients\n");
printf("Clients %s: %s\n", clients[0].id, color[0]);
printf("Clients %s: %s\n", clients[1].id, color[1]);
send(clients[0].conn, color[0], strlen(color[0]), 0);
send(clients[1].conn, color[1], strlen(color[1]), 0);
// Store the color for future reconnections
strcpy(clients[0].color, color[0]);
strcpy(clients[1].color, color[1]);
// send oppoenent's id
char client0_id_signal[BUFFER_SIZE];
char client1_id_signal[BUFFER_SIZE];
snprintf(client0_id_signal, sizeof(client0_id_signal), "%s\n", clients[0].id);
snprintf(client1_id_signal, sizeof(client1_id_signal), "%s\n", clients[1].id);
send(clients[0].conn, client1_id_signal, strlen(client1_id_signal), 0);
send(clients[1].conn, client0_id_signal, strlen(client0_id_signal), 0);
} else if (client_count > 2) {
printf("Client %s: rejected. Game is full.\n", id);
send(conn, ROOM_FULL_SIGNAL, 5, 0);
close(conn);
free(clients[client_index].id);
client_count = 2;
}
} else { // Reconnection
clients[client_index].conn = conn;
strcpy(clients[client_index].ip, client_ip);
clients[client_index].port = client_port;
clients[client_index].conn = conn;
printf("Client %s: reconnected.\n", id);
// Notify the client that it is reconnected
send(conn, RECONNECT_SIGNAL, 5, 0);
send(conn, RESTART_SIGNAL, 5, 0);
// Resend the color on reconnection
const char *color = clients[client_index].color;
send(conn, color, strlen(color), 0);
// Resend the opponent's id
char opponent_id_signal[BUFFER_SIZE];
snprintf(opponent_id_signal, sizeof(opponent_id_signal), "%s\n", clients[1 - client_index].id);
send(conn, opponent_id_signal, strlen(opponent_id_signal), 0);
// Resend the progress to restore the game
if (progress != NULL) {
send(conn, progress->data, progress->size, 0);
}
}
clients[client_index].last_active = time(NULL);
pthread_mutex_unlock(&client_lock);
while (1) {
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(conn, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) {
printf("Client %s disconnected.\n", id);
break;
}
printf("Received %ld bytes from %s\n", bytes_received, id);
printf("Data: %s\n", buffer);
// Store the progress for backup
if (progress->data != NULL) {
free(progress->data);
}
progress->size = bytes_received;
progress->data = (char *)malloc(progress->size);
memcpy(progress->data, buffer, progress->size);
// reset last active time of client
pthread_mutex_lock(&client_lock);
clients[client_index].last_active = time(NULL);
pthread_mutex_unlock(&client_lock);
if (strncmp(buffer, RESTART_GAME_SIGNAL, 5) == 0) {
printf("Restarting the game...\n");
// TODO
for (int i = 0; i < client_count; i++) {
send(clients[i].conn, "\0", 1, 0);
}
} else {
printf("Move received from %s\n", id);
printf("Forwarding the move to the other client...\n");
pthread_mutex_lock(&client_lock);
for (int i = 0; i < client_count; i++) {
if (clients[i].conn != conn) {
send(clients[i].conn, progress->data, progress->size, 0);
}
}
pthread_mutex_unlock(&client_lock);
}
}
// Client disconnected, reserve the spot and allow reconnection
printf("Disconnected from %s\n", id);
pthread_mutex_lock(&client_lock);
clients[client_index].conn = -1;
pthread_mutex_unlock(&client_lock);
close(conn);
return NULL;
}
int main(int argc, char *argv[]) {
const char *server_ip = argv[1];
const char *server_port_str = argv[2];
const int server_port = atoi(server_port_str);
srand(time(NULL));
progress = (BackupStorage *)malloc(sizeof(BackupStorage));
pthread_mutex_init(&client_lock, NULL);
signal(SIGINT, signal_handler);
// Create the server socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Enable address reuse
int opt = 1;
if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// Setup the server address structure
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(server_ip); // Explicit loopback address
server_addr.sin_port = htons(server_port);
// Bind the socket to the server address and port
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// listen for incoming connections
if (listen(server_socket, MAX_CLIENTS) < 0) {
perror("Listen failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server started. Waiting for clients to connect on %s:%d...\n", server_ip, server_port);
// Accept clients
while (server_running) {
connection_attempts++;
int *conn = (int *)malloc(sizeof(int));
if ((*conn = accept(server_socket, NULL, NULL)) < 0) {
perror("Accept failed");
free(conn);
continue;
}
pthread_t thread;
if (connection_attempts < MAX_CONNECTION_ATTEMPTS) {
pthread_create(&thread, NULL, handle_client, conn); // Pass the connection pointer
pthread_detach(thread);
}
connection_attempts--;
}
printf("Both clients connected. Starting the game...\n");
// Keep the server running
while (server_running) {
sleep(1);
time_t current_time = time(NULL);
// check if disconnected clients is timed out
pthread_mutex_lock(&client_lock);
for (int i = 0; i < client_count; i++) {
if (clients[i].conn == -1 && difftime(current_time, clients[i].last_active) > RECONNECT_TIMEOUT) {
printf("Client %s did not reconnect in time. Removing from the game.\n", clients[i].id);
free(clients[i].id);
clients[i] = clients[--client_count];
}
}
pthread_mutex_unlock(&client_lock);
}
// Clean up when shutting down
cleanup();
return 0;
}