iphone上实现HTTP server
来源:互联网 发布:淘宝哪家银店真的吗 编辑:程序博客网 时间:2024/06/06 13:13
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,你仍然不得不写大量模式化的代码。
01
HTTPServer的start method:
02
03
socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,
04
05
IPPROTO_TCP, 0, NULL, NULL);
06
07
if
(!socket)
08
09
{
10
11
[self errorWithName:@
"Unable to create socket."
];
12
13
return
;
14
15
}
16
17
18
19
int
reuse =
true
;
20
21
int
fileDescriptor = CFSocketGetNative(socket);
22
23
if
(setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR,
24
25
(
void
*)&reuse,
sizeof
(
int
)) != 0)
26
27
{
28
29
[self errorWithName:@
"Unable to set socket options."
];
30
31
return
;
32
33
}
34
35
36
37
struct
sockaddr_in address;
38
39
memset
(&address, 0,
sizeof
(address));
40
41
address.sin_len =
sizeof
(address);
42
43
address.sin_family = AF_INET;
44
45
address.sin_addr.s_addr = htonl(INADDR_ANY);
46
47
address.sin_port = htons(HTTP_SERVER_PORT);
48
49
CFDataRef addressData =
50
51
CFDataCreate(NULL, (
const
UInt8 *)&address,
sizeof
(address));
52
53
[(id)addressData autorelease];
54
55
56
57
if
(CFSocketSetAddress(socket, addressData) != kCFSocketSuccess)
58
59
{
60
61
[self errorWithName:@
"Unable to bind socket to address."
];
62
63
return
;
64
65
}
这么多的代码只是在做一件事情:打开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) 。
- 对于listeningHandle接收到的每个新连接,会“自动”创建一个file handle。我们会不停地监听这些新的handles(key会记录在inconmingRequests字典中),并记录了每个连接的数据。
现在,我们收到了一个新的自动创建的file handle,我们创建了一个http请求消息CFHTTPMessageRef(用于储存请求数据),并把这些对象存在incomingRequests字典以便下次访问CFHTTPMessageRef。
CFHTTPMessageRef存放并对请求数据进行解析。 ,我们可以调用CFHTTMessageHeaderComplete()函数进行判断,一直到http头完成并产生一个response handler时 。
在HTTPServerreceiveIncomingDataNotification:方法中,我们生成了response handler。
01
if
(CFHTTPMessageIsHeaderComplete(incomingRequest))
02
03
{
04
05
HTTPResponseHandler *handler =
06
07
[HTTPResponseHandler
08
09
handlerForRequest:incomingRequest
10
11
fileHandle:incomingFileHandle
12
13
server:self];
14
15
16
17
[responseHandlers addObject:handler];
18
19
[self stopReceivingForFileHandle:incomingFileHandle close:NO];
20
21
22
23
[handler startResponse];
24
25
return
;
26
27
}
服务器停止监听连接的同时并不关闭它,因为file handle被传递给HTTPResponseHandler以便HTTP响应能发至相同的file handle。
弹性的响应处理
+ [HTTPResponseHandlerhandlerForRequest:fileHandle:server:]方法到底返回哪个子类取决于 response的内容。它遍历已注册的handlers数组(以排序),轮询每个handler看哪个愿意处理这个请求。
01
+ (Class)handlerClassForRequest:(CFHTTPMessageRef)aRequest
02
03
method:(NSString *)requestMethod
04
05
url:(NSURL *)requestURL
06
07
headerFields:(NSDictionary *)requestHeaderFields
08
09
{
10
11
for
(Class handlerClass in registeredHandlers)
12
{
13
14
if
([handlerClass canHandleRequest:aRequest
15
method:requestMethod
16
url:requestURL
17
headerFields:requestHeaderFields])
18
{
19
return
handlerClass;
20
}
21
22
}
23
24
return
nil;
25
26
}
因此,所有HTTPResponseHandlers都需要向基类进行注册。最简单的方法是在每个子类的+NSObjectload方法中进行注册。
1
+ (
void
)load
2
{
3
4
[HTTPResponseHandler registerHandler:self];
5
6
}
在这里,仅有的response handler是AppTextFileResponse。这个类负责处理requestURL等于”/”的请求。
01
+ (
BOOL
)canHandleRequest:(CFHTTPMessageRef)aRequest
02
03
method:(NSString *)requestMethod
04
05
url:(NSURL *)requestURL
06
07
headerFields:(NSDictionary *)requestHeaderFields
08
09
{
10
11
if
([requestURL.path isEqualToString:@
"/"
])
12
{
13
return
YES;
14
}
15
16
return
NO;
17
18
}
随后,AppTextFileResponse在startResponse方法里进行同步响应,把程序保存的文本文件内容写入响应消息里。
01
- (
void
)startResponse
02
03
{
04
05
NSData *fileData =
06
07
[NSData dataWithContentsOfFile:[AppTextFileResponse pathForFile]];
08
09
10
11
CFHTTPMessageRef response =
12
13
CFHTTPMessageCreateResponse(
14
15
kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
16
17
CFHTTPMessageSetHeaderFieldValue(
18
19
response, (CFStringRef)@
"Content-Type"
, (CFStringRef)@
"text/plain"
);
20
21
CFHTTPMessageSetHeaderFieldValue(
22
23
response, (CFStringRef)@
"Connection"
, (CFStringRef)@
"close"
);
24
25
CFHTTPMessageSetHeaderFieldValue(
26
27
response,
28
29
(CFStringRef)@
"Content-Length"
,
30
31
(CFStringRef)[NSString stringWithFormat:@
"%ld"
, [fileData length]]);
32
33
CFDataRef headerData = CFHTTPMessageCopySerializedMessage(response);
34
35
36
37
@
try
38
{
39
40
[fileHandle writeData:(NSData *)headerData];
41
42
[fileHandle writeData:fileData];
43
44
}
45
46
@
catch
(NSException *exception)
47
48
{
49
50
// Ignore the exception, it normally just means the client
51
52
// closed the connection from the other end.
53
54
}
55
56
@finally
57
{
58
59
CFRelease(headerData);
60
61
[server closeHandler:self];
62
63
}
64
65
}
[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/kmyhy/article/details/7031329,不过他这个图已经不能看了!
- iphone上实现HTTP server
- iphone上实现HTTP server
- iphone上实现HTTP server
- 在iPhone上实现简单Http服务
- 在iPhone上实现简单Http服务
- 在iPhone上实现简单Http服务
- 在iPhone上实现简单Http服务
- 在iPhone上实现简单Http服务
- 在iPhone上实现简单Http服务
- 在iPhone上实现Http服务-Proxy
- 在iPhone上实现简单Http服务
- Firefox OS上实现HTTP Web Server
- IOS 在iPhone上实现简单Http服务
- IOS 在iPhone上实现简单Http服务
- IOS 在iPhone上实现简单Http服务
- IOS 在iPhone上实现简单Http服务
- 实现Http Server
- libevent 实现http server
- zoj 3610
- 杭电_3790_最短路径问题
- MyEclipse 快捷键
- hdu 1251 统计难题
- 【初学动态规划】之装箱问题
- iphone上实现HTTP server
- 关于const 与 引用 在函数传参的时候 区别
- session丢失看来,真的是session_start放首位之问题
- iis7.5应用程序池自动停止
- C#之I/O系统(五)
- hdu 2846 Repository
- Java 基本数据类型
- 异常
- Android Library 工程实现模块复用