-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhcq_server.c
More file actions
executable file
·396 lines (359 loc) · 13.2 KB
/
hcq_server.c
File metadata and controls
executable file
·396 lines (359 loc) · 13.2 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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "hcq.h"
#include "hcq_server.h"
#ifndef PORT
#define PORT 56535
#endif
#define MAX_BACKLOG 5
#define BUF_SIZE 30
/* Use global variables so we can have exactly one TA list, one student list, one course list,
* one clients linked list, and one all_fds.
*/
Client *clients = NULL;
Ta *ta_list = NULL;
Student *stu_list = NULL;
Course *courses;
int num_courses = 0;
fd_set all_fds;
/* Instantiate the course list and the client linked list.
*/
void configure_lists() {
// Configure the list of available courses for the help centre queue.
num_courses = config_course_list(&courses, NULL);
// Instantiate the clients linked list.
clients = malloc(sizeof(Client));
if (clients == NULL) {
error_message("malloc for head of clients linked list");
}
clients->sock_fd = -1;
clients->name = NULL;
clients->role = NULL;
clients->course = NULL;
clients->next = NULL;
(clients->buf)[0] = '\0';
clients->inbuf = 0;
clients->room = sizeof(clients->buf); // How many bytes remaining in buffer?
clients->after = clients->buf; // Pointer to position after the data in buf.
}
/* Print the perror message for system calls and terminates the program.
*/
void error_message(char *msg) {
perror(msg);
exit(1);
}
/* Write the message str to the client at client_fd and close the client if the writing was unsuccessful.
*/
void write_and_clean(int client_fd, char *str) {
int writing = write(client_fd, str, strlen(str));
if (writing != strlen(str)) {
Client *client = clients;
while (client != NULL) {
if (client->sock_fd == client_fd) {
cleanup(client);
break;
}
client = client->next;
}
}
}
/* Free the memory, reset the client so each field has its initial values when instantiation,
* and close the client from connecting to this server.
*/
void cleanup(Client *client) {
if (client->role != NULL) {
if (strcmp(client->role, "S") == 0) {
give_up_waiting(&stu_list, client->name);
} else if (strcmp(client->role, "T") == 0) {
remove_ta(&ta_list, client->name);
}
free(client->role);
}
if (client->name != NULL) {
free(client->name);
}
if (client->course != NULL) {
free(client->course);
}
FD_CLR(client->sock_fd, &all_fds);
close(client->sock_fd);
client->sock_fd = -1;
client->name = NULL;
client->role = NULL;
client->course = NULL;
(client->buf)[0] = '\0';
client->inbuf = 0;
client->room = sizeof(client->buf); // How many bytes remaining in buffer?
client->after = client->buf; // Pointer to position after the data in buf.
}
/* Search the first n characters of buf for a network newline (\r\n).
* Return one plus the index of the '\n' of the first network newline,
* or -1 if no network newline is found.
* Definitely do not use strchr or other string functions to search here. (Why not?)
*/
int find_network_newline(const char *buf, int n) {
for (int i = 0; i < n-1; i++) {
if (buf[i] == '\r') {
if (buf[i+1] == '\n') {
return i+2;
}
}
}
return -1;
}
/* Accept a connection. Note that a new file descriptor is created for
* communication with the client. The initial socket descriptor is used
* to accept connections, but the new socket is used to communicate.
* Return the new client's file descriptor or -1 on error.
*/
int accept_connection(int fd) {
int client_fd = accept(fd, NULL, NULL);
if (client_fd < 0) {
perror("server: accept");
close(fd);
exit(1);
}
Client *prev = NULL;
Client *client = clients;
while (client != NULL) {
if (client->sock_fd == -1) {
client->sock_fd = client_fd;
return client_fd;
}
prev = client;
client = client->next;
}
client = malloc(sizeof(Client));
if (client == NULL) {
error_message("malloc for connecting a new client");
}
client->sock_fd = client_fd;
client->name = NULL;
client->role = NULL;
client->course = NULL;
client->next = NULL;
(client->buf)[0] = '\0';
client->inbuf = 0;
client->room = sizeof(client->buf); // How many bytes remaining in buffer?
client->after = client->buf; // Pointer to position after the data in buf.
if (prev != NULL) {
prev->next = client;
} else {
clients = client;
}
return client_fd;
}
/* Read a message from the client and execute a client's command according to whether the client is a Student of a TA.
*/
void read_from(Client *client) {
int client_fd = client->sock_fd;
char *buf = client->buf;
int num_read;
if ((num_read = read(client_fd, client->after, client->room)) > 0) {
// Step 1: update inbuf (how many bytes were just added?)
client->inbuf += num_read;
int where;
// Step 2: the loop condition below calls find_network_newline
// to determine if a full line has been read from the client.
// Your next task should be to implement find_network_newline
// (found at the bottom of this file).
//
// Note: we use a loop here because a single read might result in
// more than one full line.
while ((where = find_network_newline(buf, client->inbuf)) > 0) {
// Step 3: Okay, we have a full line.
// Output the full line, not including the "\r\n",
// using print statement below.
// Be sure to put a '\0' in the correct place first;
// otherwise you'll get junk in the output.
buf[where-2] = '\0';
if (client->name == NULL) {
// read this client's name
client->name = malloc(sizeof(char)*BUF_SIZE);
if (client->name == NULL) {
error_message("malloc for client name");
}
(client->name)[0] = '\0';
strcpy(client->name, buf);
char *str = "Are you a TA or a Student (enter T or S)?\r\n";
write_and_clean(client_fd, str);
} else if (client->role == NULL) {
// read this client's role
client->role = malloc(sizeof(char)*BUF_SIZE);
if (client->role == NULL) {
error_message("malloc for client role");
}
(client->role)[0] = '\0';
strcpy(client->role, buf);
if (strcmp(client->role, "S") == 0) {
char *str = "Valid courses: CSC108, CSC148, CSC209\r\nWhich course are you asking about?\r\n";
write_and_clean(client_fd, str);
} else if (strcmp(client->role, "T") == 0) {
char *str = "Valid commands for TA:\r\n\t\tstats\r\n\t\tnext\r\n\t\t(or use Ctrl-C to leave)\r\n";
add_ta(&ta_list, client->name);
write_and_clean(client_fd, str);
} else {
char *str ="Invalid role (enter T or S)\r\n";
write_and_clean(client_fd, str);
free(client->role);
client->role = NULL;
}
} else if (strcmp(client->role, "S") == 0 && client->course == NULL) {
// read in this Student's course
if (strcmp(buf, "CSC108") != 0 && strcmp(buf, "CSC148") != 0 && strcmp(buf, "CSC209") != 0) {
char *str = "This is not a valid course. Good-bye.\r\n";
write_and_clean(client_fd, str);
cleanup(client);
return;
}
client->course = malloc(sizeof(char)*BUF_SIZE);
if (client->course == NULL) {
error_message("malloc for client course");
}
(client->course)[0] = '\0';
strcpy(client->course, buf);
if (add_student(&stu_list, client->name, client->course, courses, num_courses)) {
char *err_str = "You are already in the queue and cannot be added again for any course. Good-bye.\r\n";
write_and_clean(client_fd, err_str);
cleanup(client);
return;
}
char *str = "You have been entered into the queue. While you wait, you can use the command stats to see which TAs are currently serving students.\r\n";
write_and_clean(client_fd, str);
} else if (strcmp(client->role, "T") == 0) {
// execute valid commands for a TA
if (strcmp(buf, "stats") == 0) {
char *str = print_full_queue(stu_list);
write_and_clean(client_fd, str);
free(str);
} else if (strcmp(buf, "next") == 0) {
next_overall(client->name, &ta_list, &stu_list);
Ta *curr_ta = find_ta(ta_list, client->name);
Student *stu = curr_ta->current_student;
if (stu != NULL) {
Client *my_student_client = clients;
while (my_student_client != NULL) {
if (my_student_client->name != NULL && strcmp(my_student_client->name, stu->name) == 0 && strcmp(my_student_client->role, "S") == 0) {
char *str = "Your turn to see the TA.\r\nWe are disconnecting you from the server now. Press Ctrl-C to close nc\r\n";
write_and_clean(my_student_client->sock_fd, str);
cleanup(my_student_client);
break;
}
my_student_client = my_student_client->next;
}
}
} else {
char *str = "Incorrect syntax\r\n";
write_and_clean(client_fd, str);
}
} else if (strcmp(client->role, "S") == 0) {
// execute valid commands for a Student
if (strcmp(buf, "stats") == 0) {
char *str = print_currently_serving(ta_list);
write_and_clean(client_fd, str);
free(str);
} else {
char *str = "Incorrect syntax\r\n";
write_and_clean(client_fd, str);
}
}
// Step 4: update inbuf and remove the full line from the buffer
// There might be stuff after the line, so don't just do inbuf = 0.
client->inbuf = client->inbuf - where;
// You want to move the stuff after the full line to the beginning
// of the buffer. A loop can do it, or you can use memmove.
// memmove(destination, source, number_of_bytes)
memmove(buf, &(buf[where]), client->inbuf);
}
// Step 5: update after and room, in preparation for the next read.
client->room = BUF_SIZE - client->inbuf;
client->after = &(buf[client->inbuf]);
// Step 6: If the input is longer than BUF_SIZE, we close this client.
if (client->room == 0) {
cleanup(client);
}
} else if (num_read == -1) {
error_message("read in read_from");
} else if (num_read == 0) {
// The client has closed and we are not reading anything.
cleanup(client);
}
}
/* Runs the server for the help centre queue and
*/
int main(void) {
// Instantiate the global variables
configure_lists();
// Create the socket FD.
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("server: socket");
exit(1);
}
// Set information about the port (and IP) we want to be connected to.
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = INADDR_ANY;
// This should always be zero. On some systems, it won't error if you
// forget, but on others, you'll get mysterious errors. So zero it.
memset(&server.sin_zero, 0, 8);
// Setup so the port will be released as soon as the server process terminates.
int on = 1;
int status = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on));
if(status == -1) {
perror("setsockopt -- REUSEADDR");
}
// Bind the selected port to the socket.
if (bind(sock_fd, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("server: bind");
close(sock_fd);
exit(1);
}
// Announce willingness to accept connections on this socket.
if (listen(sock_fd, MAX_BACKLOG) < 0) {
perror("server: listen");
close(sock_fd);
exit(1);
}
// The client accept - message accept loop. First, we prepare to listen to multiple
// file descriptors by initializing a set of file descriptors.
int max_fd = sock_fd;
FD_ZERO(&all_fds);
FD_SET(sock_fd, &all_fds);
while (1) {
// select updates the fd_set it receives, so we always use a copy and retain the original.
fd_set listen_fds = all_fds;
int nready = select(max_fd + 1, &listen_fds, NULL, NULL, NULL);
if (nready == -1) {
perror("server: select");
exit(1);
}
// Is it the original socket? Create a new connection ...
if (FD_ISSET(sock_fd, &listen_fds)) {
int client_fd = accept_connection(sock_fd);
if (client_fd > max_fd) {
max_fd = client_fd;
}
FD_SET(client_fd, &all_fds);
char *str = "Welcome to the Help Centre, what is your name?\r\n";
write_and_clean(client_fd, str);
}
// Next, check the clients.
// NOTE: We could do some tricks with nready to terminate this loop early.
Client *client = clients;
while(client != NULL) {
if (client->sock_fd > -1 && FD_ISSET(client->sock_fd, &listen_fds)) {
read_from(client);
}
client = client->next;
}
}
// Should never get here.
return 1;
}