CocoaAsyncSocket 文檔3:介紹GCDAsyncSocket

原文地址:https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Intro_GCDAsyncSocket

GCDAsyncSocket is a TCP library. It’s built atop Grand Central Dispatch.

This page provides an introduction to the library.

GCDAsyncSocket是一個TCP庫。它建立在GCD技術之上。本頁就是介紹這個庫。

Initialization


The most common way to initialize an instance is simply like this:

socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

The delegate and delegate_queue are required in order for GCDAsyncSocket to invoke your delegate methods. The code above specifies “self” as the delegate, and instructs the library to invoke all delegate methods on the main thread.

Setting a delegate is likely a familiar operation. However, providing a delegateQueue may be a new concept. Most typical libraries are single-threaded. When it’s time to invoke a delegate method, they just call it. The libraries assume your delegate code is also single-thread. Or the libraries may be multi-threaded internally, but they assume your delegate code is only single-threaded, and designed to run only on the main thread. So they simply always invoke all delegate methods on the main thread.

GCDAsyncSocket, on the other hand, was designed for performance. It allows you to receive delegate callbacks on dedicated gcd queues of your choosing. This allows it to be used in high-performance servers, and can support thousands upon thousands of concurrent connections. But it also helps in typical applications. Want your UI to be a bit snappier? Ever considered moving that network processing code off the UI thread? Even today’s mobile devices have multiple CPU cores… perhaps it’s time to start taking advantage of them.

初始化


通常,初始化一個實例用如下方法:

socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

用到delegate和delegate_queue是爲了GCDAsyncsocket調用你的委託方法。上面的代碼指定self爲delegate(代理),然後通知庫在主線程調用委託方法。

設置代理可能是一個熟悉的操作。然而,使用delegateQueue可能是一個新的概念。大部分典型的庫是單線程的。當該調用委託方法的時候,調用就是了。庫假定你的委託代碼也是單線程。或庫可能是內部的多線程,但他們認爲你的delegate代碼只是單線程,並設計爲只在主線程中運行。所以他們在主線程中調用所有的委託方法。

另一方面,GCDAsyncsocket被設計爲高性能的。它允許在你指定的GCD隊列中,接受委託方法的回調。這允許它被用於高性能服務器,並且可以支持成千上萬的併發連接。同時,這對特定的應用也有幫助。想讓你的界面更加流暢麼?你考慮過把網絡線程代碼移出界面線程麼?甚至在如今移動設備都具有多核CPU的今天……也許是時候開始利用他們的優勢了。

Configuration


Most of the time no configuration is necessary. There are various configuration options (as described in the header file), but they’re mainly for advanced use cases.

Note: Security (TLS/SSL) is something you setup later. These protocols actually run on top of TCP (they’re not part of TCP itself.)

配置


大多數的時間沒有配置是必要的。有多種配置選項(如頭文件中所述),但它們主要用於高級用例。
注:安全(TLS / SSL)是你安裝後。這些協議是運行在TCP之上(他們不是TCP本身的一部分。)

Connecting


The most common way to connect is:

NSError *err = nil;
if (![socket connectToHost:@"deusty.com" onPort:80 error:&err]) // Asynchronous!
{
    // If there was an error, it's likely something like "already connected" or "no delegate set"
    NSLog(@"I goofed: %@", err);
}

The connect methods are asynchronous. What does this mean? It means when you call the connect methods, they start a background operation to connect to the desired host/port, and then immediately return. This asynchronous background operation will eventually either succeed or fail. Either way, the associated delegate method will be called:

- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port
{
    NSLog(@"Cool, I'm connected! That was easy.");
}

So if the connect method is asynchronous, why does it return a boolean and error? The only time this method will return NO is if something obvious prevents it from starting the connect operation. For example, if the socket is already connected, or if the delegate was never set.

