blob: c1059f1dd072de2b0f05c65e1466b8c10fb61439 [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 epoll, runs on port 23. Main purpose is epoll testing.
*
* 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/epoll.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()
{
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 epoll_waits instead of
* blocking calls. It's not 100%, but with a human on the other end, it
* should be fine. */
bool has_epolled = 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, con_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. */
#define EP_SET_SZ 10 /* this is actually the ID of the largest FD */
int epfd = epoll_create(EP_SET_SZ);
struct epoll_event ep_ev;
struct epoll_event results[EP_SET_SZ];
if (epfd < 0) {
perror("epoll_create");
exit(-1);
}
ep_ev.events = EPOLLIN | EPOLLET;
#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. */
ep_ev.data.fd = listen_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ep_ev)) {
perror("epoll_ctl_add listen");
exit(-1);
}
has_epolled = 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 (epoll_wait(epfd, results, EP_SET_SZ, -1) != 1) {
perror("epoll_wait");
exit(-1);
}
has_epolled = TRUE;
assert(results[0].data.fd == listen_fd);
assert(results[0].events == EPOLLIN);
}
printf("Listened and got line %s\n", ldir);
assert(has_epolled);
/* No longer need listen_fd. You should CTL_DEL before closing. */
if (epoll_ctl(epfd, EPOLL_CTL_DEL, listen_fd, &ep_ev)) {
perror("epoll_ctl_del");
exit(-1);
}
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
ep_ev.data.fd = srv_socket;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, srv_socket, &ep_ev)) {
perror("epoll_ctl_add srv_socket");
exit(-1);
}
has_epolled = 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 (epoll_wait(epfd, results, EP_SET_SZ, -1) != 1) {
perror("epoll_wait");
exit(-1);
}
has_epolled = TRUE;
assert(results[0].data.fd == srv_socket);
assert(results[0].events == EPOLLIN);
}
printf("Accepted and got dfd %d\n", dfd);
assert(has_epolled);
if (epoll_ctl(epfd, EPOLL_CTL_DEL, srv_socket, &ep_ev)) {
perror("epoll_ctl_del");
while (1);
exit(-1);
}
#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);
}
ep_ev.data.fd = dfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, dfd, &ep_ev)) {
perror("epoll_ctl_add dvd");
exit(-1);
}
/* echo until EOF */
has_epolled = 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 epoll on this direction too. */
if (write(dfd, buf, n) < 0) {
perror("writing");
exit(-1);
}
}
if (n == 0)
break;
if (epoll_wait(epfd, results, EP_SET_SZ, -1) != 1) {
perror("epoll_wait 2");
exit(-1);
}
has_epolled = TRUE;
assert(results[0].data.fd == dfd);
/* you might get a HUP, but keep on reading! */
}
assert(has_epolled);
if (epoll_ctl(epfd, EPOLL_CTL_DEL, dfd, &ep_ev)) {
perror("epoll_ctl_del dfd");
exit(-1);
}
#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
}