csci4061/p2-code/server.c
Michael Zhang 041f660ccd
f
2018-01-29 17:28:37 -06:00

266 lines
No EOL
9.5 KiB
C

#include <errno.h>
#include "blather.h"
// Gets a pointer to the client_t struct at the given index. If the
// index is beyond n_clients, the behavior of the function is
// unspecified and may cause a program crash.
client_t *server_get_client(server_t *server, int idx) {
if (idx > server->n_clients) {
// cause a kernel panic
raise(SIGSEGV);
}
return &server->client[idx];
}
// Initializes and starts the server with the given name. A join fifo
// called "server_name.fifo" should be created. Removes any existing
// file of that name prior to creation. Opens the FIFO and stores its
// file descriptor in join_fd._
//
// ADVANCED: create the log file "server_name.log" and write the
// initial empty who_t contents to its beginning. Ensure that the
// log_fd is position for appending to the end of the file. Create the
// POSIX semaphore "/server_name.sem" and initialize it to 1 to
// control access to the who_t portion of the log.
void server_start(server_t *server, char *server_name, int perms) {
// add .fifo to the server name
int len = strlen(server_name);
strncpy(server->server_name, server_name, len + 1);
char server_fifo_name[len + 6];
snprintf(server_fifo_name, len + 6, "%s.fifo", server_name);
// remove existing fifo
remove(server_fifo_name);
if (mkfifo(server_fifo_name, perms) == -1) {
fprintf(stderr, "Error creating '%s': %s\n", server_fifo_name,
strerror(errno));
exit(1);
}
// open the fifo
if ((server->join_fd = open(server_fifo_name, O_RDWR)) == -1) {
fprintf(stderr, "Error opening '%s': %s\n", server_fifo_name,
strerror(errno));
exit(1);
}
}
// Shut down the server. Close the join FIFO and unlink (remove) it so
// that no further clients can join. Send a BL_SHUTDOWN message to all
// clients and proceed to remove all clients in any order.
//
// ADVANCED: Close the log file. Close the log semaphore and unlink
// it.
void server_shutdown(server_t *server) {
// close the fifo
close(server->join_fd);
// get fifo name
int len = strlen(server->server_name);
char server_fifo_name[len + 6];
snprintf(server_fifo_name, len + 6, "%s.fifo", server->server_name);
// remove the fifo
remove(server_fifo_name);
// send BL_SHUTDOWN
mesg_t msg = {.kind = BL_SHUTDOWN};
server_broadcast(server, &msg);
// remove clients
for (int i = 0; i < server->n_clients; ++i) {
server_remove_client(server, i);
}
}
// Adds a client to the server according to the parameter join which
// should have fileds such as name filed in. The client data is
// copied into the client[] array and file descriptors are opened for
// its to-server and to-client FIFOs. Initializes the data_ready field
// for the client to 0. Returns 0 on success and non-zero if the
// server as no space for clients (n_clients == MAXCLIENTS).
int server_add_client(server_t *server, join_t *join) {
if (server->n_clients == MAXCLIENTS)
return 1;
int to_server_fd, to_client_fd;
if ((to_server_fd = open(join->to_server_fname, O_RDWR)) == -1) {
fprintf(stderr, "Could not open connection '%s': %s\n",
join->to_server_fname, strerror(errno));
exit(1);
}
if ((to_client_fd = open(join->to_client_fname, O_RDWR)) == -1) {
fprintf(stderr, "Could not open connection '%s': %s\n",
join->to_server_fname, strerror(errno));
exit(1);
}
client_t client = {.to_server_fd = to_server_fd,
.to_client_fd = to_client_fd,
.data_ready = 0};
strncpy(client.name, join->name, MAXPATH);
strncpy(client.to_server_fname, join->to_server_fname, MAXPATH);
strncpy(client.to_client_fname, join->to_client_fname, MAXPATH);
server->client[server->n_clients++] = client;
return 0;
}
// Remove the given client likely due to its having departed or
// disconnected. Close fifos associated with the client and remove
// them. Shift the remaining clients to lower indices of the client[]
// array and decrease n_clients.
int server_remove_client(server_t *server, int idx) {
// close fds
client_t *client = server_get_client(server, idx);
close(client->to_server_fd);
close(client->to_client_fd);
for (int i = idx; i < server->n_clients - 1; ++i)
server->client[i] = server->client[i + 1];
server->n_clients--;
return 0;
}
// Send the given message to all clients connected to the server by
// writing it to the file descriptors associated with them.
//
// ADVANCED: Log the broadcast message unless it is a PING which
// should not be written to the log.
int server_broadcast(server_t *server, mesg_t *mesg) {
client_t *client;
for (int i = 0; i < server->n_clients; ++i) {
client = server_get_client(server, i);
if ((write(client->to_client_fd, mesg, sizeof(mesg_t))) < 0)
return 1;
}
return 0;
}
// Checks all sources of data for the server to determine if any are
// ready for reading. Sets the servers join_ready flag and the
// data_ready flags of each of client if data is ready for them.
// Makes use of the select() system call to efficiently determine
// which sources are ready.
void server_check_sources(server_t *server) {
fd_set readfds;
FD_ZERO(&readfds);
int maxfd = server->join_fd;
FD_SET(server->join_fd, &readfds);
client_t *client;
for (int i = 0; i < server->n_clients; ++i) {
client = server_get_client(server, i);
client->data_ready = 0;
FD_SET(client->to_server_fd, &readfds);
if (client->to_server_fd > maxfd)
maxfd = client->to_server_fd;
}
if (select(maxfd + 1, &readfds, NULL, NULL, NULL)) {
if (FD_ISSET(server->join_fd, &readfds))
server->join_ready = 1;
for (int i = 0; i < server->n_clients; ++i) {
client = server_get_client(server, i);
if (FD_ISSET(client->to_server_fd, &readfds))
client->data_ready = 1;
}
}
}
// Return the join_ready flag from the server which indicates whether
// a call to server_handle_join() is safe.
int server_join_ready(server_t *server) { return server->join_ready; }
// Call this function only if server_join_ready() returns true. Read a
// join request and add the new client to the server. After finishing,
// set the servers join_ready flag to 0.
int server_handle_join(server_t *server) {
// read join request
join_t request;
if (read(server->join_fd, &request, sizeof(join_t)) < 0)
return -1;
printf("name: %s\n", request.name);
// add client
server_add_client(server, &request);
server->join_ready = 0;
return 0;
}
// Return the data_ready field of the given client which indicates
// whether the client has data ready to be read from it.
int server_client_ready(server_t *server, int idx) {
client_t *client = server_get_client(server, idx);
return client->data_ready;
}
// Process a message from the specified client. This function should
// only be called if server_client_ready() returns true. Read a
// message from to_server_fd and analyze the message kind. Departure
// and Message types should be broadcast to all other clients. Ping
// responses should only change the last_contact_time below. Behavior
// for other message types is not specified. Clear the client's
// data_ready flag so it has value 0.
//
// ADVANCED: Update the last_contact_time of the client to the current
// server time_sec.
int server_handle_client(server_t *server, int idx) {
client_t *client = server_get_client(server, idx);
mesg_t payload;
read(client->to_server_fd, &payload, sizeof(mesg_t));
printf("received message: (%d) <%s> '%s'\n", payload.kind, payload.name,
payload.body);
switch (payload.kind) {
case BL_MESG:
case BL_JOINED:
case BL_DEPARTED:
server_broadcast(server, &payload);
break;
case BL_SHUTDOWN:
case BL_DISCONNECTED:
// shouldn't happen
break;
case BL_PING:
// TODO
break;
}
client->data_ready = 0;
return 0;
}
// ADVANCED: Increment the time for the server
void server_tick(server_t *server);
// ADVANCED: Ping all clients in the server by broadcasting a ping.
void server_ping_clients(server_t *server);
// ADVANCED: Check all clients to see if they have contacted the
// server recently. Any client with a last_contact_time field equal to
// or greater than the parameter disconnect_secs should be
// removed. Broadcast that the client was disconnected to remaining
// clients. Process clients from lowest to highest and take care of
// loop indexing as clients may be removed during the loop
// necessitating index adjustments.
void server_remove_disconnected(server_t *server, int disconnect_secs);
// ADVANCED: Write the current set of clients logged into the server
// to the BEGINNING the log_fd. Ensure that the write is protected by
// locking the semaphore associated with the log file. Since it may
// take some time to complete this operation (acquire semaphore then
// write) it should likely be done in its own thread to preven the
// main server operations from stalling. For threaded I/O, consider
// using the pwrite() function to write to a specific location in an
// open file descriptor which will not alter the position of log_fd so
// that appends continue to write to the end of the file.
void server_write_who(server_t *server);
// ADVANCED: Write the given message to the end of log file associated
// with the server.
void server_log_message(server_t *server, mesg_t *mesg);