Created
March 15, 2021 08:47
-
-
Save grayed/82fb068e51adcc1085f45983d27cac4a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <sys/un.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <err.h> | |
#include <errno.h> | |
#include <netdb.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
/* number of lines to generate on client side */ | |
static const unsigned int nlines = 10; | |
static void proceed_client(FILE *f, const char *name); | |
static void server_loop(int servsock); | |
static void client_loop(int csock, unsigned int lines); | |
static void usage(const char *msg); | |
/* | |
* Processes data sent by client until end-of-stream or error. | |
*/ | |
void | |
proceed_client(FILE *f, const char *name) { | |
char *line = NULL; | |
size_t linesize = 0; | |
ssize_t linelen; | |
while ((linelen = getline(&line, &linesize, f)) != -1) { | |
printf("from %s: ", name); | |
fwrite(line, linelen, 1, stdout); | |
} | |
free(line); | |
/* don't care if there was any error, or just EOF */ | |
} | |
/* | |
* Accepts clients and proceeds them in protocol-agnostic way. | |
*/ | |
void | |
server_loop(int servsock) { | |
struct sockaddr_storage sas; | |
FILE *f; | |
socklen_t len; | |
int csock, cnum = 0; | |
char numbuf[20]; | |
for (;;) { | |
len = sizeof(struct sockaddr_storage); | |
if ((csock = accept(servsock, (struct sockaddr*)&sas, &len)) == -1) { | |
if (errno == EINTR) | |
continue; | |
err(1, "accept"); | |
break; | |
} | |
if ((f = fdopen(csock, "r+")) == NULL) | |
err(1, "fdopen"); | |
cnum++; | |
snprintf(numbuf, sizeof(numbuf), "client %d", cnum); | |
printf("===> %s connected\n", numbuf); | |
proceed_client(f, numbuf); | |
printf("===> %s disconnected\n", numbuf); | |
fclose(f); | |
close(csock); | |
} | |
} | |
#define countof(x) (sizeof(x) / sizeof(x[0])) | |
void | |
client_loop(int csock, unsigned int lines) { | |
static const char *nouns[] = { | |
"danger", | |
"security", | |
"table", | |
"picture", | |
"rainbow" | |
}; | |
static const char *verbs[] = { | |
"eats", | |
"sleeps", | |
"invites", | |
"sends", | |
"sees" | |
}; | |
static const char *adjectives[] = { | |
"beautifully", | |
"exclusively", | |
"blue", | |
"funny", | |
"last" | |
}; | |
FILE *f; | |
int i, j; | |
const char *noun, *verb, *adjective; | |
if ((f = fdopen(csock, "r+")) == NULL) | |
err(1, "fdopen"); | |
setvbuf(f, NULL, _IOLBF, 0); | |
for (i = 0; i < lines; i++) { | |
/* | |
* CAUTION in non-fun code you should care about uniform | |
* distribution; see, e.g., arc4random(3). | |
*/ | |
noun = nouns[rand() % countof(nouns)]; | |
verb = verbs[rand() % countof(verbs)]; | |
adjective = adjectives[rand() % countof(adjectives)]; | |
if (fprintf(f, "%s %s %s\n", noun, verb, adjective) < 0) | |
break; | |
sleep(1); | |
} | |
fclose(f); | |
} | |
#undef countof | |
void | |
usage(const char *msg) { | |
const char *name; | |
if (msg != NULL) | |
fprintf(stderr, "%s\n", msg); | |
name = getprogname(); | |
fprintf(stderr, "usage: %s {server|client} unix path\n", name); | |
fprintf(stderr, " %s {server|client} {inet|inet6} port [address]\n", name); | |
exit(2); | |
} | |
int | |
main(int argc, char **argv) { | |
struct sockaddr_storage ss; | |
socklen_t slen; | |
int s, servermode; | |
if (argc < 4 || argc > 5) | |
usage(NULL); | |
if (strcmp(argv[1], "server") == 0) | |
servermode = 1; | |
else if (strcmp(argv[1], "client") == 0) | |
servermode = 0; | |
else | |
usage("invalid mode, should be either server or client"); | |
memset(&ss, 0, sizeof(struct sockaddr_storage)); | |
if (strcmp(argv[2], "unix") == 0) { | |
struct sockaddr_un *sun = (struct sockaddr_un*)&ss; | |
if (argc > 4) | |
usage(NULL); | |
sun->sun_family = AF_UNIX; | |
if (strlcpy(sun->sun_path, argv[3], sizeof(sun->sun_path)) >= sizeof(sun->sun_path)) | |
usage("UNIX socket path is too long"); | |
slen = sizeof(struct sockaddr_un); | |
if (servermode) | |
unlink(argv[3]); | |
} else if (strcmp(argv[2], "inet") == 0 || strcmp(argv[2], "inet6") == 0) { | |
int port, rv; | |
if (argv[2][4] == '\0') { | |
ss.ss_family = AF_INET; | |
slen = sizeof(struct sockaddr_in); | |
} else { | |
ss.ss_family = AF_INET6; | |
slen = sizeof(struct sockaddr_in6); | |
} | |
port = atoi(argv[3]); | |
if (port <= 0 || port > 65535) | |
errx(1, "invalid port: %s", argv[3]); | |
if (argc > 4) { | |
if (ss.ss_family == AF_INET) { | |
struct sockaddr_in *sin = (struct sockaddr_in*)&ss; | |
sin->sin_port = htons((uint16_t)port); | |
rv = inet_pton(AF_INET, argv[4], &sin->sin_addr); | |
} else { | |
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss; | |
sin6->sin6_port = htons((uint16_t)port); | |
rv = inet_pton(AF_INET6, argv[4], &sin6->sin6_addr); | |
} | |
if (!rv) | |
errx(1, "invalid network address: %s", argv[4]); | |
} else if (servermode) { | |
/* | |
* Binding to "any" address is done by zeroing address, | |
* which was already done by memset(3). | |
*/ | |
} else { | |
/* connect to local address by default */ | |
if (ss.ss_family == AF_INET) { | |
inet_pton(ss.ss_family, "127.0.0.1", | |
&((struct sockaddr_in*)&ss)->sin_addr); | |
} else { | |
inet_pton(ss.ss_family, "::1", | |
&((struct sockaddr_in6*)&ss)->sin6_addr); | |
} | |
} | |
} else { | |
usage("invalid protocol family"); | |
} | |
if ((s = socket(ss.ss_family, SOCK_STREAM, 0)) == -1) | |
err(1, "socket"); | |
if (servermode) { | |
if (bind(s, (const struct sockaddr*)&ss, slen) == -1) | |
err(1, "bind"); | |
if (listen(s, 10) == -1) | |
err(1, "listen"); | |
server_loop(s); | |
} else { | |
if (connect(s, (const struct sockaddr*)&ss, slen) == -1) | |
err(1, "connect"); | |
client_loop(s, nlines); | |
} | |
close(s); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment