Skip to content

Commit 9898490

Browse files
committed
Cross platform makefile. Consolidated kqueue operations with updateEvent(). Improved Ctrl-C handling
1 parent 053c091 commit 9898490

File tree

5 files changed

+129
-127
lines changed

5 files changed

+129
-127
lines changed

Makefile

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ SRCDIR := src
66
BINDIR := bin
77
BUILDDIR := build
88
TARGET := httpserver
9+
UNAME := $(shell uname)
910

1011
# Debug Flags
1112
DEBUGFLAGS := -g3 -O0 -Wall
@@ -14,13 +15,15 @@ RTCHECKS := -fmudflap -fstack-check -gnato
1415
# Production Flags
1516
PRODFLAGS := -Wall -O2
1617

17-
# OSX Flags
18-
#CFLAGS := -std=c++11 -stdlib=libc++ -Iinclude/ $(DEBUGFLAGS)
19-
#LINK := -stdlib=libc++ $(DEBUGFLAGS)
20-
18+
ifeq ($(UNAME), Linux)
2119
# Linux Flags
2220
CFLAGS := -std=c++11 -Iinclude/ $(DEBUGFLAGS)
23-
LINK := -lpthread $(DEBUGFLAGS) -lkqueue
21+
LINK := -lpthread -lkqueue $(DEBUGFLAGS)
22+
else
23+
# OSX / BSD Flags
24+
CFLAGS := -std=c++11 -stdlib=libc++ -Iinclude/ $(DEBUGFLAGS)
25+
LINK := -stdlib=libc++ $(DEBUGFLAGS)
26+
endif
2427

2528

2629
SRCEXT := cpp

README

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
HTTP Server
1+
HTTP Server
22
Ramsey Kant
33

4-
https://github.com/RamseyK
5-
6-
I am writing this HTTP server as a learning tool with a focus on performance.
4+
https://github.com/RamseyK/httpserver
5+
6+
A high performance, single threaded, HTTP server written in C++ as a learning tool
77

88
Features:
99
-Clean, documented code
1010
-Efficient socket management with kqueue
1111
-Easy to understand HTTP protocol parser (from my ByteBuffer project)
1212
-Runs on Linux*, FreeBSD, OS X
1313

14-
* When compiling on Linux, you must link with pthreads & libkqueue
14+
* When compiling on Linux, you must link with pthreads & libkqueue. See Makefile
1515

1616
Benchmarks:
1717
Coming soon!

src/HTTPServer.cpp

