您的位置:首页 > 理论基础 > 计算机网络

iOS从零开始学习socket编程——HTTP1.0服务器端

2015-04-19 18:40 417 查看
在前一篇文章《iOS从零开始学习socket编程——HTTP1.0客户端》中已经简单的介绍过了Socket编程和一些基本原理。并且实现了简单的iOS客户端(原文地址:/article/1477419.html

这里再简单介绍一下如何使用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函数详解》

地址:/article/1477420.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: