blob: ec5dcaf04565ad6135ca63ac0c51552afa5632cb [file] [log] [blame]
/* Copyright (c) 2014 The Regents of the University of California
* Copyright (c) 2015 Google, Inc.
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Echo server using select, runs on port 23. Used for debugging select. Based
* on epoll_server.
*
* If you want to build the BSD sockets version, you need to comment out the
* #define for PLAN9NET. */
/* Comment this out for BSD sockets */
//#define PLAN9NET
#include <stdlib.h>
#include <stdio.h>
#include <parlib/parlib.h>
#include <unistd.h>
#include <parlib/event.h>
#include <benchutil/measure.h>
#include <parlib/uthread.h>
#include <parlib/timing.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef PLAN9NET
#include <iplib/iplib.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
int main(void)
{
int ret;
int afd, dfd, lcfd, listen_fd;
char adir[40], ldir[40];
int n;
char buf[256];
/* We'll use this to see if we actually did a select instead of blocking
* calls. It's not 100%, but with a human on the other end, it should
* be fine. */
bool has_selected = FALSE;
#ifdef PLAN9NET
printf("Using Plan 9's networking stack\n");
/* This clones a conversation (opens /net/tcp/clone), then reads the
* cloned fd (which is the ctl) to givure out the conv number (the
* line), then writes "announce [addr]" into ctl. This "announce"
* command often has a "bind" in it too. plan9 bind just sets the local
* addr/port. TCP announce also does this. Returns the ctlfd. */
afd = announce9("tcp!*!23", adir, 0);
if (afd < 0) {
perror("Announce failure");
return -1;
}
printf("Announced on line %s\n", adir);
#else
printf("Using the BSD socket shims over Plan 9's networking stack\n");
int srv_socket;
struct sockaddr_in dest, srv = {0};
srv.sin_family = AF_INET;
srv.sin_addr.s_addr = htonl(INADDR_ANY);
srv.sin_port = htons(23);
socklen_t socksize = sizeof(struct sockaddr_in);
/* Equiv to cloning a converstation in plan 9. The shim returns the
* data FD for the conversation. */
srv_socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (srv_socket < 0) {
perror("Socket failure");
return -1;
}
/* bind + listen is equiv to announce() in plan 9. Note that the "bind"
* command is used, unlike in the plan9 announce. */
/* Binds our socket to the given addr/port in srv. */
ret = bind(srv_socket, (struct sockaddr*)&srv, sizeof(struct
sockaddr_in));
if (ret < 0) {
perror("Bind failure");
return -1;
}
/* marks the socket as a listener/server */
ret = listen(srv_socket, 1);
if (ret < 0) {
perror("Listen failure");
return -1;
}
printf("Listened on port %d\n", ntohs(srv.sin_port));
#endif
/* at this point, the server has done all the prep necessary to be able to
* sleep/block/wait on an incoming connection. */
fd_set rfds;
#ifdef PLAN9NET
snprintf(buf, sizeof(buf), "%s/listen", adir);
listen_fd = open(buf, O_PATH);
if (listen_fd < 0) {
perror("listen fd");
return -1;
}
/* This is a little subtle. We're putting a tap on the listen file /
* listen_fd. When this fires, we get an event because of that
* listen_fd. But we don't actually listen or do anything to that
* listen_fd. It's solely for monitoring. We open a path, below, and
* we'll reattempt to do *that* operation when someone tells us that our
* listen tap fires. */
FD_ZERO(&rfds);
FD_SET(listen_fd, &rfds);
has_selected = FALSE;
while (1) {
/* Opens the conversation's listen file. This blocks til
* someone connects. When they do, a new conversation is
* created, and that open returned an FD for the new conv's ctl.
* listen() reads that to find out the conv number (the line)
* for this new conv. listen() returns the ctl for this new
* conv.
*
* Non-block is for the act of listening, and applies to lcfd.
* */
lcfd = listen9(adir, ldir, O_NONBLOCK);
if (lcfd >= 0)
break;
if (errno != EAGAIN) {
perror("Listen failure");
return -1;
}
if (select(listen_fd + 1, &rfds, 0, 0, 0) < 0) {
perror("select");
return -1;
}
has_selected = TRUE;
assert(FD_ISSET(listen_fd, &rfds));
}
printf("Listened and got line %s\n", ldir);
assert(has_selected);
/* No longer need listen_fd. */
close(listen_fd);
/* Writes "accept [NUM]" into the ctlfd, then opens the conv's data file
* and returns that fd. Writing "accept" is a noop for most of our
* protocols. */
dfd = accept9(lcfd, ldir);
if (dfd < 0) {
perror("Accept failure");
return -1;
}
#else
FD_ZERO(&rfds);
FD_SET(srv_socket, &rfds);
has_selected = FALSE;
while (1) {
/* returns an FD for a new socket. */
dfd = accept(srv_socket, (struct sockaddr*)&dest, &socksize);
if (dfd >= 0)
break;
if (errno != EAGAIN) {
perror("Accept failure");
return -1;
}
if (select(srv_socket + 1, &rfds, 0, 0, 0) < 0) {
perror("select");
return -1;
}
has_selected = TRUE;
assert(FD_ISSET(srv_socket, &rfds));
}
printf("Accepted and got dfd %d\n", dfd);
assert(has_selected);
#endif
/* In lieu of accept4, we set the new socket's nonblock status manually.
* Both OSs do this. */
ret = fcntl(dfd, F_SETFL, O_NONBLOCK);
if (ret < 0) {
perror("setfl dfd");
exit(-1);
}
FD_SET(dfd, &rfds);
/* echo until EOF */
has_selected = FALSE;
printf("Server read: ");
while (1) {
while ((n = read(dfd, buf, sizeof(buf))) > 0) {
for (int i = 0; i < n; i++)
printf("%c", buf[i]);
fflush(stdout);
/* Should select on this direction too. */
if (write(dfd, buf, n) < 0) {
perror("writing");
exit(-1);
}
}
if (n == 0)
break;
if (select(dfd + 1, &rfds, 0, 0, 0) < 0) {
perror("select 2");
exit(-1);
}
has_selected = TRUE;
assert(FD_ISSET(dfd, &rfds));
/* you might get a HUP, but keep on reading! */
/* Crazy fork tests. This will fork and let the child keep
* going with the connection. */
switch (fork()) {
case -1:
perror("Fork");
exit(-1);
break;
case 0:
break;
default:
exit(0);
}
}
assert(has_selected);
#ifdef PLAN9NET
close(dfd); /* data fd for the new conv, from listen */
close(lcfd); /* ctl fd for the new conv, from listen */
close(afd); /* ctl fd for the listening conv */
#else
close(dfd); /* new connection socket, from accept */
close(srv_socket);
#endif
}