From 49f044782e651329a9a73b60426b5de6014b9d20 Mon Sep 17 00:00:00 2001 From: NerdRat Date: Sun, 7 Jun 2020 11:11:54 -0500 Subject: [PATCH] Initial commit for networking playground in C --- Makefile | 10 ++++++ README.md | 4 +++ examples/Makefile | 12 +++++++ examples/client.c | 86 ++++++++++++++++++++++++++++++++++++++++++++ examples/server.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++ src/Makefile | 15 ++++++++ src/client.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++ src/networking.c | 35 ++++++++++++++++++ src/networking.h | 8 +++++ src/server.c | 0 10 files changed, 353 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/Makefile create mode 100644 examples/client.c create mode 100644 examples/server.c create mode 100644 src/Makefile create mode 100644 src/client.c create mode 100644 src/networking.c create mode 100644 src/networking.h create mode 100644 src/server.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20bfafb --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all clean: + cd src && $(MAKE) $@ + +samples: + cd examples && $(MAKE) + +clean-samples: + cd examples && $(MAKE) clean + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6cb599 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Networking +========== + +A playground to learn network programming in C. diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..8dd4b69 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,12 @@ +all: client server + +client: client.c + $(CC) $< -o $@ + +server: server.c + $(CC) $< -o $@ + +clean: + rm server client + +.PHONY: clean diff --git a/examples/client.c b/examples/client.c new file mode 100644 index 0000000..2b08826 --- /dev/null +++ b/examples/client.c @@ -0,0 +1,86 @@ + +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 500 + +int main(int argc, char *argv[]) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s, j; + size_t len; + ssize_t nread; + char buf[BUF_SIZE]; + + if (argc < 3) { + fprintf(stderr, "Usage: %s host port msg...\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Obtain address(es) matching host/port */ + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + s = getaddrinfo(argv[1], argv[2], &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully connect(2). + If socket(2) (or connect(2)) fails, we (close the socket + and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; /* Success */ + + close(sfd); + } + freeaddrinfo(result); /* No longer needed */ + + /* Send remaining command-line arguments as separate + datagrams, and read responses from server */ + + for (j = 3; j < argc; j++) { + len = strlen(argv[j]) + 1; + /* +1 for terminating null byte */ + + if (len > BUF_SIZE) { + fprintf(stderr, + "Ignoring long message in argument %d\n", j); + continue; + } + + if (write(sfd, argv[j], len) != len) { + fprintf(stderr, "partial/failed write\n"); + exit(EXIT_FAILURE); + } + + nread = read(sfd, buf, BUF_SIZE); + if (nread == -1) { + perror("read"); + exit(EXIT_FAILURE); + } + + printf("Received %zd bytes: %s\n", nread, buf); + } + + exit(EXIT_SUCCESS); +} diff --git a/examples/server.c b/examples/server.c new file mode 100644 index 0000000..012a2d1 --- /dev/null +++ b/examples/server.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 500 + +int main(int argc, char *argv[]) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len; + ssize_t nread; + char buf[BUF_SIZE]; + + if (argc != 2) { + fprintf(stderr, "Usage: %s port\n", argv[0]); + exit(EXIT_FAILURE); + } + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + s = getaddrinfo(NULL, argv[1], &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully bind(2). + If socket(2) (or bind(2)) fails, we (close the socket + and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; /* Success */ + + close(sfd); + } + + if (rp == NULL) { /* No address succeeded */ + fprintf(stderr, "Could not bind\n"); + exit(EXIT_FAILURE); + } + + freeaddrinfo(result); /* No longer needed */ + + /* Read datagrams and echo them back to sender */ + + for (;;) { + peer_addr_len = sizeof(struct sockaddr_storage); + nread = recvfrom(sfd, buf, BUF_SIZE, 0, + (struct sockaddr *) &peer_addr, &peer_addr_len); + if (nread == -1) + continue; /* Ignore failed request */ + + char host[NI_MAXHOST], service[NI_MAXSERV]; + + s = getnameinfo((struct sockaddr *) &peer_addr, + peer_addr_len, host, NI_MAXHOST, + service, NI_MAXSERV, NI_NUMERICSERV); + if (s == 0) { + printf("Received %zd bytes from %s:%s\n", + nread, host, service); + printf("Data: %s\n", buf); + } else { + fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s)); + } + + if (sendto(sfd, buf, nread, 0, + (struct sockaddr *) &peer_addr, + peer_addr_len) != nread) + fprintf(stderr, "Error sending response\n"); + } +} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..e809e6f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,15 @@ +DISTDIR=../dist + +all: dist networking + +networking: networking.o + $(CC) ${DISTDIR}/$< -o ${DISTDIR}/$@ + +networking.o: networking.c + $(CC) -c $< -o ${DISTDIR}/$@ + +dist: + mkdir -p ${DISTDIR} + +clean: + rm -r ${DISTDIR} diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..8ddb58e --- /dev/null +++ b/src/client.c @@ -0,0 +1,91 @@ +#include "networking.h" + +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 500 + + +void client_run(const char *const host, + const char *const port, + const char *const msg) { + + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s, j; + size_t len; + ssize_t nread; + char buf[BUF_SIZE]; + + /* Obtain address(es) matching host/port */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + s = getaddrinfo(host, port, &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + /* getaddrinfo() returns a list of address structures. + * try each address untill we succesfully connect(2). + * If socket(2) (or connect(2)) fails, we (close the socket + * and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; /* Success */ + + close(sfd); + } + + if (rp == NULL) { /* No address succeeded */ + fprintf(stderr, "Could not connect\n"); + exit(EXIT_FAILURE); + } + + freeaddrinfo(result); /* No longer needed */ + + /* Send remaining command-line arguments as separate + datagrams, and read responses from server */ + + /* TODO: Adapt this to use msg instead */ + for (j = 3; j < argc; j++) { + len = strlen(argv[j]) + 1; + /* +1 for terminating null byte */ + + if (len > BUF_SIZE) { + fprintf(stderr, + "Ignoring long message in argument %d\n", j); + continue; + } + + if (write(sfd, argv[j], len) != len) { + fprintf(stderr, "partial/failed write\n"); + exit(EXIT_FAILURE); + } + + nread = read(sfd, buf, BUF_SIZE); + if (nread == -1) { + perror("read"); + exit(EXIT_FAILURE); + } + + printf("Received %zd bytes: %s\n", nread, buf); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/networking.c b/src/networking.c new file mode 100644 index 0000000..b4793bc --- /dev/null +++ b/src/networking.c @@ -0,0 +1,35 @@ +#include +#include + +#include "networking.h" + +void usage(const char *const progname); +void help(void); + +int main(int argc, char *argv[]) { + (void) fprintf(stdout, "%s\n", "Networking with C"); + + if (argc < 3) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} + +void usage(const char *const progname) { + + fprintf(stderr, "Usage: %s [OPTION] host port msg...\n", progname); + help(); +} + +void help(void) { + fprintf(stdout, "\t%s\n", "OPTIONS:"); + fprintf(stdout, "\t\t%s\n\t\t%s\n", "-s", "--server"); + fprintf(stdout, "\t\t%s\n\n", "Run the program as a server."); + fprintf(stdout, "\t\t%s\n\t\t%s\n", "-c", "--client"); + fprintf(stdout, "\t\t%s\n\n", "Run the program as a client."); + fprintf(stdout, "\t%s\n", "Running as a client is the default behavior."); +} + + diff --git a/src/networking.h b/src/networking.h new file mode 100644 index 0000000..a2aa0de --- /dev/null +++ b/src/networking.h @@ -0,0 +1,8 @@ +#ifndef NETWORKING_H +#define NETWORKING_H + +void client_run(const char *const host, + const char *const port, + const char *const msg); + +#endif /* NETWORKING_H */ diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..e69de29