你好,歡迎來到IOS教程網

 Ios教程網 >> IOS訊息 >> 關於IOS >> iOS開發之CocoaAsyncSocket學習

iOS開發之CocoaAsyncSocket學習

編輯:關於IOS

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。

編寫的示例。

准備工作:如何在iOS項目中使用

基本上是兩步:

將CocoaAsyncSocket項目中的.h和.m文件拖拽到自己項目的Classes目錄中
添加framework:CFNetwork

編寫簡單的TCP連接

編寫個簡單的TCP連接應用。HTTP其實就是建立在TCP協議上的。這裡就用向網站發起請求和獲得響應來演示。

為了形象說明,先手工模擬一下HTTP。這需要用到telnet工具,這是個命令行工具,如果在命令行裡敲:

C:UsersMarshal 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.1nn 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.1nn 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.1nn 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的操作。這裡就不多說了。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved