Repository navigation
Expand file tree
/
Copy pathserver.c
More file actions
336 lines (279 loc) · 8.95 KB
/
Copy pathserver.c
File metadata and controls
336 lines (279 loc) · 8.95 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
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#define BUFFER_LEN 100
/**
* The error function prints out a given message and then closes
* the program
*
* @param msg A given message to print
*/
void error(const char *msg)
{
printf("Error: %s\n", msg);
exit(1);
}
/**
* Get and return the port number
*
* @param argv The given arguments when the .exe file is run
* @return The parsed port number
*/
int get_port_num (char *argv[])
{
int num;
// parse the port number from a string to an int
if (sscanf(argv[1], "%d", &num) != 1)
error("Failed to obtain port number");
if (num < 1024) // port number cannot be less then 1024
return -1;
return num;
}
/**
* Create a TCP socket
*
* @return the file descriptor of the socket
*/
int create_socket()
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
// check for socket creation failure
if (fd == -1)
error("Unable to create socket");
printf("Socket was created!\n");
return fd;
}
/**
* Create a socket address with the given port number
*
* @param port_num The port number
* @return a sockaddr_in stucture that represents the address
*/
struct sockaddr_in create_address(int port_num)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port_num); // port number
addr.sin_addr.s_addr = INADDR_ANY; // any address
printf("Address created\n");
return addr;
}
/**
* Binds the address to the TCP socket
*
* @param addr the previously created socket address
* @param fd the file descripter
*/
void bind_address(int fd, struct sockaddr_in addr)
{
int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
// check for binding failure
if (ret < 0)
error("Failed to bind address to socket");
printf("Binding was successful\n");
}
/**
* Listens for incoming connections with the socket
*
* @param fd the file descriptor of the socket
*/
void listen_for_incoming_connection(int fd)
{
if (listen(fd, SOMAXCONN) < 0)
error("Failed when listening for connections");
printf("Listening successful\n");
}
/**
* Accepts the clients connection to the socket
*
* @param fd The file descriptor for the socket
* @return The file descriptor of the client's accepted connection
*/
int accept_client (int fd)
{
struct sockaddr_in client_addr;
int addrlen = sizeof(client_addr);
// accepct the incoming cliet connection
int client_fd = accept(fd, (struct sockaddr *)&client_addr, (socklen_t*)&addrlen);
// check for acception failure
if(client_fd < 0)
error("Failed to accept client");
printf("Successfully accepted client\n");
return client_fd;
}
/**
* Sends a given message to the clients display
*
* @param client_fd The client's file descriptor
* @param msg The specified messaged to send
*/
void send_to_client(int client_fd, const char *msg)
{
// send the message
ssize_t bytes_sent = send(client_fd, msg, strlen(msg), 0);
// check for failure
if(bytes_sent < 0) {
error("Failed to send message to client");
}
}
/**
* Recieve a message that was sent from the client
*
* @param client_fd The client's file descpritor
* @param buffer The pointer to the buffer to contain the recieved message
* @return The number of bytes recieved, -1 if failure occurred
*/
ssize_t receive_client_message(int client_fd, char *buffer)
{
// recieve the clients message and save it to the buffer
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_LEN, 0);
// check for recieving failure
if(bytes_received < 0) {
error("Failed to recieve message");
}
return bytes_received;
}
/**
* Check to match the first word of any sentence to another given word
*
* @param str the given string
* @param target the string to match
* @return 0 if strings match, otherwise it is false
*/
int strings_match(char *str, char *target) {
char str_cpy[BUFFER_LEN];
int i;
// Find the first word in input
for (i = 0; str[i] != ' ' && str[i] != '\n'; i++)
str_cpy[i] = tolower(str[i]);
str_cpy[i] = '\0'; // terminate rest of string
return strcmp(str_cpy, target); // Compare the first words
}
/**
* Gets the filename and opens the file in the appropriate mode.
* Had to include multiple error message parameters to accurately follow given guildlines
*
* @param buffer The buffer containing the filename
* @param client_fd The client's file descriptor
* @param mode The given mode for opening the file
* @param err_msg2 The first error message
* @param err_msg2 The second error message
* @return The opened file
*/
FILE *open_file(char buffer[], int client_fd, char mode, char *err_msg1, char *err_msg2) {
char *filename = strchr(buffer, ' '); // get position of whitespace char
// no whitespace = no filename
if (filename == NULL) {
send_to_client(client_fd, err_msg1);
return NULL; // return a null file
}
filename++; // move filename pointer past the space
filename[strcspn(filename, "\n")] = '\0'; // remove newline character from filename and replace with a null pointer
FILE *file = fopen(filename, &mode); // open file
// send error if the file isn't avalible
if (file == NULL)
send_to_client(client_fd, err_msg2);
return file;
}
/**
* Sends the specified file contents to the client
*
* @param client_fd The clients file descriptor
* @param buffer The buffer containing the filename
*/
void get_file(int client_fd, char buffer[])
{
// handle GET request
FILE *file = open_file(buffer, client_fd, 'r', "SERVER 500 Get Error\n", "SERVER 404 Not Found\n");
// file couldn't open
if (file == NULL)
return; // go back to accepting new connections
// file opened correctly
send_to_client(client_fd, "SERVER 200 OK\n\n");
// send file contents to client character by character
int c;
while ((c = fgetc(file)) != EOF) {
send_to_client(client_fd, (char *)&c);
}
send_to_client(client_fd, "\n\n\n");
fclose(file);
}
/**
* Write the data that is recieved from the client into a specified file
*
* @param client_fd The clients file descriptor
* @param buffer The buffer containing the filename
*/
void put_file(int client_fd, char buffer[])
{
// Handle PUT request
FILE *file = open_file(buffer, client_fd, 'w', "SERVER 501 Put Error\n", "SERVER 501 Put Error\n");
// file couldn't open
if (file == NULL)
return; // go back to accepting new connections
// receive and write data to file until two consecutive empty lines
int empty_lines = 0;
while (1) {
int bytes_received = receive_client_message(client_fd, buffer);
// Check that the first character sent is a next line char,
// and that the line only contains at most 2 characters total (\n and \0)
if (buffer[0] == '\n' && bytes_received <= 2)
empty_lines++;
else
empty_lines = 0;
if (empty_lines > 1)
break;
fwrite(buffer, 1, bytes_received, file);
}
fclose(file);
// file opened and closed correctly
send_to_client(client_fd, "SERVER 200 OK\n\n");
}
/**
* Entry point of the server program.
* @param argc The number of command-line arguments.
* @param argv The array of command-line arguments:
* argv[0]: program name
* argv[1]: port number
* @return Returns 0 upon successful execution, -1 otherwise.
*/
int main(int argc, char *argv[])
{
// SERVER SET UP:
if (argc != 2)
return -1; // incorrect amount of arguments
// get port number
int port_num = get_port_num(argv);
if (port_num == -1)
return port_num; // port number is invalid
int fd = create_socket(); // create TCP socket
struct sockaddr_in addr = create_address(port_num); // create address
bind_address(fd, addr); // bind address to socket
listen_for_incoming_connection(fd); // listen for incomming connections
// HANDLING CLIENT INPUT:
while(1) {
int client_fd = accept_client(fd); // get client file descriptor/accept client
send_to_client(client_fd, "HELLO\n"); // send client a HELLO message
// recieve message from the client
char buffer[BUFFER_LEN];
receive_client_message(client_fd, buffer);
// CLIENT COMMANDS:
// GET: display file contents
if (strings_match(buffer, "get") == 0)
get_file(client_fd, buffer);
// PUT: write into a file
else if (strings_match(buffer, "put") == 0)
put_file(client_fd, buffer);
// If not BYE: send an error
else if (strings_match(buffer, "bye") != 0)
send_to_client(client_fd, "SERVER 502 Command Error\n");
// close client file descriptor
close(client_fd);
}
close(fd);
return 0;
}