Skip to content

Commit f6698d9

Browse files
committed
Migrated from select to kqueue
1 parent ef0975e commit f6698d9

File tree

3 files changed

+57
-56
lines changed

3 files changed

+57
-56
lines changed

HTTPServer.cpp

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ HTTPServer::HTTPServer() {
3333

3434
// Instance clientMap, relates Socket Descriptor to pointer to Client object
3535
clientMap = new map<SOCKET, Client*>();
36-
37-
// Zero the file descriptor sets
38-
FD_ZERO(&fd_master);
39-
FD_ZERO(&fd_read);
4036
}
4137

4238
/**
@@ -85,13 +81,17 @@ void HTTPServer::start(int port) {
8581
return;
8682
}
8783

88-
// Add the listening socket to the master set and the largest FD is now the listening socket
89-
FD_SET(listenSocket, &fd_master);
90-
fdmax = listenSocket;
91-
92-
// Set select to timeout at 50 microseconds
93-
timeout.tv_sec = 0;
94-
timeout.tv_usec = 50;
84+
// Setup kqueue
85+
kqfd = kqueue();
86+
if(kqfd == -1) {
87+
cout << "Could not create the kernel event queue!" << endl;
88+
return;
89+
}
90+
91+
// Have kqueue watch the listen socket
92+
struct kevent kev;
93+
EV_SET(&kev, listenSocket, EVFILT_READ, EV_ADD, 0, 0, NULL); // Fills kev
94+
kevent(kqfd, &kev, 1, NULL, 0, NULL);
9595

9696
cout << "Server started. Listening on port " << port << "..." << endl;
9797

@@ -155,12 +155,10 @@ void HTTPServer::acceptConnection() {
155155
// Instance Client object
156156
Client *cl = new Client(clfd, clientAddr);
157157

158-
// Add to the master FD set
159-
FD_SET(clfd, &fd_master);
160-
161-
// If the new client's handle is greater than the max, set the new max
162-
if(clfd > fdmax)
163-
fdmax = clfd;
158+
// Have kqueue track the new client socket (udata contains pointer to Client object)
159+
struct kevent kev;
160+
EV_SET(&kev, clfd, EVFILT_READ, EV_ADD, 0, 0, cl); // Fills kev
161+
kevent(kqfd, &kev, 1, NULL, 0, NULL);
164162

165163
// Add the client object to the client map
166164
clientMap->insert(std::pair<int, Client*>(clfd, cl));
@@ -175,35 +173,34 @@ void HTTPServer::acceptConnection() {
175173
* the listening socket
176174
*/
177175
void HTTPServer::process() {
178-
int sret = 0;
176+
int nev = 0; // Number of changed events returned by kevent
177+
Client* cl = NULL;
179178

180179
while(canRun) {
181-
// Copy master set into fd_read for processing
182-
fd_read = fd_master;
183-
184-
// Populate read_fd set with client descriptors that are ready to be read
185-
// return values: -1 = unsuccessful, 0 = timeout, > 0 - # of sockets that need to be read
186-
sret = select(fdmax+1, &fd_read, NULL, NULL, &timeout);
187-
if(sret > 0) {
188-
// Loop through all descriptors in the read_fd set and check to see if data needs to be processed
189-
for(int i = 0; i <= fdmax; i++) {
190-
// Socket i isn't ready to be read (not in the read set), continue
191-
if(!FD_ISSET(i, &fd_read))
192-
continue;
193-
194-
// A new client is waiting to be accepted on the listenSocket
195-
if(listenSocket == i) {
196-
acceptConnection();
197-
} else { // The descriptor is a client
198-
Client *cl = getClient(i);
199-
handleClient(cl);
200-
}
201-
}
202-
} else if(sret < 0) {
203-
cout << "Select failed!" << endl;
204-
break;
180+
// Get a list of changed socket descriptors (if any) in evlist
181+
// Timeout is NULL, kevent will wait for a change before returning
182+
nev = kevent(kqfd, NULL, 0, evlist, QUEUE_SIZE, NULL);
183+
184+
if(nev > 0) {
185+
// Loop through only the sockets that have changed in the evlist array
186+
for(int i = 0; i < nev; i++) {
187+
if(evlist[i].ident == (unsigned int)listenSocket) { // A client is waiting to connect
188+
acceptConnection();
189+
} else { // Client descriptor has triggered an event
190+
cl = (Client*)evlist[i].udata;
191+
if(evlist[i].flags & EVFILT_READ) {
192+
handleClient(cl);
193+
} else if(evlist[i].flags & EV_EOF) {
194+
disconnectClient(cl);
195+
} else {
196+
// unhandled event
197+
}
198+
}
199+
}
200+
} else if(nev < 0) {
201+
cout << "kevent failed!" << endl; // should call errno..
205202
} else { // Timeout
206-
usleep(100);
203+
//usleep(100);
207204
}
208205
}
209206
}
@@ -239,12 +236,9 @@ void HTTPServer::disconnectClient(Client *cl, bool mapErase) {
239236
if(cl == NULL)
240237
return;
241238

242-
// Close the socket descriptor
239+
// Close the socket descriptor (which will also remove from kqueue's event list on the next kevent call)
243240
close(cl->getSocket());
244241

245-
// Remove the client from the master FD map
246-
FD_CLR(cl->getSocket(), &fd_master);
247-
248242
// Remove the client from the clientMap
249243
if(mapErase)
250244
clientMap->erase(cl->getSocket());

HTTPServer.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#include <string>
2424

2525
#include <time.h>
26-
#include <unistd.h>
26+
#include <sys/event.h> // kqueue
2727
#include <sys/types.h>
2828
#include <sys/socket.h>
2929
#include <sys/time.h>
@@ -39,17 +39,16 @@
3939

4040
#define SOCKET int
4141
#define INVALID_SOCKET -1
42+
#define QUEUE_SIZE 1024
4243

4344
class HTTPServer {
4445
bool canRun;
4546

4647
// Network
4748
SOCKET listenSocket; // Descriptor for the listening socket
4849
struct sockaddr_in serverAddr; // Structure for the server address
49-
fd_set fd_master; // Master FD set (listening socket + client sockets)
50-
fd_set fd_read; // FD set of sockets being read / operated on
51-
struct timeval timeout; // Select timeout
52-
int fdmax; // Max FD number (max sockets hanlde)
50+
int kqfd; // kqueue descriptor
51+
struct kevent evlist[QUEUE_SIZE]; // Events that have changed (max QUEUE_SIZE at a time)
5352
map<SOCKET, Client*> *clientMap; // Client map, maps Socket descriptor to Client object
5453

5554
// Resources / File System

README

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
Yet Another HTTP Server
1+
HTTP Server
22
Ramsey Kant
33

44
https://github.com/RamseyK
55

6-
A simple HTTP server for *nix that uses my HTTP parsing classes from ByteBuffer. I will eventually move from select() to epoll() in order to demonstrate better socket management techniques.
6+
I am writing this HTTP server as a learning tool with a focus on performance.
77

8-
Dependencies:
9-
Boost Threads
8+
Features:
9+
-Clean, documented code
10+
-Efficient socket management with kqueue
11+
-Easy to understand HTTP protocol parser (from my ByteBuffer project)
12+
-Runs on Linux*, FreeBSD, OS X
13+
14+
* When compiling on Linux, you must link with pthreads & libkqueue
15+
16+
Benchmarks:
17+
Coming soon!
1018

1119
License:
1220
See LICENSE.TXT

0 commit comments

Comments
 (0)