CocoaAsyncSocket的使用

CocoaAsyncSocket支持tcp和udp。其中:

AsyncSocket类是支持TCP的

AsyncUdpSocket是支持UDP的

AsyncSocket是封装了CFSocket和CFSteam的TCP/IP socket网络库。它提供了异步操作,本地cocoa类的基于delegate的完整支持。主要有以下特性:

队列的非阻塞的读和写,而且可选超时。你可以调用它读取和写入,它会当完成后告知你。

自动的socket接收。如果你调用它接收连接,它将为每个连接启动新的实例,当然,也可以立即关闭这些连接。

委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可以通过委托模式调用。

基于run loop的,而不是线程的。虽然可以在主线程或者工作线程中使用它,但你不需要这样做。它异步的调用委托方法,使用NSRunLoop。委托方法包括socket的参数,可让你在多个实例中区分。

自包含在一个类中。你无需操作流或者socket,这个类帮你做了全部。

支持基于IPV4和IPV6的TCP流

AsyncUdpSocket是UDP/IP socket网络库,包装自CFSocket。它的工作很像TCP版本,只不过是用于处理UDP的。它包括基于非阻塞队列的发送接收操作,完整的委托支持,基于runloop,自包含的类,以及支持IPV4和IPV6。

以下内容是根据官方网站参考:

http://code.google.com/p/cocoaasyncsocket/wiki/Reference_AsyncSocket

编写的示例。

准备工作:如何在iOS项目中使用

可按照官网链接执行:

http://code.google.com/p/cocoaasyncsocket/wiki/iPhone

基本上是两步:

将CocoaAsyncSocket项目中的.h和.m文件拖拽到自己项目的Classes目录中

添加framework:CFNetwork

编写简单的TCP连接

编写个简单的TCP连接应用。HTTP其实就是建立在TCP协议上的。这里就用向网站发起请求和获得响应来演示。

为了形象说明,先手工模拟一下HTTP。这需要用到telnet工具,这是个命令行工具,如果在命令行里敲:

C:\Users\Marshal Wu>telnet

‘telnet’ 不是内部或外部命令,也不是可运行的程序

或批处理文件。

说明你使用的是windows vista或者windows7,因为windows xp是默认安装该软件的。

我用的是Mac OSX,上面自带这个工具。如果你出现上面的问题,可参照vista下使用telnet的做法安装telnet。

然后,可以使用这个工具发出socket信息,并接收socket返回信息。下面说一下步骤,如图:

下面用CocoaAsyncSocket来实现。

首先是要实现相关的delegate:

#import

#import "AsyncSocket.h"

@interface SocketDemosViewController : UIViewController

然后,在实现代码中:

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{

NSLog(@"did connect to host");

}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

NSLog(@"did read data");

NSString* message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

NSLog(@"message is: \n%@",message);

}

AsyncSocketDelegate中的方法都是可选的。我实现了对建立连接后以及读取数据的监听。

这些监听需要创建和使用AsyncSocket实例时才能被用到。下面就是这部分代码:

