IOS開發之CocoaAsyncSocket學習

來源:互聯網
上載者:User

以下內容翻譯自:

http://code.google.com/p/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

基本上是兩步:

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

 

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

 

測試得到的日誌:

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的操作。這裡就不多說了。

原文:http://marshal.easymorse.com/archives/4533

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.