Lines changed: 87 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ HTTPServer::HTTPServer() {
4343
HTTPServer::~HTTPServer() {
4444
// Loop through hostList and delete all ResourceHosts
4545
while(!hostList.empty()) {
46-
delete hostList.back();
46+
ResourceHost* resHost = hostList.back();
47+
delete resHost;
4748
hostList.pop_back();
4849
}
4950
vhosts.clear();
@@ -56,14 +57,15 @@ HTTPServer::~HTTPServer() {
5657
* @param port Port to listen on
5758
* @return True if initialization succeeded. False if otherwise
5859
*/
59-
void HTTPServer::start(int port) {
60-
canRun = false;
60+
bool HTTPServer::start(int port) {
61+
canRun = false;
62+
listenPort = port;
6163

6264
// Create a handle for the listening socket, TCP
6365
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
6466
if(listenSocket == INVALID_SOCKET) {
6567
std::cout << "Could not create socket!" << std::endl;
66-
return;
68+
return false;
6769
}
6870

6971
// Set socket as non blocking
@@ -73,50 +75,60 @@ void HTTPServer::start(int port) {
7375
// modify to support multiple address families (bottom): http://eradman.com/posts/kqueue-tcp.html
7476
memset(&serverAddr, 0, sizeof(struct sockaddr_in)); // clear the struct
7577
serverAddr.sin_family = AF_INET; // Family: IP protocol
76-
serverAddr.sin_port = htons(port); // Set the port (convert from host to netbyte order)
78+
serverAddr.sin_port = htons(listenPort); // Set the port (convert from host to netbyte order)
7779
serverAddr.sin_addr.s_addr = INADDR_ANY; // Let OS intelligently select the server's host address
7880

7981
// Bind: Assign the address to the socket
8082
if(bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
8183
std::cout << "Failed to bind to the address!" << std::endl;
82-
return;
84+
return false;
8385
}
8486

8587
// Listen: Put the socket in a listening state, ready to accept connections
8688
// Accept a backlog of the OS Maximum connections in the queue
8789
if(listen(listenSocket, SOMAXCONN) != 0) {
8890
std::cout << "Failed to put the socket in a listening state" << std::endl;
89-
return;
91+
return false;
9092
}
9193

9294
// Setup kqueue
9395
kqfd = kqueue();
9496
if(kqfd == -1) {
9597
std::cout << "Could not create the kernel event queue!" << std::endl;
96-
return;
98+
return false;
9799
}
98100

99101
// Have kqueue watch the listen socket
100-
struct kevent kev;
101-
EV_SET(&kev, listenSocket, EVFILT_READ, EV_ADD, 0, 0, NULL); // Fills kev
102-
kevent(kqfd, &kev, 1, NULL, 0, NULL);
103-
104-
std::cout << "Server started. Listening on port " << port << "..." << std::endl;
102+
updateEvent(listenSocket, EVFILT_READ, EV_ADD, 0, 0, NULL);
105103

106-
// Start processing
107104
canRun = true;
108-
process();
105+
std::cout << "Server ready. Listening on port " << listenPort << "..." << std::endl;
106+
return true;
109107
}
110108

111109
/**
112110
* Stop
113-
* Cleanup all server resources created in start()
111+
* Disconnect all clients and cleanup all server resources created in start()
114112
*/
115113
void HTTPServer::stop() {
116114
canRun = false;
117115

118-
if(listenSocket != INVALID_SOCKET)
119-
closeSockets();
116+
if(listenSocket != INVALID_SOCKET) {
117+
// Close all open connections and delete Client's from memory
118+
for(auto& x : clientMap)
119+
disconnectClient(x.second, false);
120+
121+
// Clear the map
122+
clientMap.clear();
123+
124+
// Remove listening socket from kqueue
125+
updateEvent(listenSocket, EVFILT_READ, EV_DELETE, 0, 0, NULL);
126+
127+
// Shudown the listening socket and release it to the OS
128+
shutdown(listenSocket, SHUT_RDWR);
129+
close(listenSocket);
130+
listenSocket = INVALID_SOCKET;
131+
}
120132

121133
if(kqfd != -1) {
122134
close(kqfd);
@@ -127,65 +139,14 @@ void HTTPServer::stop() {
127139
}
128140

129141
/**
130-
* Close Sockets
131-
* Disconnect and delete all clients in the client map. Shutdown the listening socket
142+
* Update Event
143+
* Update the kqueue by creating the appropriate kevent structure
144+
* See kqueue documentation for parameter descriptions
132145
*/
133-
void HTTPServer::closeSockets() {
134-
// Close all open connections and delete Client's from memory
135-
for(auto& x : clientMap)
136-
disconnectClient(x.second, false);
137-
138-
// Clear the map
139-
clientMap.clear();
140-
141-
// Remove listening socket from kqueue
146+
void HTTPServer::updateEvent(int ident, short filter, u_short flags, u_int fflags, int data, void *udata) {
142147
struct kevent kev;
143-
EV_SET(&kev, listenSocket, EVFILT_READ, EV_DELETE, 0, 0, NULL);
148+
EV_SET(&kev, ident, filter, flags, fflags, data, udata);
144149
kevent(kqfd, &kev, 1, NULL, 0, NULL);
145-
146-
// Shudown the listening socket and release it to the OS
147-
shutdown(listenSocket, SHUT_RDWR);
148-
close(listenSocket);
149-
listenSocket = INVALID_SOCKET;
150-
}
151-
152-
/**
153-
* Accept Connection
154-
* When a new connection is detected in runServer() this function is called. This attempts to accept the pending
155-
* connection, instance a Client object, and add to the client Map
156-
*/
157-
void HTTPServer::acceptConnection() {
158-
// Setup new client with prelim address info
159-
sockaddr_in clientAddr;
160-
int clientAddrLen = sizeof(clientAddr);
161-
int clfd = INVALID_SOCKET;
162-
163-
// Accept the pending connection and retrive the client descriptor
164-
clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen);
165-
if(clfd == INVALID_SOCKET)
166-
return;
167-
168-
// Set socket as non blocking
169-
fcntl(clfd, F_SETFL, O_NONBLOCK);
170-
171-
// Instance Client object
172-
Client *cl = new Client(clfd, clientAddr);
173-
174-
// Add kqueue event to track the new client socket for READ events (enabled)
175-
struct kevent read_kev;
176-
EV_SET(&read_kev, clfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); // Fills read_kev
177-
kevent(kqfd, &read_kev, 1, NULL, 0, NULL);
178-
179-
// Add kqueue event to track the new client socket for WRITE events (disabled)
180-
struct kevent write_kev;
181-
EV_SET(&write_kev, clfd, EVFILT_WRITE, EV_ADD | EV_DISABLE, 0, 0, NULL); // Fills write_kev
182-
kevent(kqfd, &write_kev, 1, NULL, 0, NULL);
183-
184-
// Add the client object to the client map
185-
clientMap.insert(std::pair<int, Client*>(clfd, cl));
186-
187-
// Print the client's IP on connect
188-
std::cout << "[" << cl->getClientIP() << "] connected" << std::endl;
189150
}
190151

191152
/**
@@ -197,20 +158,20 @@ void HTTPServer::process() {
197158
int nev = 0; // Number of changed events returned by kevent
198159
Client* cl = NULL;
199160
struct kevent read_kev, write_kev;
200-
161+
201162
while(canRun) {
202163
// Get a list of changed socket descriptors with a read event triggered in evList
203-
// Timeout is NULL, kevent will wait for a change before returning
204-
nev = kevent(kqfd, NULL, 0, evList, QUEUE_SIZE, NULL);
164+
// Timeout set in the header
165+
nev = kevent(kqfd, NULL, 0, evList, QUEUE_SIZE, &kqTimeout);
205166

206167
if(nev <= 0)
207168
continue;
208169

209-
// Loop through only the sockets that have changed in the evList array
210-
for(int i = 0; i < nev; i++) {
211-
if(evList[i].ident == (unsigned int)listenSocket) { // A client is waiting to connect
212-
acceptConnection();
213-
} else { // Client descriptor has triggered an event
170+
// Loop through only the sockets that have changed in the evList array
171+
for(int i = 0; i < nev; i++) {
172+
if(evList[i].ident == (unsigned int)listenSocket) { // A client is waiting to connect
173+
acceptConnection();
174+
} else { // Client descriptor has triggered an event
214175
cl = getClient(evList[i].ident); // ident contains the clients socket descriptor
215176
if(cl == NULL) {
216177
std::cout << "Could not find client" << std::endl;
@@ -230,28 +191,57 @@ void HTTPServer::process() {
230191
if(evList[i].filter == EVFILT_READ) {
231192
//std::cout << "read filter " << evList[i].data << " bytes available" << std::endl;
232193
// Read and process any pending data on the wire
233-
readClient(cl, evList[i].data); // data contains the number of bytes waiting to be read
194+
readClient(cl, evList[i].data); // data contains the number of bytes waiting to be read
234195

235196
// Have kqueue disable tracking of READ events and enable tracking of WRITE events
236-
EV_SET(&read_kev, evList[i].ident, EVFILT_READ, EV_DISABLE, 0, 0, NULL);
237-
EV_SET(&write_kev, evList[i].ident, EVFILT_WRITE, EV_ENABLE, 0, 0, NULL);
238-
kevent(kqfd, &read_kev, 1, NULL, 0, NULL);
239-
kevent(kqfd, &write_kev, 1, NULL, 0, NULL);
240-
} else if((evList[i].filter == EVFILT_WRITE)) {
197+
updateEvent(evList[i].ident, EVFILT_READ, EV_DISABLE, 0, 0, NULL);
198+
updateEvent(evList[i].ident, EVFILT_WRITE, EV_ENABLE, 0, 0, NULL);
199+
} else if(evList[i].filter == EVFILT_WRITE) {
241200
//std::cout << "write filter with " << evList[i].data << " bytes available" << std::endl;
242201
// Write any pending data to the client - writeClient returns true if there is additional data to send in the client queue
243202
if(!writeClient(cl, evList[i].data)) { // data contains number of bytes that can be written
244203
//std::cout << "switch back to read filter" << std::endl;
245204
// If theres nothing more to send, Have kqueue disable tracking of WRITE events and enable tracking of READ events
246-
EV_SET(&read_kev, evList[i].ident, EVFILT_READ, EV_ENABLE, 0, 0, NULL);
247-
EV_SET(&write_kev, evList[i].ident, EVFILT_WRITE, EV_DISABLE, 0, 0, NULL);
248-
kevent(kqfd, &write_kev, 1, NULL, 0, NULL);
249-
kevent(kqfd, &read_kev, 1, NULL, 0, NULL);
205+
updateEvent(evList[i].ident, EVFILT_READ, EV_ENABLE, 0, 0, NULL);
206+
updateEvent(evList[i].ident, EVFILT_WRITE, EV_DISABLE, 0, 0, NULL);
250207
}
251208
}
252-
}
253-
} // Pending event loop
254-
} // Main while
209+
}
210+
} // Event loop
211+
} // canRun
212+
}
213+
214+
/**
215+
* Accept Connection
216+
* When a new connection is detected in runServer() this function is called. This attempts to accept the pending
217+
* connection, instance a Client object, and add to the client Map
218+
*/
219+
void HTTPServer::acceptConnection() {
220+
// Setup new client with prelim address info
221+
sockaddr_in clientAddr;
222+
int clientAddrLen = sizeof(clientAddr);
223+
int clfd = INVALID_SOCKET;
224+
225+
// Accept the pending connection and retrive the client descriptor
226+
clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen);
227+
if(clfd == INVALID_SOCKET)
228+
return;
229+
230+
// Set socket as non blocking
231+
fcntl(clfd, F_SETFL, O_NONBLOCK);
232+
233+
// Instance Client object
234+
Client *cl = new Client(clfd, clientAddr);
235+
236+
// Add kqueue event to track the new client socket for READ and WRITE events
237+
updateEvent(clfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
238+
updateEvent(clfd, EVFILT_WRITE, EV_ADD | EV_DISABLE, 0, 0, NULL); // Disabled initially
239+
240+
// Add the client object to the client map
241+
clientMap.insert(std::pair<int, Client*>(clfd, cl));
242+
243+
// Print the client's IP on connect
244+
std::cout << "[" << cl->getClientIP() << "] connected" << std::endl;
255245
}
256246

257247
/**
@@ -288,11 +278,8 @@ void HTTPServer::disconnectClient(Client *cl, bool mapErase) {
288278
std::cout << "[" << cl->getClientIP() << "] disconnected" << std::endl;
289279

290280
// Remove socket events from kqueue
291-
struct kevent read_kev, write_kev;
292-
EV_SET(&read_kev, cl->getSocket(), EVFILT_READ, EV_DELETE, 0, 0, NULL);
293-
EV_SET(&write_kev, cl->getSocket(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
294-
kevent(kqfd, &read_kev, 1, NULL, 0, NULL);
295-
kevent(kqfd, &write_kev, 1, NULL, 0, NULL);
281+
updateEvent(cl->getSocket(), EVFILT_READ, EV_DELETE, 0, 0, NULL);
282+
updateEvent(cl->getSocket(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
296283

297284
// Close the socket descriptor
298285
close(cl->getSocket());

0 commit comments

Comments
 (0)