There are actually several different connect methods available to you. They afford you different options such as:

  • Optionally specify a connect timeout.
    E.g. Fail if it doesn’t connect in 5 seconds
  • Optionally specify the interface to connect with E.g. Connect using
    bluetooth, or Connect using WiFi regardless of whether a wired
    connection is available.
  • Supply a raw socket address instead of a name/port pair E.g. I
    resolved an address using NSNetService, and I just want to connect to
    that address.

連接


通常是這樣連接的:

NSError *err = nil;
if (![socket connectToHost:@"deusty.com" onPort:80 error:&err]) // Asynchronous!
{
    // If there was an error, it's likely something like "already connected" or "no delegate set"
    NSLog(@"I goofed: %@", err);
}

連接方法是異步的。這是什麼意思?這意味着當你調用連接方法,他們開始一個後臺操作連接到所需的主機/端口,然後立即返回。這種異步的後臺操作最終會成功或失敗。無論哪種方式,關聯的委託方法都將被調用:

- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port
{
    NSLog(@"Cool, I'm connected! That was easy.");
}

這裏連接方法是異步的,什麼情況它返回布爾值和錯誤?唯一可情況返回NO的是,有一些明顯的阻礙,組織它開始連接操作。比如,如果Socket已連接,或者Delegate沒有設置。

實際上有幾種不同的連接方法可供您選擇。他們提供給你不同的選擇,如:

  • 可選擇指定連接超時 例如:超過五秒斷開連接
  • 可選擇指定連接的接口 例如:使用藍牙連接,或使用WiFi,無論此時有線連接是可用的。
  • 使用一個原始的套接字地址,而不是一個名稱/端口 例如:我決定用nsnetservice地址,我只是想連接到那個地址。

Reading & Writing


One of the best features of the library is “queued read/write operations”. What does that mean? A quick code example may explain it best:

NSError *err = nil;
if (![socket connectToHost:@"deusty.com" onPort:80 error:&err]) // Asynchronous!
{
    // If there was an error, it's likely something like "already connected" or "no delegate set"
    NSLog(@"I goofed: %@", err);
    return;
}

// At this point the socket is NOT connected.
// But I can start writing to it anyway!
// The library will queue all my write operations,
// and after the socket connects, it will automatically start executing my writes!
[socket writeData:request1 withTimeout:-1 tag:1];

// In fact, I know I have 2 requests.
// Why not just get them both out of the way now?
[socket writeData:request2 withTimeout:-1 tag:2];

// Heck, while I'm at it, I might as well queue up the read for the first response.
[socket readDataToLength:responseHeaderLength withTimeout:-1 tag:TAG_RESPONSE_HEADER];

You may have noticed the tag parameter. What’s that all about? Well, it’s all about convenience for you. The tag parameter you specify is not sent over the socket or read from the socket. The tag parameter is simply echo’d back to you via the various delegate methods. It is designed to help simplify the code in your delegate method.

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    if (tag == 1)
        NSLog(@"First request sent");
    else if (tag == 2)
        NSLog(@"Second request sent");
}

Tags are most helpful when it comes to reading:

#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_WELCOME)
    {
        // Ignore welcome message
    }
    else if (tag == TAG_CAPABILITIES)
    {
        [self processCapabilities:data];
    }
    else if (tag == TAG_MSG)
    {
        [self processMessage:data];
    }
}

You see, the TCP protocol is modeled on the concept of a single continuous stream of unlimited length. It’s critical to understand this - and is, in fact, the number one cause of confusion that we see.

Imagine that you’re trying to send a few messages over the socket. So you do something like this (in pseudocode):

socket.write("Hi Sandy.");
socket.write("Are you busy tonight?");

How does the data show up on the other end? If you think the other end will receive two separate sentences in two separate reads, then you’ve just fallen victim to a common pitfall! Gasp! But fear not! Your condition isn’t life threatening; it’s just a common cold. The cure can be found by reading the Common Pitfalls page.

