Skip to content
This repository has been archived by the owner on Oct 9, 2020. It is now read-only.

Commit

Permalink
Adding multithreaded example.
Browse files Browse the repository at this point in the history
  • Loading branch information
robbiehanson committed Sep 12, 2010
1 parent 1a7c4e9 commit f3da5ff
Show file tree
Hide file tree
Showing 14 changed files with 3,252 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Samples/MultiThreadedHTTPServer/AppDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#import <Cocoa/Cocoa.h>

@class HTTPServer;


@interface AppDelegate : NSObject
{
HTTPServer *httpServer;
}

@end
25 changes: 25 additions & 0 deletions Samples/MultiThreadedHTTPServer/AppDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#import "AppDelegate.h"
#import "ThreadPoolServer.h"
#import "ThreadPerConnectionServer.h"


@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
httpServer = [[ThreadPoolServer alloc] init];
// httpServer = [[ThreadPerConnectionServer alloc] init];

[httpServer setType:@"_http._tcp."];
[httpServer setDocumentRoot:[NSURL fileURLWithPath:[@"~/Sites" stringByExpandingTildeInPath]]];

NSError *error = nil;
BOOL success = [httpServer start:&error];

if(!success)
{
NSLog(@"Error starting HTTP Server: %@", error);
}
}

@end
Binary file not shown.
2,447 changes: 2,447 additions & 0 deletions Samples/MultiThreadedHTTPServer/English.lproj/MainMenu.nib/designable.nib

Large diffs are not rendered by default.

Binary file not shown.
28 changes: 28 additions & 0 deletions Samples/MultiThreadedHTTPServer/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.MultiThreadedHTTPServer</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
18 changes: 18 additions & 0 deletions Samples/MultiThreadedHTTPServer/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Software License Agreement (BSD License)

Copyright (c) 2006, Deusty Designs, LLC
All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.

* Neither the name of Desuty Designs nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of Deusty Designs, LLC.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'MultiThreadedHTTPServer' target in the 'MultiThreadedHTTPServer' project
//

#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
25 changes: 25 additions & 0 deletions Samples/MultiThreadedHTTPServer/ThreadPerConnectionServer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// The ThreadPerConnectionServer creates a new thread for every incoming connection.
// After the thread is created, the connection is moved to the new thread.
//
// This is for DEMONSTRATION purposes only, and this technique will not scale well.
// Please understand the cost of creating threads on your target platform.
//

#import "HTTPServer.h"
#import "HTTPConnection.h"

@interface ThreadPerConnectionServer : HTTPServer
{

}

@end

@interface TPCConnection : HTTPConnection
{
NSRunLoop *myRunLoop;
BOOL continueRunLoop;
}

@end
94 changes: 94 additions & 0 deletions Samples/MultiThreadedHTTPServer/ThreadPerConnectionServer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#import "ThreadPerConnectionServer.h"
#import "AsyncSocket.h"


@interface HTTPConnection (InternalAPI)

- (void)startReadingRequest;

@end

@implementation ThreadPerConnectionServer

- (id)init
{
if(self = [super init])
{
connectionClass = [TPCConnection self];
}
return self;
}

@end

@implementation TPCConnection

- (id)initWithAsyncSocket:(AsyncSocket *)newSocket forServer:(HTTPServer *)myServer
{
if(self = [super initWithAsyncSocket:newSocket forServer:myServer])
{
continueRunLoop = YES;
[NSThread detachNewThreadSelector:@selector(setupRunLoop) toTarget:self withObject:nil];

// Note: The target of the thread is automatically retained, and released when the thread exits.
}
return self;
}

- (void)setupRunLoop
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

@synchronized(self)
{
myRunLoop = [NSRunLoop currentRunLoop];
}

// Note: It is assumed the main listening socket is running on the main thread.
// If this assumption is incorrect in your case, you'll need to call switchRunLoop on correct thread.
[self performSelectorOnMainThread:@selector(switchRunLoop) withObject:nil waitUntilDone:YES];

[self startReadingRequest];

while (continueRunLoop)
{
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];

[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];

NSLog(@"iteration");
[innerPool release];
}

NSLog(@"%p: RunLoop closing down", self);

[pool release];
}

- (void)switchRunLoop
{
@synchronized(self)
{
// The moveToRunLoop method must be called on the socket's existing runloop/thread
[asyncSocket moveToRunLoop:myRunLoop];
}

NSLog(@"%p: Run loop up", self);
}


- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
// Do nothing here - wait until the socket has been moved to the proper thread
}

/**
* Called when the connection dies.
**/
- (void)die
{
continueRunLoop = NO;
[super die];
}

