-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.c
250 lines (219 loc) · 7.72 KB
/
server.c
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <string.h>
#include "server.h"
#include "chat.h"
/**
* Stack of currently connected clients. There are no gaps in this array.
*/
static int *client_descriptors;
/**
* Number of client currently connected.
*/
static int number_connections;
int create_server_socket();
void handle_connection(int server_descriptor);
int accept_connection(int sd);
void handle_disconnect(int connection_index, char *message);
void send_to_clients(char *content);
void close_server(int server_descriptor);
/**
* Prints out debugging messages and exit the program.
* @param details
*/
void handle_server_failure(char *details){
printf("Failed to start the server: %s\n", details);
printf("Error: %d: %s\n", errno, strerror(errno));
exit(1);
}
char *force_read_message(int descriptor){
int bytes_read = 0;
char *message = calloc(MESSAGE_SIZE, sizeof(char));
while(bytes_read != MESSAGE_SIZE){
char temp[MESSAGE_SIZE] = {'\0'};
bytes_read += read(descriptor, temp, MESSAGE_SIZE);
strcat(message, temp);
}
return message;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-noreturn"
/**
* Server main function. Will listen for incoming connections and handle communication between them
*
* This should be called as a separate thread.
* @param arg Not used
* @return Not used
*/
void *startServer(void *arg){
int server_descriptor = create_server_socket();
number_connections = 0;
client_descriptors = calloc(MAX_CONNECTION, sizeof(int));
// Thread to listen for connections
fd_set read_fds;
struct timespec sleep_spec;
sleep_spec.tv_nsec = 100000000; // .1 seconds
while (true) {
//select() modifies read_fds
//we must reset it at each iteration
FD_ZERO(&read_fds); // clears fd set
for(int i = 0; i < number_connections; i++){
FD_SET(client_descriptors[i], &read_fds);
}
FD_SET(server_descriptor, &read_fds);
//select will block until either fd is ready
// 60 is an arbitrary number. select() requires a number so it doesn't cause the kernal to check a lot of file descriptors
select(60, &read_fds, NULL, NULL, NULL);
// A client is requesting to connect to the server
if (FD_ISSET(server_descriptor, &read_fds)) {
handle_connection(server_descriptor);
}
// See which client is sending data to the server
for(int i = 0; i < number_connections; i++){
if(FD_ISSET(client_descriptors[i], &read_fds)){
char *received_data = force_read_message(client_descriptors[i]);
char message_type[MESSAGE_SIZE] = {'\0'};
// The first line (beginning of packet to first new line character) determines what type of message
strcpy(message_type, received_data);
char *end_header = strchr(message_type, '\n');
*end_header = '\0';
// Handle each type of message
if(strcmp(message_type, MESSAGE) == 0){
send_to_clients(received_data);
}else if(strcmp(message_type, LEAVE) == 0){
if(i == 0){
// the host left
send_to_clients(EXIT);
free(received_data);
close_server(server_descriptor);
printf("Chat closed. Not accepting any connections.\n");
break; // end server thread
}else{
// client (other than host) left
handle_disconnect(i, received_data);
}
}else if(strcmp(message_type, JOIN) == 0){
send_to_clients(received_data);
}
free(received_data);
}
}
// Put the infinite loop to sleep so it doesn't kill the CPU
nanosleep(&sleep_spec, &sleep_spec);
}
// TODO: handle exiting server gracefully
}
#pragma clang diagnostic pop
void handle_disconnect(int connection_index, char *message){
// remove descriptor
close(client_descriptors[connection_index]);
// shift client descriptors over to not leave gaps
for(int i = connection_index; i < number_connections - connection_index; i++){
client_descriptors[i] = client_descriptors[i] + 1;
}
number_connections--;
// tell all the other connectors that someone left
send_to_clients(message);
}
void send_to_clients(char *content){
for(int i = 0; i < number_connections; i++){
write(client_descriptors[i], content, MESSAGE_SIZE);
}
}
void handle_connection(int server_descriptor){
int connection = accept_connection(server_descriptor);
if(number_connections == 20){
char buff[MESSAGE_SIZE] = {'\0'};
strcat(buff, FULL);
write(connection, buff, MESSAGE_SIZE);
close(number_connections);
return;
}
client_descriptors[number_connections] = connection;
number_connections++;
if(number_connections == 1){
// host doesn't need a copy of the chat since it has it already
return;
}
// send the whole chat over
struct message *first_message_buff;
struct message *last_message_buff;
size_t message_length_buff;
get_message_lock(&first_message_buff, &last_message_buff, &message_length_buff);
struct message *current = first_message_buff;
for(int i = 0; i < message_length_buff; i++){
char buffer[MESSAGE_SIZE] = {'\0'};
strcat(buffer, MESSAGE);
strcat(buffer, "\n");
strcat(buffer, current->username);
strcat(buffer, "\n");
strcat(buffer, current->content);
write(connection, buffer, MESSAGE_SIZE);
current = current->next;
}
release_message_lock();
}
void close_server(int server_descriptor){
for(int i = 0; i < number_connections; i++){
close(client_descriptors[i]);
}
free(client_descriptors);
close(server_descriptor);
}
/**
* Creates the server socket and allow for incoming connection.
* Copied from DW code (he told us to)
* @return
*/
int create_server_socket() {
int sd, state;
//create the socket
sd = socket( AF_INET, SOCK_STREAM, 0 );
if(sd < 0){
handle_server_failure("Failed socket creation");
}
//setup structs for getaddrinfo
struct addrinfo * hints, * results;
hints = (struct addrinfo *)calloc(1, sizeof(struct addrinfo));
hints->ai_family = AF_INET; //IPv4 address
hints->ai_socktype = SOCK_STREAM; //TCP socket
hints->ai_flags = AI_PASSIVE; //Use all valid addresses
getaddrinfo(NULL, PORT, hints, &results); //NULL means use local address
//bind the socket to address and port
state = bind(sd, results->ai_addr, results->ai_addrlen);
if(state < 0){
close(sd);
handle_server_failure("Failed binding socket");
}
//set socket to listen state
state = listen(sd, 10);
if(state < 0){
close(sd);
handle_server_failure("Failed setting socket to listen state");
}
//free the structs used by getaddrinfo
free(hints);
freeaddrinfo(results);
return sd;
}
/**
* Accepts the incoming connection.
* @param sd Server socket descriptor
* @return Socket to communicate with the client.
*/
int accept_connection(int sd) {
int client_socket;
socklen_t sock_size;
struct sockaddr_storage client_address;
sock_size = sizeof(client_address);
client_socket = accept(sd, (struct sockaddr *)&client_address, &sock_size);
return client_socket;
}