Add epoll_server test
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 3 Sep 2015 18:53:51 +0000 (14:53 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 28 Sep 2015 19:14:00 +0000 (15:14 -0400)
It's a simple telnet server, using epoll for both the listen/accept and
the read-side of the data connection.

Similar to listener, it can use either the BSD sockets or the raw Plan 9
interface.

tests/epoll_server.c [new file with mode: 0644]

diff --git a/tests/epoll_server.c b/tests/epoll_server.c
new file mode 100644 (file)
index 0000000..6200562
--- /dev/null
@@ -0,0 +1,254 @@
+/* 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];
+       char debugbuf[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, O_NONBLOCK);
+
+       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;
+       }
+       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 new connection.  Not the act of listening. */
+               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);
+       /* In lieu of accept4, we set the new socket's nonblock status manually */
+       ret = fcntl(dfd, F_SETFL, O_NONBLOCK);
+       if (ret < 0) {
+               perror("setfl dfd");
+               exit(-1);
+       }
+       if (epoll_ctl(epfd, EPOLL_CTL_DEL, srv_socket, &ep_ev)) {
+               perror("epoll_ctl_del");
+               while (1);
+               exit(-1);
+       }
+
+#endif
+
+       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;
+       while (1) {
+               while ((n = read(dfd, buf, sizeof(buf))) > 0) {
+                       snprintf(debugbuf, n, "%s", buf);
+                       printf("Server read: %s", debugbuf);
+                       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
+}