@end
21 changes: 21 additions & 0 deletions Samples/MultiThreadedHTTPServer/ThreadPoolServer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// The ThreadPoolServer uses a pool of threads to handle connections.
// Each incoming connection is moved to a thread within the thread pool.
// We attempt to evenly spread the connections between the threads.
// To do this, we maintain the number of connections that are on each thread.
// A new incoming connection will be placed on the thread with the least connections.
//

#import "HTTPServer.h"
#import "HTTPConnection.h"

// Define number of connection threads to run
#define THREAD_POOL_SIZE 10

@interface ThreadPoolServer : HTTPServer
{
NSMutableArray *runLoops;
NSMutableArray *runLoopsLoad;
}

@end
125 changes: 125 additions & 0 deletions Samples/MultiThreadedHTTPServer/ThreadPoolServer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#import "ThreadPoolServer.h"

@interface HTTPServer (InternalAPI)

- (void)connectionDidDie:(NSNotification *)notification;

@end


@implementation ThreadPoolServer

- (id)init
{
if(self = [super init])
{
// Initialize an array to reference all the threads
runLoops = [[NSMutableArray alloc] initWithCapacity:THREAD_POOL_SIZE];

// Initialize an array to hold the number of connections being processed for each thread
runLoopsLoad = [[NSMutableArray alloc] initWithCapacity:THREAD_POOL_SIZE];

// Start threads
uint i;
for(i = 0; i < THREAD_POOL_SIZE; i++)
{
[NSThread detachNewThreadSelector:@selector(connectionThread:)
toTarget:self
withObject:[NSNumber numberWithUnsignedInt:i]];
}
}
return self;
}


- (void)connectionThread:(NSNumber *)threadNum
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

@synchronized(runLoops)
{
[runLoops addObject:[NSRunLoop currentRunLoop]];
[runLoopsLoad addObject:[NSNumber numberWithUnsignedInt:0]];
}

NSLog(@"Starting thread %@", threadNum);

// We can't run the run loop unless it has an associated input source or a timer.
// So we'll just create a timer that will never fire - unless the server runs for 10,000 years.
[NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(ignore:) userInfo:nil repeats:NO];

// Start the run loop
[[NSRunLoop currentRunLoop] run];

[pool release];
}

/**
* Called when a new socket is spawned to handle a connection. This method should return the runloop of the
* thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used.
**/
- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket
{
// Figure out what thread/runloop to run the new connection on.
// We choose the thread/runloop with the lowest number of connections.

uint m = 0;
NSRunLoop *mLoop = nil;
uint mLoad = 0;

@synchronized(runLoops)
{
mLoop = [runLoops objectAtIndex:0];
mLoad = [[runLoopsLoad objectAtIndex:0] unsignedIntValue];

uint i;
for(i = 1; i < THREAD_POOL_SIZE; i++)
{
uint iLoad = [[runLoopsLoad objectAtIndex:i] unsignedIntValue];

if(iLoad < mLoad)
{
m = i;
mLoop = [runLoops objectAtIndex:i];
mLoad = iLoad;
}
}

[runLoopsLoad replaceObjectAtIndex:m withObject:[NSNumber numberWithUnsignedInt:(mLoad + 1)]];
}

NSLog(@"Choosing run loop %u with load %u", m, mLoad);

// And finally, return the proper run loop
return mLoop;
}

/**
* This method is automatically called when a HTTPConnection dies.
* We need to update the number of connections per thread.
**/
- (void)connectionDidDie:(NSNotification *)notification
{
// Note: This method is called on the thread/runloop that posted the notification

@synchronized(runLoops)
{
unsigned int runLoopIndex = [runLoops indexOfObject:[NSRunLoop currentRunLoop]];

if(runLoopIndex < [runLoops count])
{
unsigned int runLoopLoad = [[runLoopsLoad objectAtIndex:runLoopIndex] unsignedIntValue];

NSNumber *newLoad = [NSNumber numberWithUnsignedInt:(runLoopLoad - 1)];

[runLoopsLoad replaceObjectAtIndex:runLoopIndex withObject:newLoad];

NSLog(@"Updating run loop %u with load %@", runLoopIndex, newLoad);
}
}

// Don't forget to call super, or the connection won't get proper deallocated!
[super connectionDidDie:notification];
}

@end
14 changes: 14 additions & 0 deletions Samples/MultiThreadedHTTPServer/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// main.m
// MultiThreadedHTTPServer
//
// Created by Robbie Hanson on 8/15/08.
// Copyright __MyCompanyName__ 2008. All rights reserved.
//

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}

0 comments on commit f3da5ff

Please sign in to comment.