`
l241002209
  • 浏览: 87972 次
文章分类
社区版块
存档分类
最新评论

在iPhone上实现简单Http服务

阅读更多

原文:A simple, extensible HTTP server in Cocoa

原文地址:http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html

http是计算机之间通讯协议的比较简单的一种。在iPhone上,由于没有同步数据和文件共享的APIs,实现iPhone应用程序与PC之间的数据传输的最佳方式就是在程序中嵌入一个http服务器。在这篇帖子理,我将演示如何写一个简单但可以扩展的http服务器。该服务器类也可在Mac下运行。

介绍

示例程序运行效果如下:

程序很简单:你可以编辑和保存一个文本文件(总是保存在同一个文件)。当程序还在运行的时候,它会在8080端口上运行一个http服务。如果你请求”/”路径,它会返回文本文件的内容。其他请求会导致501错误。要想将文本文件从iPhone程序传输到PC,只需在浏览器中输入iPhone的ip地址并加上端口号8080。

HTTPServer类和 HTTPResponseHandler 类

该http服务器涉及两个类:服务器(负责监听连接请求并读取数据,直到http头结束),响应处理(发送响应并从http头以后进行数据读取)。

设计server类和response类的目的是简化其他response类的实现,只需要实现这3个方法:

  • canHandleRequest:method:url:headerFields: 指定该response是否会对某个请求进行处理。
  • startResponse:开始进行响应。
  • load: — 所有子类都应该实现 +[NSObject load] 方法,并将自己向基类进行注册。

这就是一个最基本的http服务器,但它可以让你很快在程序中集成http通讯的功能。

建立Socket监听

包括http在内的大部分服务器通讯,都要从建立socket监听开始。Cocoa的Sockets可以完全采用BSDsockets代码实现,但使用CoreFoundation 的CFSocket API要容易一些。不幸的是,虽然已经“尽可能大地”简化——但为了打开一个socket,你仍然不得不写大量模式化的代码。

.

HTTPServer的start method:

socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,

IPPROTO_TCP, 0, NULL, NULL);

if (!socket)

{

[self errorWithName:@"Unable to create socket."];

return;

}

int reuse = true;

int fileDescriptor = CFSocketGetNative(socket);

if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR,

(void *)&reuse, sizeof(int)) != 0)

{

[self errorWithName:@"Unable to set socket options."];

return;

}

struct sockaddr_in address;

memset(&address, 0, sizeof(address));

address.sin_len = sizeof(address);

address.sin_family = AF_INET;

address.sin_addr.s_addr = htonl(INADDR_ANY);

address.sin_port = htons(HTTP_SERVER_PORT);

CFDataRef addressData =

CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address));

[(id)addressData autorelease];

if (CFSocketSetAddress(socket, addressData) != kCFSocketSuccess)

{

[self errorWithName:@"Unable to bind socket to address."];

return;

}

这么多的代码只是在做一件事情:打开socket,监听来自HTTP_SERVER_PORT(8080端口)的TCP连接。

此外,我使用了SO_REUSEADDR。这是为了重用已经打开的端口(这是因为,如果我们在程序崩溃后立即重新打开程序,经常会导致端口被占用)。

接受请求

socket一旦建立,事情就变得简单了。对于每个监听到的连接通知,我们可以从fileDescriptor 构造一个NSFileHandle 以接受请求。

listeningHandle = [[NSFileHandle alloc]

initWithFileDescriptor:fileDescriptor

closeOnDealloc:YES];

[[NSNotificationCenter defaultCenter]

addObserver:self

selector:@selector(receiveIncomingConnectionNotification:)

name:NSFileHandleConnectionAcceptedNotification

object:nil];

[listeningHandle acceptConnectionInBackgroundAndNotify];

当 receiveIncomingConnectionNotification:方法被调用时,每个新来的请求都会创建一个NSFileHandle. 继续跟踪下去你会发现:

  • 对于socket fileDescriptor监听到的新连接请求,从fileDescriptor“手动”创建了一个file handle(listeningHandle) 。
  • 1 file handle (listeningHandle) manually created from the socket fileDesriptor to listen on the socket for new connections.
  • 对于listeningHandle接收到的每个新连接,会“自动”创建一个file handle。我们会不停地监听这些新的handles(key会记录在inconmingRequests字典中),并记录了每个连接的数据。

现在,我们收到了一个新的自动创建的file handle,我们创建了一个http请求消息CFHTTPMessageRef(用于储存请求数据),并把这些对象存在incomingRequests字典以便下次访问CFHTTPMessageRef。

CFHTTPMessageRef存放并对请求数据进行解析。 ,我们可以调用CFHTTMessageHeaderComplete()函数进行判断,一直到http头完成并产生一个response handler时 。

在HTTPServerreceiveIncomingDataNotification:方法中,我们生成了response handler。

if(CFHTTPMessageIsHeaderComplete(incomingRequest))

{

HTTPResponseHandler *handler =

[HTTPResponseHandler

handlerForRequest:incomingRequest

fileHandle:incomingFileHandle

server:self];

[responseHandlers addObject:handler];

[self stopReceivingForFileHandle:incomingFileHandle close:NO];

[handler startResponse];

return;

}

服务器停止监听连接的同时并不关闭它,因为file handle被传递给HTTPResponseHandler以便HTTP响应能发至相同的file handle。

弹性的响应处理

+[HTTPResponseHandlerhandlerForRequest:fileHandle:server:]方法到底返回哪个子类取决于response的内容。它遍历已注册的handlers数组(以排序),轮询每个handler看哪个愿意处理这个请求。

+ (Class)handlerClassForRequest:(CFHTTPMessageRef)aRequest

method:(NSString *)requestMethod

url:(NSURL *)requestURL

headerFields:(NSDictionary *)requestHeaderFields

{

for (Class handlerClass in registeredHandlers)

{

if ([handlerClass canHandleRequest:aRequest

method:requestMethod

url:requestURL

headerFields:requestHeaderFields])

{

return handlerClass;

}

}

return nil;

}

因此,所有HTTPResponseHandlers都需要向基类进行注册。最简单的方法是在每个子类的+NSObjectload方法中进行注册。

+ (void)load

{

[HTTPResponseHandler registerHandler:self];

}

在这里,仅有的response handler是AppTextFileResponse。这个类负责处理requestURL等于”/”的请求。

+ (BOOL)canHandleRequest:(CFHTTPMessageRef)aRequest

method:(NSString *)requestMethod

url:(NSURL *)requestURL

headerFields:(NSDictionary *)requestHeaderFields

{

if ([requestURL.path isEqualToString:@"/"])

{

return YES;

}

return NO;

}

随后,AppTextFileResponse在startResponse方法里进行同步响应,把程序保存的文本文件内容写入响应消息里。

- (void)startResponse

{

NSData *fileData =

[NSData dataWithContentsOfFile:[AppTextFileResponse pathForFile]];

CFHTTPMessageRef response =

CFHTTPMessageCreateResponse(

kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);

CFHTTPMessageSetHeaderFieldValue(

response, (CFStringRef)@"Content-Type", (CFStringRef)@"text/plain");

CFHTTPMessageSetHeaderFieldValue(

response, (CFStringRef)@"Connection", (CFStringRef)@"close");

CFHTTPMessageSetHeaderFieldValue(

response,

(CFStringRef)@"Content-Length",

(CFStringRef)[NSString stringWithFormat:@"%ld", [fileData length]]);

CFDataRef headerData = CFHTTPMessageCopySerializedMessage(response);

@try

{

[fileHandle writeData:(NSData *)headerData];

[fileHandle writeData:fileData];

}

@catch (NSException *exception)

{

// Ignore the exception, it normally just means the client

// closed the connection from the other end.

}

@finally

{

CFRelease(headerData);

[server closeHandler:self];

}

}

[servercloseHandler:self]; 告诉服务端从当前handlers中移除HTTPResponseHandler。

服务端会调用endResponse移除handler(在关闭连接时——因为handler不支持keep-alive即常连接)。

有待完善之处

最大的任务解析http请求体没有被实现。因为正常的http体解析过程十分复杂。http体的长度在content-length头中指定,但可能不会被指定——因此无法直到http体何时结束。http体还可能是编码的,编码方式有各种各样的:包括chunk, quoted-printable,base64, gzip— 而每一种的处理都完全不同。

我重来没想过实现一种普遍的解决方案。通常要根据你的实际需要而定,你可以在HTTPRequestHandler的receiveIncomingDataNotification:方法中处理请求体。默认,我忽略了http请求头之后的所有数据。

提示:

HTTPRequestHandlerreceiveIncomingDataNotification:方法第一次调用时,Http体的开始字节已经从fileHandle中获得并且添加进了request实例变量中。如果你想获取http体,要么继续读入到request对象中,要么记得把开始的字节加进去。

另外一个没有处理的是常连接keep-alive。这也是在-[HTTPRequestHandler receiveIncomingDataNotification:]方法中处理,那里我已经做了注释。实际上简单的做法是设置每个response的Connection http头,告诉客户端你不处理keep-alive。HttpReseponseHandler不会使用请求中的Content-Type头。如果你想处理这个,你应该在+[HTTPResponseHandler handlerClassForRequest:method:url:headerFields:]方法中进行处理。

最后, 服务器不处理SSL/TLS。因为在本地网络而言数据传输是相对安全的。如果在开放的internet中要提供一个安全链接,在socket这一层需要进行大量的改动。如果安全对你来说很主要,你可能不应该自己实现服务器——可能的话,采用一种成熟的TLSHTTP服务器,并只在客户端进行处理。用Cocoa处理客户端的安全是很容易的——通过CFReadStream和NSURLConnection,它完全是自动的和透明的。

结语

下载: the sample app TextTransfer.zip(45kB) ,包含 HTTPServer 和 HTTPResponseHandler 类。虽然主流的HTTP 服务器都是庞大而复杂的软件,但不意味着它必须是庞大和复杂的— 本文的实现只有两个类,然而也可以进行配置和扩展。

当然,我们的目的不是把它当作一个复杂web服务器使用,它适用于在你的iPhone或Mac程序中作为一个轻量级的数据共享的入口。

转自:http://blog.csdn.net/smallsky_keke/article/details/7311024

原文:A simple, extensible HTTP server in Cocoa

原文地址:http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html

http是计算机之间通讯协议的比较简单的一种。在iPhone上,由于没有同步数据和文件共享的APIs,实现iPhone应用程序与PC之间的数据传输的最佳方式就是在程序中嵌入一个http服务器。在这篇帖子理,我将演示如何写一个简单但可以扩展的http服务器。该服务器类也可在Mac下运行。

介绍

示例程序运行效果如下:

程序很简单:你可以编辑和保存一个文本文件(总是保存在同一个文件)。当程序还在运行的时候,它会在8080端口上运行一个http服务。如果你请求”/”路径,它会返回文本文件的内容。其他请求会导致501错误。要想将文本文件从iPhone程序传输到PC,只需在浏览器中输入iPhone的ip地址并加上端口号8080。

HTTPServer类和 HTTPResponseHandler 类

该http服务器涉及两个类:服务器(负责监听连接请求并读取数据,直到http头结束),响应处理(发送响应并从http头以后进行数据读取)。

设计server类和response类的目的是简化其他response类的实现,只需要实现这3个方法:

  • canHandleRequest:method:url:headerFields: 指定该response是否会对某个请求进行处理。
  • startResponse:开始进行响应。
  • load: — 所有子类都应该实现 +[NSObject load] 方法,并将自己向基类进行注册。

这就是一个最基本的http服务器,但它可以让你很快在程序中集成http通讯的功能。

建立Socket监听

包括http在内的大部分服务器通讯,都要从建立socket监听开始。Cocoa的Sockets可以完全采用BSDsockets代码实现,但使用CoreFoundation 的CFSocket API要容易一些。不幸的是,虽然已经“尽可能大地”简化——但为了打开一个socket,你仍然不得不写大量模式化的代码。

.

HTTPServer的start method:

socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,

IPPROTO_TCP, 0, NULL, NULL);

if (!socket)

{

[self errorWithName:@"Unable to create socket."];

return;

}

int reuse = true;

int fileDescriptor = CFSocketGetNative(socket);

if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR,

(void *)&reuse, sizeof(int)) != 0)

{

[self errorWithName:@"Unable to set socket options."];

return;

}

struct sockaddr_in address;

memset(&address, 0, sizeof(address));

address.sin_len = sizeof(address);

address.sin_family = AF_INET;

address.sin_addr.s_addr = htonl(INADDR_ANY);

address.sin_port = htons(HTTP_SERVER_PORT);

CFDataRef addressData =

CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address));

[(id)addressData autorelease];

if (CFSocketSetAddress(socket, addressData) != kCFSocketSuccess)

{

[self errorWithName:@"Unable to bind socket to address."];

return;

}

这么多的代码只是在做一件事情:打开socket,监听来自HTTP_SERVER_PORT(8080端口)的TCP连接。

此外,我使用了SO_REUSEADDR。这是为了重用已经打开的端口(这是因为,如果我们在程序崩溃后立即重新打开程序,经常会导致端口被占用)。

接受请求

socket一旦建立,事情就变得简单了。对于每个监听到的连接通知,我们可以从fileDescriptor 构造一个NSFileHandle 以接受请求。

listeningHandle = [[NSFileHandle alloc]

initWithFileDescriptor:fileDescriptor

closeOnDealloc:YES];

[[NSNotificationCenter defaultCenter]

addObserver:self

selector:@selector(receiveIncomingConnectionNotification:)

name:NSFileHandleConnectionAcceptedNotification

object:nil];

[listeningHandle acceptConnectionInBackgroundAndNotify];

当 receiveIncomingConnectionNotification:方法被调用时,每个新来的请求都会创建一个NSFileHandle. 继续跟踪下去你会发现:

  • 对于socket fileDescriptor监听到的新连接请求,从fileDescriptor“手动”创建了一个file handle(listeningHandle) 。
  • 1 file handle (listeningHandle) manually created from the socket fileDesriptor to listen on the socket for new connections.
  • 对于listeningHandle接收到的每个新连接,会“自动”创建一个file handle。我们会不停地监听这些新的handles(key会记录在inconmingRequests字典中),并记录了每个连接的数据。

现在,我们收到了一个新的自动创建的file handle,我们创建了一个http请求消息CFHTTPMessageRef(用于储存请求数据),并把这些对象存在incomingRequests字典以便下次访问CFHTTPMessageRef。

CFHTTPMessageRef存放并对请求数据进行解析。 ,我们可以调用CFHTTMessageHeaderComplete()函数进行判断,一直到http头完成并产生一个response handler时 。

在HTTPServerreceiveIncomingDataNotification:方法中,我们生成了response handler。

if(CFHTTPMessageIsHeaderComplete(incomingRequest))

{

HTTPResponseHandler *handler =

[HTTPResponseHandler

handlerForRequest:incomingRequest

fileHandle:incomingFileHandle

server:self];

[responseHandlers addObject:handler];

[self stopReceivingForFileHandle:incomingFileHandle close:NO];

[handler startResponse];

return;

}

服务器停止监听连接的同时并不关闭它,因为file handle被传递给HTTPResponseHandler以便HTTP响应能发至相同的file handle。

弹性的响应处理

+[HTTPResponseHandlerhandlerForRequest:fileHandle:server:]方法到底返回哪个子类取决于response的内容。它遍历已注册的handlers数组(以排序),轮询每个handler看哪个愿意处理这个请求。

+ (Class)handlerClassForRequest:(CFHTTPMessageRef)aRequest

method:(NSString *)requestMethod

url:(NSURL *)requestURL

headerFields:(NSDictionary *)requestHeaderFields

{

for (Class handlerClass in registeredHandlers)

{

if ([handlerClass canHandleRequest:aRequest

method:requestMethod

url:requestURL

headerFields:requestHeaderFields])

{

return handlerClass;

}

}

return nil;

}

因此,所有HTTPResponseHandlers都需要向基类进行注册。最简单的方法是在每个子类的+NSObjectload方法中进行注册。

+ (void)load

{

[HTTPResponseHandler registerHandler:self];

}

在这里,仅有的response handler是AppTextFileResponse。这个类负责处理requestURL等于”/”的请求。

+ (BOOL)canHandleRequest:(CFHTTPMessageRef)aRequest

method:(NSString *)requestMethod

url:(NSURL *)requestURL

headerFields:(NSDictionary *)requestHeaderFields

{

if ([requestURL.path isEqualToString:@"/"])

{

return YES;

}

return NO;

}

随后,AppTextFileResponse在startResponse方法里进行同步响应,把程序保存的文本文件内容写入响应消息里。

- (void)startResponse

{

NSData *fileData =

[NSData dataWithContentsOfFile:[AppTextFileResponse pathForFile]];

CFHTTPMessageRef response =

CFHTTPMessageCreateResponse(

kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);

CFHTTPMessageSetHeaderFieldValue(

response, (CFStringRef)@"Content-Type", (CFStringRef)@"text/plain");

CFHTTPMessageSetHeaderFieldValue(

response, (CFStringRef)@"Connection", (CFStringRef)@"close");

CFHTTPMessageSetHeaderFieldValue(

response,

(CFStringRef)@"Content-Length",

(CFStringRef)[NSString stringWithFormat:@"%ld", [fileData length]]);

CFDataRef headerData = CFHTTPMessageCopySerializedMessage(response);

@try

{

[fileHandle writeData:(NSData *)headerData];

[fileHandle writeData:fileData];

}

@catch (NSException *exception)

{

// Ignore the exception, it normally just means the client

// closed the connection from the other end.

}

@finally

{

CFRelease(headerData);

[server closeHandler:self];

}

}

[servercloseHandler:self]; 告诉服务端从当前handlers中移除HTTPResponseHandler。

服务端会调用endResponse移除handler(在关闭连接时——因为handler不支持keep-alive即常连接)。

有待完善之处

最大的任务解析http请求体没有被实现。因为正常的http体解析过程十分复杂。http体的长度在content-length头中指定,但可能不会被指定——因此无法直到http体何时结束。http体还可能是编码的,编码方式有各种各样的:包括chunk, quoted-printable,base64, gzip— 而每一种的处理都完全不同。

我重来没想过实现一种普遍的解决方案。通常要根据你的实际需要而定,你可以在HTTPRequestHandler的receiveIncomingDataNotification:方法中处理请求体。默认,我忽略了http请求头之后的所有数据。

提示:

HTTPRequestHandlerreceiveIncomingDataNotification:方法第一次调用时,Http体的开始字节已经从fileHandle中获得并且添加进了request实例变量中。如果你想获取http体,要么继续读入到request对象中,要么记得把开始的字节加进去。

另外一个没有处理的是常连接keep-alive。这也是在-[HTTPRequestHandler receiveIncomingDataNotification:]方法中处理,那里我已经做了注释。实际上简单的做法是设置每个response的Connection http头,告诉客户端你不处理keep-alive。HttpReseponseHandler不会使用请求中的Content-Type头。如果你想处理这个,你应该在+[HTTPResponseHandler handlerClassForRequest:method:url:headerFields:]方法中进行处理。

最后, 服务器不处理SSL/TLS。因为在本地网络而言数据传输是相对安全的。如果在开放的internet中要提供一个安全链接,在socket这一层需要进行大量的改动。如果安全对你来说很主要,你可能不应该自己实现服务器——可能的话,采用一种成熟的TLSHTTP服务器,并只在客户端进行处理。用Cocoa处理客户端的安全是很容易的——通过CFReadStream和NSURLConnection,它完全是自动的和透明的。

结语

下载: the sample app TextTransfer.zip(45kB) ,包含 HTTPServer 和 HTTPResponseHandler 类。虽然主流的HTTP 服务器都是庞大而复杂的软件,但不意味着它必须是庞大和复杂的— 本文的实现只有两个类,然而也可以进行配置和扩展。

当然,我们的目的不是把它当作一个复杂web服务器使用,它适用于在你的iPhone或Mac程序中作为一个轻量级的数据共享的入口。

分享到:
评论

相关推荐

    js实现类似iphone图片切换效果简单实用

    实现类似于iphone手机桌面图片切换效果,简单使用!

    iphone用Animation实现动画效果

    用animation实现UIView动画效果,简单上中下移动

    使用css绘制的iphoneX界面

    使用css绘制的苹果X手机界面,配合JQ可以体现出更多效果

    iphone通讯录的简单实现

    用UITableView实现我的通讯录的功能,包括索引功能、名字的头字母的排序等,搜索功能还没实现。

    如何设置IPAD iphone邮箱

    电子邮件是我们日常生活中必不可少的实用工具,尤其是在公司商务发面发挥着重要的作用... 至于其它邮 箱的话则可在菜单中选择添加其它,通过设置IMAP、POP、Exchange等参数实现移动电子邮件服务,相对起来就比较麻烦。

    IPHONE实现转盘

    IPHONE实现转盘效果,通过角度计算实现罗盘转动,以最简易的代码实现效果,希望大家喜欢!

    Android仿IPhone简易锁屏(源码)

    Android上简易实现IPhone锁屏。

    iphone跑马灯效果

    iphone跑马灯效果,可以实现简单的跑马灯效果。其实跑马灯可以通过多种方法实现。

    iPhone 的 Github 客户端 GitPhone.zip

    iPhone 的 Github 客户端 GitPhone ,GitPhone 是 iPhone 上的一个体验的 Github 客户端 App,可实现简单的查看资...

    iPhone中Sqlite的使用

    在iPhone中简单应用Sqlite3.0数据库,实现创建表,初始化数据,带参查询,简单查询,以及插入数据信息。

    iPhoneX适配

    简单的适配了iPhone X ,以及其它一些机型,代码主要实现了屏幕底部按钮,tableview等适配

    java实现IPHONE推送功能技术文档

    这个压缩包里面包含三个DOC文件:1.iphone推送java实现.doc 2.iphone推送简单JAVA示例.doc 3.实现iphone推送服务端原理.doc 三个文档很全面的指导学者学习怎么用java实现ios推送功能,不仅仅是讲解怎么实现,还讲解...

    react-一个使用react和redux实现的仿iphone6s计算器

    一个使用react和redux实现的仿iphone6s计算器

    Cocos2d-iphone 开发教程

    复杂讲,Cocos2d-iPhone是基亍 GNU LGPL v3 license的,考虑到在iPhone的平台上无法实现发布第三方劢态链接库,因此他扩展了上述协议,允许通过静态链接库戒者直接使用源代码的方式实现你的应用,而丌必公开你的源...

    北亚Iphone苹果手机数据恢复软件 v3.74.zip

    北亚Iphone苹果手机数据恢复软件由北京北亚数据恢复中心研发,能够实现iPhone、ipad、iTouch等多种苹果平台的短信、通讯录、通话记录、QQ聊天记录、微信删除的数据恢复,让用户不用再当心手机里的重要文件丢失。...

    iphone 简单动画示例源码

    iphone 简单动画示例源码,实现了点击按钮,按钮再画面上漂流的功能。

    java版的简单iphone推送

    用java实现了一个简单的iphone推送,能够进行简单的推送,已经过测试,iphone客户端能够接收到服务器推送的内容

    iPhone格式转换器6.2好易iPhone视频转换软件)

    同时iPhone格式转换器具有强大的视频批量转换和简易可视化编辑功能,可以截取部分精彩视频转换,实现跨格式视频合并成一个文件,并且可以从视频播放中抓取精美图片。好易-iPhone格式转换器预设了iPhone常用输出格式...

    苹果iphone手机数据恢复软件 v2.1 免费版.zip

    软件不仅能实现iphone手机数据恢复,还能实现包括iPad等设备的数据恢复。软件可恢复短信、通讯录、通话记录、QQ聊天记录等诸多数据。软件界面美观简洁、简单全面、实用方便,无需培训,即可快速上手,轻轻松松完成...

Global site tag (gtag.js) - Google Analytics