在前一篇文章《iOS从零开始学习socket编程——HTTP1.0客户端》中已经简单的介绍过了Socket编程和一些基本原理。并且实现了简单的iOS客户端(原文地址:http://blog.csdn.net/abc649395594/article/details/45081567)
这里再简单介绍一下如何使用OC搭建socket的服务器端。虽然这并不是一个好的解决方案,通常我们使用Java或者PHP抑或NodeJS来搭建服务器后台。但是还是有必要了解一下OC的做法,顺便加深对Socket编程的理解。
废话不多说,还是导入AsyncSocket.h和AsyncSocket.m文件。这里我们创建的是一个桌面端的软件(iOS端也可。),创建了一个AppController类。
下面直接上代码,有点长,会慢慢解释。原来的程序是有图形界面的,不过为了更好地解释socket的工作原理,这里就把不必要的
//AppController.h
#import <Cocoa/Cocoa.h>
#import "AsyncSocket.h"
@interface AppController : NSObject<AsyncSocketDelegate>
{
AsyncSocket *listenSocket;
NSMutableArray *connectedSockets;
BOOL isRunning;
IBOutlet id logView;
IBOutlet id portField;
IBOutlet id startStopButton;
}
@property (atomic,strong) NSString *fileName;
@property (atomic,strong) NSMutableData *receiveData;
- (IBAction)startStop:(id)sender;
@end
AppController.h文件非常简单,有一个AsyncSocket类的socket对象,还有一个用来存放已连接的socket的数组。isRunning表示是否在运行,三个IBOutlet是界面需要的。fileName用于读取http请求的文件名,还有一个动作事件,在启动/结束键被按下时触发。
#import "AppController.h"
#import "AsyncSocket.h"
#import <AppKit/AppKit.h>
#define WELCOME_MSG 0
#define ECHO_MSG 1
#define WARNING_MSG 2
#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0
#define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__]
@interface AppController (PrivateAPI)
@end
@implementation AppController
@synthesize fileName;
- (id)init
{
if((self = [super init]))
{
listenSocket = [[AsyncSocket alloc] initWithDelegate:self];
connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
isRunning = NO;
}
return self;
}
- (void)awakeFromNib
{
[logView setString:@""];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[listenSocket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
- (IBAction)startStop:(id)sender
{
if(!isRunning)
{
int port = [portField intValue];
if(port < 0 || port > 65535)
{
port = 0;
}
NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
return;
}
isRunning = YES;
[portField setEnabled:NO];
[startStopButton setTitle:@"Stop"];
}
else
{
// Stop accepting connections
[listenSocket disconnect];
// Stop any client connections
NSUInteger i;
for(i = 0; i < [connectedSockets count]; i++)
{
[[connectedSockets objectAtIndex:i] disconnect];
}
isRunning = false;
[portField setEnabled:YES];
[startStopButton setTitle:@"Start"];
}
}
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
[connectedSockets addObject:newSocket];
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
[sock readDataWithTimeout:-1 tag:0];//very important
NSString *welcomeMsg = @"Welcome to the AsyncSocket Echo Server\r\n";
NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if(tag == ECHO_MSG)
{
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
NSRange endRange = [msg rangeOfString:@"HTTP"];
NSRange beginRange = [msg rangeOfString:@"/"];
if (beginRange.location != NSNotFound && endRange.location != NSNotFound) {
NSRange fileRange = NSMakeRange(beginRange.location, endRange.location - beginRange.location-1);
fileName = [msg substringWithRange:fileRange];
if(fileName && ![fileName isEqualToString:@""]){
NSData *returnData = [self getResponseHeader:fileName];
[sock writeData:returnData withTimeout:-1 tag:ECHO_MSG];
fileName = nil;
[sock disconnectAfterWriting];
}
else{
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
fileName = nil;
[sock disconnectAfterWriting];
}
}
else{
NSLog(@"Header not found");
dispatch_async(dispatch_get_main_queue(), ^{
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
fileName = nil;
[sock disconnectAfterWriting];
});
}
}
/**
* This method is called if a read has timed out.
* It allows us to optionally extend the timeout.
* We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length
{
if(elapsed <= READ_TIMEOUT)
{
NSString *warningMsg = @"Are you still there?\r\n";
NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];
return READ_TIMEOUT_EXTENSION;
}
return 0.0;
}
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
[self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])];
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
[connectedSockets removeObject:sock];
}
- (NSData *)getResponseHeader:(NSString *)localFileName{
//获取Response Header
NSMutableString *header = [[NSMutableString alloc]init];
NSMutableData *returnData = [[NSMutableData alloc]init];
NSData *fileContent = [NSData dataWithContentsOfFile:localFileName];
int contentLength = (int)[fileContent length];
[header appendString:@"HTTP/1.0 200 OK\r\n"];
[header appendString:@"Connection: close\r\n"];
[header appendString:@"Server: Apache/1.3.0(Unix)\r\n"];
NSString *contentLengthString = [NSString stringWithFormat:@"Content-Length:%d\r\n",contentLength];
[header appendString:contentLengthString];
NSString *mimeString = [NSString stringWithFormat:@"Content-Type: %@\r\n",[self getMimeByFileName:localFileName]];
[header appendString:mimeString];
[header appendString:@"\r\n"];
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"header = %@",header);
[returnData appendData:headerData];
[returnData appendData:fileContent];
//NSLog(@"return data = %@",returnData);
return (NSData *)returnData;
}
- (NSString *)getMimeByFileName:(NSString *)localFileName{
//通过文件名判断Response Header的MIME类型
NSRange htmlRange = [localFileName rangeOfString:@"html"];
NSRange htmRange = [localFileName rangeOfString:@"htm"];
NSRange pngRange = [localFileName rangeOfString:@"png"];
if (htmlRange.location != NSNotFound || htmRange.location != NSNotFound) {
NSString *mime = [[NSString alloc]initWithFormat:@"text/html"];
return mime;
}
else if(pngRange.location != NSNotFound){
NSString *mime = [[NSString alloc]initWithFormat:@"image/png"];
return mime;
}
NSString *mime = [[NSString alloc]initWithFormat:@"unknown"];
return mime;
}
@end
因为Storyboard文件无法上传,所以以上代码只能显示Socket服务器的工作流程而不能直接复制运行。需要自己写出图形界面或者绑定端口号。
关于didReadData方法使用时遇到的问题和解决方法以及详细分析,参见另一篇文章——《AsyncSocket didReadData函数详解》
地址:http://blog.csdn.net/abc649395594/article/details/45046871