- (void)viewDidLoad {

[super viewDidLoad];

AsyncSocket *socket=[[AsyncSocket alloc] initWithDelegate:self];

[socket connectToHost:@"www.baidu.com" onPort:80 error:nil];

[socket readDataWithTimeout:3 tag:1];

[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];

我把这部分代码直接写到controller的viewDidLoad中了。

执行的日志如下:

2011-07-19 17:17:46.545 SocketDemos[27120:207] did connect to host

2011-07-19 17:17:46.620 SocketDemos[27120:207] did read data

2011-07-19 17:17:46.621 SocketDemos[27120:207] message is:

HTTP/1.1 200 OK

Date: Tue, 19 Jul 2011 09:17:46 GMT

Server: BWS/1.0

Content-Length: 7691

Content-Type: text/html;charset=gb2312

Cache-Control: private

Expires: Tue, 19 Jul 2011 09:17:46 GMT

Set-Cookie: BAIDUID=9389BA38262D7997D220A564154CCA87:FG=1; expires=Tue, 19-Jul-41 09:17:46 GMT; path=/; domain=.baidu.com

P3P: CP=" OTI DSP COR IVA OUR IND COM "

Connection: Keep-Alive

这里的HTTP响应被截断了,因为我们不是要编写真的接收HTTP响应的功能,因此这个缺陷可以忽略。

本来HTTP请求应该是由服务器端来关闭,比如使用telent访问看到的是这样的结尾:

因此,HTTP响应没有完全接收下来,服务器端未断掉连接。可以在客户端关闭连接,这样:

[socket readDataWithTimeout:3 tag:1];

[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];

[socket disconnect];

另外,可以实现delegate中的这个方法:

- (void)onSocketDidDisconnect:(AsyncSocket *)sock{

NSLog(@"socket did disconnect");

}

这样就可以在日志中监控到关闭连接的信息。

TCP连接读取指定长度的数据。

socket连接,经常碰到这样的需求,读取固定长度的字节。这可以通过下面的示例实现。

还是基于HTTP连接做演示。比如取2次,每次50字节。然后停止socket。

可以这样写:

- (void)viewDidLoad {

[super viewDidLoad];

socket=[[AsyncSocket alloc] initWithDelegate:self];

[socket connectToHost:@"www.baidu.com" onPort:80 error:nil];

data=[[NSMutableData dataWithLength:50] retain];

[socket readDataToLength:50 withTimeout:5 tag:1];

[socket readDataToLength:50 withTimeout:5 tag:2];

[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];

在delegate中,主要是这个方法起作用:

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)_data withTag:(long)tag{

NSLog(@"did read data");

NSString* message = [[[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding] autorelease];

NSLog(@"message is: \n%@",message);

if (tag==2) {

[socket disconnect];

}

}

日志类似这样:

标红色的是两次取出的字节内容。

编写服务器端Socket

编写了Echo示例,说明最简单的服务器端Socket写法。Echo就是回声,通过telnet发送什么,服务器端就返回什么。类似这样:

服务器端,需要监听客户端的连接。等待客户端发来信息。代码是这样的:

socket=[[AsyncSocket alloc] initWithDelegate:self];

NSError *err = nil;

if ([socket acceptOnPort:4322 error:&err]) {

NSLog(@"accept ok.");

}else {

NSLog(@"accept failed.");

}

if (err) {

NSLog(@"error: %@",err);

}

这一步如果成功,应该只有一个日志信息:

2011-07-20 12:27:03.228 SocketDemos[611:707] accept ok.

这时如果有客户端与之建立连接,比如通过telnet。会依次调用AsyncSocket 的delegate的如下方法:

onSocket:didAcceptNewSocket: AsyncSocket创建了新的Socket用于处理和客户端的请求,如果这个新socket实例你不打算保留(retain),那么将拒绝和该客户端连接

onSocket:wantsRunLoopForNewSocket:,提供线程的runloop实例给AsyncSocket,后者将使用这个runloop执行socket通讯的操作

onSocketWillConnect:,将要建立连接,这时可以做一些准备工作,如果需要的话

onSocket:didConnectToHost:port:,这个方法是建立连接后执行的,一般会在这里调用写入或者读取socket的操作

在Echo示例中,不打算执行多线程,也不想支持多客户端连接,而且服务器端和客户端将建立长连接。直至客户端断开连接,服务器端才释放相应的socket。

代码如下:

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{

if (!acceptSocket) {

acceptSocket=[newSocket retain];

NSLog(@"did accept new socket");

}

}

- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket{

NSLog(@"wants runloop for new socket.");

return [NSRunLoop currentRunLoop];

}

- (BOOL)onSocketWillConnect:(AsyncSocket *)sock{

NSLog(@"will connect");

return YES;

}

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{

NSLog(@"did connect to host");

[acceptSocket readDataWithTimeout:-1 tag:1];

}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

NSLog(@"did read data");

NSString* message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

NSLog(@"message is: \n%@",message);

[acceptSocket writeData:data withTimeout:2 tag:1];

}

- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag{

NSLog(@"message did write");

[acceptSocket readDataWithTimeout:-1 tag:1];

}

- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err{

NSLog(@"onSocket:%p willDisconnectWithError:%@", sock, err);

}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock{

NSLog(@"socket did disconnect");

[acceptSocket release];

acceptSocket=nil;

}

这里timeout设置为-1,这样就可以保持长连接状态。

编写简单的UDP应用

首先,编写发送UDP数据报的示例。这需要有个服务器端能接收到内容。用Java写了个简单的接收端:

public static void main(String[] args) throws IOException {

InetSocketAddress address = new InetSocketAddress("0.0.0.0", 5555);

DatagramSocket datagramSocket=new DatagramSocket(address);

System.out.println("start udp server");

byte[] buffer=new byte[1024];

for(;;){

DatagramPacket datagramPacket=new DatagramPacket(buffer, buffer.length);

datagramSocket.receive(datagramPacket);

System.out.println("receive data:"+new String(datagramPacket.getData(),0,datagramPacket.getLength()));

}

}

下面写发送的代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc]initWithDelegate:self];

NSData *data=[@"Hello from iPhone" dataUsingEncoding:NSUTF8StringEncoding];

[socket sendData:data toHost:@"192.168.0.165" port:5555 withTimeout:-1 tag:1];

NSLog(@"send upd complete.");

执行后,在接收端成功输出如下内容:

下面,写个接收端的代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc] initWithDelegate:self];

NSError *error = nil;

[socket bindToPort:5555 error:&error];

if (error) {

NSLog(@"error: %@",error);

}

[socket receiveWithTimeout:-1 tag:1];

NSLog(@"start udp server");

另外,至少写这个delegate方法:

- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock

didReceiveData:(NSData *)data

withTag:(long)tag

fromHost:(NSString *)host

port:(UInt16)port{

NSLog(@"received data: %@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);

return YES;

}

发送端,还是用java写个测试代码:

public static void main(String[] args) throws IOException {

DatagramSocket datagramSocket = new DatagramSocket();

byte[] buffer = "Hello!".getBytes();

DatagramPacket datagramPacket = new DatagramPacket(buffer,

buffer.length, new InetSocketAddress("192.168.0.144", 5555));

datagramSocket.send(datagramPacket);

}

在iPhone日志中:

2011-07-20 15:23:33.571 SocketDemos[795:707] start udp server

2011-07-20 15:23:47.395 SocketDemos[795:707] received data: Hello!

收到了数据报。

使用UDP发送和接收组播

这里主要关注的是接收,一方面是需求上要求,另一方面,碰到过Android Wifi获取组播问题,担心iOS也有类似的机制。后来测试发现没有那么麻烦(打开组播锁)。

为了测试,还是用java编写了个发送UDP广播的简单代码:

public static void main(String[] args) throws IOException {

int port=3333;

MulticastSocket socket=new MulticastSocket(port);

InetAddress address=InetAddress.getByName("239.0.0.1");

socket.joinGroup(address);

byte[] data="Hello everyone.".getBytes();

DatagramPacket datagramPacket=new DatagramPacket(data,data.length,address,port);

socket.send(datagramPacket);

System.out.println("send ok.");

编写的iOS代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc] initWithDelegate:self];

NSError *error = nil;

[socket bindToPort:3333 error:&error];

[socket enableBroadcast:YES error:&error];

[socket joinMulticastGroup:@"239.0.0.1" error:&error];

if (error) {

NSLog(@"error: %@",error);

}

[socket receiveWithTimeout:-1 tag:1];

NSLog(@"start udp server");

delegate和上面接收普通UDP一模一样:

- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock

didReceiveData:(NSData *)data

withTag:(long)tag

fromHost:(NSString *)host

port:(UInt16)port{

NSLog(@"received data: %@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);

return YES;

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容