-
-
Save jsanders/3140843 to your computer and use it in GitHub Desktop.
#include <stdlib.h> | |
#include <netinet/in.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#define MAX_CONNECTIONS 5 | |
#define BUFSIZE 1024 | |
typedef struct { | |
int fds[MAX_CONNECTIONS]; | |
int num; | |
} connections_t; | |
void die(char *msg) { | |
perror(msg); | |
exit(1); | |
} | |
unsigned short get_port(int argc, char **argv) { | |
if (argc != 2) { | |
fprintf(stderr, "usage: %s <port>\n", argv[0]); | |
exit(1); | |
} | |
return (unsigned short) atoi(argv[1]); | |
} | |
void bind_address(int listener, unsigned short port) { | |
struct sockaddr_in address; | |
memset(&address, 0, sizeof(address)); | |
address.sin_family = AF_INET; | |
address.sin_addr.s_addr = htonl(INADDR_ANY); | |
address.sin_port = htons(port); | |
if(bind(listener, (struct sockaddr *) &address, sizeof(address)) < 0) { | |
die("ERROR on binding"); | |
} | |
} | |
int create_listener(unsigned short port) { | |
int listener = socket(AF_INET, SOCK_STREAM, 0); | |
if (listener < 0) { die("ERROR opening socket"); } | |
bind_address(listener, port); | |
if(listen(listener, 5) < 0) { die("ERROR on listen"); } | |
return listener; | |
} | |
int set_fds_for_connections(connections_t *connections, fd_set *fds, int fd_max) { | |
int curr_fd, i; | |
for(i = 0; i < (*connections).num; i++) { | |
curr_fd = (*connections).fds[i]; | |
FD_SET(curr_fd, fds); | |
if(curr_fd > fd_max) { fd_max = curr_fd; } | |
} | |
return fd_max; | |
} | |
int set_fds_for_select(int listener, connections_t *connections, fd_set *fds) { | |
int fd_max = listener; | |
FD_ZERO(fds); | |
FD_SET(listener, fds); | |
return set_fds_for_connections(connections, fds, fd_max); | |
} | |
void add_connection(connections_t *connections, int fd) { | |
if((*connections).num + 1 > MAX_CONNECTIONS) { die("ERROR too many connections"); } | |
(*connections).fds[(*connections).num++] = fd; | |
} | |
int accept_connection(int listener) { | |
struct sockaddr_in address; | |
socklen_t address_size = sizeof(address); | |
int fd = accept(listener, (struct sockaddr *) &address, &address_size); | |
if(fd < 0) { die("ERROR on accept"); } | |
return fd; | |
} | |
void handle_connection(int fd) { | |
char buf[BUFSIZE]; | |
memset(&buf, 0, BUFSIZE); | |
int n = read(fd, buf, BUFSIZE); | |
if(n < 0) { die("ERROR reading from socket"); } | |
printf("server received %d bytes: %s", n, buf); | |
n = write(fd, buf, strlen(buf)); | |
if(n < 0) { die("ERROR writing to socket"); } | |
} | |
void handle_echo(connections_t *connections, fd_set *fds) { | |
int curr_fd, i; | |
for(i = 0; i < (*connections).num; i++) { | |
curr_fd = (*connections).fds[i]; | |
if(FD_ISSET(curr_fd, fds)) { | |
handle_connection(curr_fd); | |
} | |
} | |
} | |
void handle_connections(int listener, connections_t *connections) { | |
fd_set fds; | |
int fd_max = set_fds_for_select(listener, connections, &fds); | |
int ready = select(fd_max + 1, &fds, NULL, NULL, NULL); | |
if(ready < 0) { die("ERROR on select"); } | |
if(ready == 0) { return; } | |
if(FD_ISSET(listener, &fds)) { | |
add_connection(connections, accept_connection(listener)); | |
} | |
handle_echo(connections, &fds); | |
} | |
int main(int argc, char **argv) { | |
unsigned short port = get_port(argc, argv); | |
int listener = create_listener(port); | |
connections_t connections; | |
memset(&connections, 0, sizeof(connections)); | |
while(1) { | |
handle_connections(listener, &connections); | |
} | |
} |
Ok fixed the main problem, but now there's something wonky with the buffer when you make more than one connection.
Doy. Clear the buffer. Now it works.
In set_fds_for_connections
, why pass connections
as a pointer? You're not changing it. Also, if you must, at least say connections->fds[i]
.
Other than that, looks good!
It's overkill for this, but Gnu getopt is a nice library for parsing command line arguments.
My thought on passing it as a pointer was to avoid copying. I'm not incredibly savvy with regards to the semantics of passing non-trivially-sized structs by value and/or the compiler's. Now, I suppose my struct happens to be trivially sized, but that was an arbitrary choice.
I went back and forth on the arrow syntax...I don't write enough C (obviously!) to have developed a strong sense of style, but I'm not convinced that the arrow syntax reads better to me. I feel like I can read the dereference and access left-to-right - "ok first I dereference the pointer, now I access the element" - whereas I have to back-track a tiny bit for the arrow syntax - "ok I'm using a pointer to this, oh, it's an arrow, so now I need to think of it as dereferenced, ok, now access the element". Pretty weak argument :)
It's more a case of being idiomatic. Generally, when presented with a pointer (which is a fine decision), experienced C programmers will almost always dereference it with ->
instead of (*)
. But that's really a nitpick.
Doesn't actually work yet...but it compiles!