Now that we have that out of the way, you may be wondering about those read methods. Here’s a few of them:

- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;

The first method, readDataToLength, reads and returns data of the given length. Let’s take a look at an example:

You’re writing the client-side of a protocol where the server sends responses with a fixed-length header. The header for all responses is exactly 8 bytes. The first 4 bytes contain various flags, etc. And the second 4 bytes contain the length of the response data, which is variable. So you might have code that looks like this:

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_FIXED_LENGTH_HEADER)
    {
        int bodyLength = [self parseHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1 tag:TAG_RESPONSE_BODY];
    }
    else if (tag == TAG_RESPONSE_BODY)
    {
        // Process the response
        [self handleResponseBody:data];

        // Start reading the next response
        [socket readDataToLength:headerLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
    }
}

Let’s look at another example. After all, not all protocols use a fixed length header. HTTP is one such protocol.

A typical HTTP response looks something like this:

HTTP/1.1 200 OK
Date: Thu, 24 Nov 2011 02:18:50 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 5233
Content-Type: text/html; charset=UTF-8

That’s just an example. There could be any number of header fields. In other words, the HTTP header has a variable length. How do we read it?

Well the HTTP protocol explains how. Each line in the header is terminated with a CRLF (carriage-return, line-feed : “\r\n”). Furthermore, the end of the header is marked with 2 back-to-back CRLF’s. And the length of the body is specified via the “Content-Length” header field. So we could do something like this:

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == HTTP_HEADER)
    {
        int bodyLength = [self parseHttpHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1 tag:HTTP_BODY];
    }
    else if (tag == HTTP_BODY)
    {
        // Process response
        [self processHttpBody:data];

        // Read header of next response
        NSData *term = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
        [socket readDataToData:term withTimeout:-1 tag:HTTP_HEADER];
    }
}

I’ve listed 2 available read methods. There are close to 10 different read methods available. They provide more advanced options such as specifying a maxLength, or providing your own read buffer.

讀 寫


這個庫的最佳功能之一是“讀隊列/寫操作”。這是什麼意思呢?一下這段代碼是最好的詮釋:

NSError *err = nil;
if (![socket connectToHost:@"deusty.com" onPort:80 error:&err]) // Asynchronous!
{
    // If there was an error, it's likely something like "already connected" or "no delegate set"
    NSLog(@"I goofed: %@", err);
    return;
}

// At this point the socket is NOT connected.
// 這時Socket沒有連接
// But I can start writing to it anyway!
// 但是我已經可以進行寫操作
// The library will queue all my write operations,
// 庫會將我的寫操作排成隊列
// and after the socket connects, it will automatically start executing my writes!
// 當Socket連接之後,它將自動執行我的寫操作

[socket writeData:request1 withTimeout:-1 tag:1];

// In fact, I know I have 2 requests.
// Why not just get them both out of the way now?
[socket writeData:request2 withTimeout:-1 tag:2];

// Heck, while I'm at it, I might as well queue up the read for the first response.
[socket readDataToLength:responseHeaderLength withTimeout:-1 tag:TAG_RESPONSE_HEADER];

你可能已經注意到tag參數。這是幹啥的呢?對於你來說這很方便。tag參數並不通過Socket發送或者接收,tag參數通過各種委託方法回調給你。它的目的是有助於簡化您的委託方法中的代碼。

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    if (tag == 1)
        NSLog(@"First request sent");
    else if (tag == 2)
        NSLog(@"Second request sent");
}

當信息過來時,tag非常有用

#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_WELCOME)
    {
        // Ignore welcome message
    }
    else if (tag == TAG_CAPABILITIES)
    {
        [self processCapabilities:data];
    }
    else if (tag == TAG_MSG)
    {
        [self processMessage:data];
    }
}

