当前位置:首页 > 编程开发

CocoaAsyncSocket学习

webgou14年前 (2013-02-05)编程开发147

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

基本上是两步:

  1. 将CocoaAsyncSocket项目中的.h和.m文件拖拽到自己项目的Classes目录中
  2. 添加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 <UIKit/UIKit.h>

#import "AsyncSocket.h"

@interface SocketDemosViewController : UIViewController<AsyncSocketDelegate>

然后,在实现代码中:

- (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);
}

这些监听需要创建和使用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];

执行的日志如下:

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)onSocketDidDisconnect:(AsyncSocket *)sock{
 NSLog(@"socket did disconnect");
}
- (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];

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

编写服务器端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;
}

测试得到的日志:
 
2011-07-20 16:14:30.338 SocketDemos[860:707] start udp server    
2011-07-20 16:14:42.829 SocketDemos[860:707] received data: Hello everyone.
发送组播和前面的UDP发送类似,只是多了要做join group的操作。这里就不多说了。

扫描二维码推送至手机访问。

版权声明:本文由知了博客发布,如需转载请注明出处。

本文链接:https://www.webgou.info/?id=544

分享给朋友:

“CocoaAsyncSocket学习 ” 的相关文章

S3C2440 WINCE4.2 BSP的串口修改

S3C2440 WINCE4.2 BSP的串口修改 说明:在基于SMDK2440的BSP中,S3C2440的UART0初始化为了COM1,UART1用作了WindowsCE 系统的debug串口,用户程序不可用,而UART2则工作在IrDA模式.也就是说,用户程序只有一个串 口可以使用,如果要连…

三本优秀的Python教程

 因为突然就对PY感兴趣了~囧~…所以决定找教程学习下(话说到现在还没完整学完一门语言呢…T_T)。相对于其他的程序语言,Python的教程显得单薄。这里推荐三份流传广泛的Python教程,如果你正准备找教程学py,希望本文会对你有用。...…

Android图片大小调整动态实现方法

Android操作系统中对于图片的操作我们在以前的文章中也有所介绍。不过对于图片的大小调整往往都局限于固定的调整。如何才能满足动态大小调整呢?我们在这里就为大家详细介绍有关Android图片大小调整的动态实现方法。昨天,动态获取图片资源获取的很爽啊,后来,换了一张png,128*128的,Run a…

Android屏幕大小相关技巧应用指南

Android应用程序中屏幕大小的设置大家应该都比较清楚,不过如何才能让屏幕自己适应环境而改变大小呢?在这里我们就可以为大家详细介绍一下有关Android屏幕大小的自适应方式,帮助大家理解。不同的Android target会有不同的大小,应用程序的界面需要针对不同的大小调整界面元素的尺寸。而且An…

WinXP与WinCE串口的运行机制之比较

    WinXP与WinCE串口的运行机制之比较…

音频文件的采样频率(khz)与位速(kbps)

     数码音频系统是通过将声波波形转换成一连串的二进制数据来再现原始声音的,实现这个步骤使用的设备是模/数转换器(A/D)它以每秒上万次的速 率对声波进行采样,每一次采样都记录下了原始模拟声波在某一时刻的状态,称之为样本。将一串的样本连接起来,就可以描述一…

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。