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”示例项目。

时间: 2024-11-07 01:26:20

CocoaAsyncSocket 文档3:介绍GCDAsyncSocket的相关文章

产品经理必备文档的介绍

    如果你和一个互联网产品经理聊天,可能会经常听他们说到的几个文档简称BRD,MRD,PRD和FSD.这个几个文档到底包含什么内容,有什么区别呢?总结一下,不妥之处请指正. BRD:Business Requirements Document,商业需求文档. 商业需求文档重点放在定义产品的商业需求,要说明产品能够解决的.客户碰到的一个或多个商业问题,然后提出建议解决方案-通常是用新产品或者改进现有的产品来解决这些问题. BRD也可能包括一个高级的商业案例,例如收益预测. 市场&竞争分析. 销

httpd主配置文档的介绍及小练习

一.httpd 主配置文档的介绍/etc/httpd/conf/httpd.conf ### Section 1: Global Environment 全局环境 ServerRoot "/etc/httpd" 主服务程序在这个目录下 PidFile run/httpd.pid Pid 在主服务目录下的这个文件 Timeout 60 超时时间为60秒 KeepAlive Off 持久连接关闭 MaxKeepAliveRequests 100 最大连接数 KeepAliveTimeout

Poi之Word文档结构介绍

1.poi之word文档结构介绍之正文段落 一个文档包含多个段落,一个段落包含多个Runs,一个Runs包含多个Run,Run是文档的最小单元 获取所有段落:List<XWPFParagraph> paragraphs = word.getParagraphs(); 获取一个段落中的所有Runs:List<XWPFRun> xwpfRuns = xwpfParagraph.getRuns(); 获取一个Runs中的一个Run:XWPFRun run = xwpfRuns.get(i

弹性盒属相文档详细介绍

弹性盒属相文档详细介绍 display:flex; 声明本元素是弹性盒容器 如果目标元素是行内元素 使用display:inline-flex; flex-direction 取值 描述 row 默认值 ,弹性盒子元素按X轴方向顺序排列 row-reverse 弹性盒子元素按照X轴风向逆序排列 column 弹性盒子元素按照Y轴方向顺序排列 column-reverse 弹性盒子元素按照Y轴方向逆序排列 flex-wrap 取值 描述 nowrap 默认值,flex子元素只会单行显示,flex子

Office文档修复介绍之:laola文件格式介绍

Office文档是目前应用最广泛的文档格式,但很多人都没有为office文件建立完善的安全防护措施,也没有养成进行文件备份的良好习惯,所以一旦出现操作失误.病毒破坏.系统故障等情况,就有可能造成当前正在编辑的word.excel文档和access数据库等受到损坏,下次无法打开.那么一旦遇到这类文档被破坏或者丢失我们是否就束手无策了呢,当然不是,我们可以借助专业知识和小工具进行受损文档修复. 未公开的office文档存储格式秘密 Office文档格式一直以来都是微软公司的技术机密,至今未曾向外界公

IEEE829-2008软件测试文档标准介绍

1998版中定义了一套文档用于8个已定义的软件测试阶段: 测试计划: 一个管理计划的文档 包括:   测试如何完成 (包括SUT的配置).   谁来做测试   将要测试什么   测试将持续多久 (虽然根据可以使用的资源的限制而有变化).   测试覆盖度的需求,例如所要求的质量等级   测试设计规格: 详细描述测试环境和期望的结果以及测试通过的标准.   测试用例规格: 定义用于运行于测试设计规格中所述条件的测试数据.   测试过程规格: 详细描述如何进行每项测试,包括每项预置条件和接下去的步骤.

度量快速开发平台中制作帮助文档实现介绍

度量快速开发平台中,构建的业务系统,在交给客户使用的时候,一般是需要制作客户使用帮助文档.因为度量快速开发开发平台是集成了C/S,B/S的整合平台,可以很方便的利用html文档来制作业务系统帮助文档.示例:---------------------------------------------------------------------------------------------------------------------主网页:helpIndex.htm<html><he

ElasticSearch文档操作介绍三

ElasticSearch文档的操作 文档存储位置的计算公式: shard = hash(routing) % number_of_primary_shards 上面公式中,routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值. routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到 余数 .这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是

通过cmd命令查看Python库、函数和模块的帮助文档与介绍

dir函数式可以查看对象的属性 使用方法很简单,举os类型为例,在Python命令窗口输入 dir(‘os’) 即可查看os模块的属性 打开cmd命令窗口 输入python(注意:计算机需要有Python环境,配置好Python环境变量) 输入dir('os')命令 如何查看对象某个属性的帮助文档? 如要查看’os’的split属性,可以用__doc__, 使用方法为print(’os’.split.__doc__) print(’os’.split.__doc__) 查看对象的某个属性还可以用

cmd命令查看Python模块函数等帮助文档和介绍

dir函数式可以查看对象的属性 使用方法很简单,举os类型为例,在Python命令窗口输入 dir(‘os’) 即可查看os模块的属性 打开cmd命令窗口 ? 输入python(注意:计算机需要有Python环境,配置好Python环境变量) ? 输入dir('os')命令 ? 如何查看对象某个属性的帮助文档 ? 如要查看’os’的split属性,可以用__doc__, 使用方法为print(’os’.split.__doc__) print(’os’.split.__doc__) ? 查看对象