你知道,TCP協議是一個單一連續的無限長度的流的概念。理解這一事實的關鍵是,事實上,我們所看到的數字造成混亂的原因。
想象一下你想在Socket上發送一些消息。所以你這樣做(僞代碼):

socket.write("Hi Sandy.");
socket.write("Are you busy tonight?");

數據如何顯示在另一端?如果你認爲對方會在兩個獨立的讀取接收兩個單獨的句子,你就掉溝裏啦!但是別害怕,你的情況不足以威脅生命,你只是普通感冒。對付的方法在前文可以找到: Common Pitfalls page;

既然我們已經有了這樣的方式,你可能會對那些read方法感興趣。這裏就是兩個:

- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;

第一種方法,readdatatolength,讀取並返回給定長度的數據。讓我們來看一個例子:
你正在編寫一個客戶端的協議,服務器發送帶有固定長度的頭的響應。所有響應的頭文件正是8字節。前4個字節包含各種標誌,4字節包含響應數據的長度,這是可變的。本文由B9班的真高興發佈於CSDN博客,所以你可能會寫出如下代碼:

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_FIXED_LENGTH_HEADER)
    {
        int bodyLength = [self parseHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1 tag:TAG_RESPONSE_BODY];
    }
    else if (tag == TAG_RESPONSE_BODY)
    {
        // Process the response
        [self handleResponseBody:data];

        // Start reading the next response
        [socket readDataToLength:headerLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
    }
}

讓我們再看一個例子。畢竟,不是所有的協議使用一個固定長度的頭。HTTP是一個這樣的協議。

一個典型的HTTP響應看起來像這樣:

HTTP/1.1 200 OK
Date: Thu, 24 Nov 2011 02:18:50 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 5233
Content-Type: text/html; charset=UTF-8

這只是一個例子。可以有任意長度的頭字段。換句話說,這個HTTP頭有是變長的。我們怎樣read呢?

現在我們看看協議的說明。標題中的每行是一個CRLF終止(回車,換行:“\r\n”)。此外,最終的標題標記2個背靠背的CRLF,和body的長度是通過指定的“Content-Length”頭字段表示的。所以我們會這樣做:

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == HTTP_HEADER)
    {
        int bodyLength = [self parseHttpHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1 tag:HTTP_BODY];
    }
    else if (tag == HTTP_BODY)
    {
        // Process response
        [self processHttpBody:data];

        // Read header of next response
        NSData *term = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
        [socket readDataToData:term withTimeout:-1 tag:HTTP_HEADER];
    }
}

我已經列出了2種可供read的方法。有接近10種不同的讀取方法。他們提供更高級的選項,如指定最大長度,或提供自己的讀緩衝區。

Writing a server


GCDAsyncSocket also allows you to create a server, and accept incoming connections. It looks something like this:

listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

NSError *error = nil;
if (![listenSocket acceptOnPort:port error:&error])
{
    NSLog(@"I goofed: %@", error);
}

- (void)socket:(GCDAsyncSocket *)sender didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    // The "sender" parameter is the listenSocket we created.
    // The "newSocket" is a new instance of GCDAsyncSocket.
    // It represents the accepted incoming client connection.

    // Do server stuff with newSocket...
}

It’s as simple as that! For a more concrete example, see the “EchoServer” sample project that comes with the repository.

寫服務端


GCDAsyncSocket還允許您創建一個服務器,並接受傳入的連接。它看起來像這樣:

listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

NSError *error = nil;
if (![listenSocket acceptOnPort:port error:&error])
{
    NSLog(@"I goofed: %@", error);
}

- (void)socket:(GCDAsyncSocket *)sender didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    // The "sender" parameter is the listenSocket we created.
    // The "newSocket" is a new instance of GCDAsyncSocket.
    // It represents the accepted incoming client connection.

    // Do server stuff with newSocket...
}

這很簡單!一個更具體的例子,看看自帶的庫”echoserver”示例項目。

發佈了92 篇原創文章 · 獲贊 137 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章