iOS 长连接-GCDAsyncSocket API简介

本篇为GCDAsyncSocket类中接口注释的中文翻译,并参考Reference_GCDAsyncSocket文档以及结合本人使用CocoaAsyncSocket进行客户端socket开发的经验做了一些方法的分类。由于本人仅使用了CocoaAsyncSocket部分方法来满足公司客户端项目的需求,所以对一些未使用的方法理解不够深刻,在此也仅仅是做了一些翻译记录。

初始化

初始化

GCDAsyncSocket使用标准的委托范式,将在给定的委托调度队列上执行所有委托回调。这样可以实现最大的并发性,同时又提供了简单的线程安全性。

在使用套接字之前,您必须设置一个委托和委托分派队列,否则将会出现错误。

套接字队列是GCDAsyncSocket实例在内部对其进行操作的调度队列。您可以选择在初始化期间设置套接字队列。如果您选择不这样做或传递NULL,GCDAsyncSocket将自动创建它自己的套接字队列。如果选择提供套接字队列,则套接字队列不能为并发队列。

如果选择提供套接字队列,并且套接字队列具有已配置的目标队列,则请参阅有关markSocketQueueTargetQueue方法的解析。

委托队列和套接字队列可以选择相同的队列。

- (id)init

- (id)init

使用nil值调用指定的初始化程序。您需要在使用套接字之前设置委托和委托队列。

initWithSocketQueue:

- (id)initWithSocketQueue:(dispatch_queue_t)sq

使用给定的socketQueue调用指定的初始化程序。您需要在使用套接字之前设置委托和委托队列。

initWithDelegate:delegateQueue:

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq

用给定的委托和委托队列调用指定的初始化程序。

initWithDelegate:delegateQueue:socketQueue:

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq

指定的初始化程序。
使用给定的委托和委托调度队列初始化套接字。
套接字分派队列是可选的。这是套接字将在内部对其进行操作的调度队列。如果为NULL,将自动创建一个新的调度队列。如果选择提供套接字队列,则套接字队列不得为并发队列。
委托队列和套接字队列可以选择相同。

其它
从已连接的BSD套接字文件描述符创建GCDAsyncSocket。

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error;

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error;

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error;

配置

delegate

@property (atomic, weak, readwrite, nullable) id<GCDAsyncSocketDelegate> delegate;

可读写。
返回(设置)当前为套接字设置的委托。
建议您在释放套接字之前取消套接字的委托。有关更多信息,请参见断开连接方法。

delegateQueue

#if OS_OBJECT_USE_OBJC
@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue;
#else
@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue;
#endif

可读写。
返回(设置)当前为套接字设置的委托队列。所有委托方法将在此队列上异步调用。

getDelegate:delegateQueue:

- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr

委托和委托队列通常是并进的。此方法提供了一种线程安全的方法,可以在一个操作中获取当前的委托配置(委托及其队列)。

setDelegate:delegateQueue:

- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue

在一个操作中提供一种简单且线程安全的方式来更改委托和委托队列。
如果您打算同时更改委托和委托队列,则此方法是首选方法。

IPv4Enabled

@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled;

isIPv4Enabled

默认情况下,同时启用IPv4和IPv6。
对于接受传入连接,这意味着GCDAsyncSocket自动支持两种协议,并且可以同时接受任一协议的传入连接。
对于传出连接,这意味着GCDAsyncSocket可以连接到运行任一协议的远程主机。如果DNS查找仅返回IPv4结果,则GCDAsyncSocket将自动使用IPv4。如果DNS查找仅返回IPv6结果,则GCDAsyncSocket将自动使用IPv6。如果DNS查找同时返回IPv4和IPv6结果,则将选择首选协议。默认情况下,首选协议为IPv4,但可以根据需要进行配置。

setIPv4Enabled:

启用或禁用对IPv4的支持。
注意:在已连接(客户端)或接受连接(服务器端)的套接字上更改此属性不会影响当前的套接字。它只会影响以后的连接(在断开当前套接字后)。设置后,该首选项将影响GCDAsyncSocket实例上的所有将来连接。

IPv6Enabled

@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled;

isIPv6Enabled

默认情况下,同时启用IPv4和IPv6。
对于接受传入连接,这意味着GCDAsyncSocket自动支持两种协议,并且可以同时接受任一协议的传入连接。
对于传出连接,这意味着GCDAsyncSocket可以连接到运行任一协议的远程主机。如果DNS查找仅返回IPv4结果,则GCDAsyncSocket将自动使用IPv4。如果DNS查找仅返回IPv6结果,则GCDAsyncSocket将自动使用IPv6。如果DNS查找同时返回IPv4和IPv6结果,则将选择首选协议。默认情况下,首选协议为IPv4,但可以根据需要进行配置。

setIPv6Enabled:

启用或禁用对IPv6的支持。
注意:在已连接(客户端)或接受连接(服务器端)的套接字上更改此属性不会影响当前的套接字。它只会影响以后的连接(在断开当前套接字后)。设置后,该首选项将影响GCDAsyncSocket实例上的所有将来连接。

IPv4PreferredOverIPv6

isIPv4PreferredOverIPv6

默认情况下,首选协议是IPv4。

setPreferIPv4OverIPv6

设置首选协议。有关更多信息,请参见关于isIPv4Enabled的解析。

alternateAddressDelay

@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay;

当使用Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555连接到IPv4和IPv6时,连接到首选协议和回退协议之间的延迟时间。
默认为300毫秒。

userData

@property (atomic, strong, readwrite, nullable) id userData;

用户数据允许您将任意信息与套接字关联。socket不以任何方式在内部使用这些数据。

其它

如果在委托的dealloc方法中将委托设置为nil,您可能需要使用下面的同步版本。

- (void)synchronouslySetDelegate:(id)delegate;
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;


客户端

连接

一旦调用了其中一个accept或connect方法,GCDAsyncSocket实例将被锁定,并且必须先断开套接字的连接才能调用其他accept / connect方法。

connectToHost:onPort:error:

- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr

连接到给定的主机和端口。
此方法调用connectToHost:onPort:viaInterface:withTimeout:error:并使用默认接口,并且没有超时。
如果启动了异步连接尝试,则返回YES。如果在请求中检测到错误,赋值可选的errPtr变量,且返回NO。

connectToHost:onPort:withTimeout:error:

- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr

通过可选的超时连接到给定的主机和端口。
此方法调用connectToHost:onPort:viaInterface:withTimeout:error:并使用默认接口。

connectToHost:onPort:viaInterface:withTimeout:error:

- (BOOL)connectToHost:(NSString *)host
               onPort:(UInt16)port
         viaInterface:(NSString *)interface
          withTimeout:(NSTimeInterval)timeout
                error:(NSError **)errPtr

通过可选接口及可选超时连接到给定的主机和端口。
主机可以是域名(例如“ deusty.com”)或IP地址字符串(例如“ 192.168.0.2”)。主机也可以是特殊字符串“local host”或“loopback”,以指定连接到本地计算机上的服务。
该接口可以是名称(例如“ en1”或“ lo0”)或相应的IP地址(例如“ 192.168.4.35”)。接口也可用于指定本地端口(见下文)。
如果不使用超时,请使用负值的时间间隔。
如果检测到错误,此方法将返回NO,并且赋值错误指针(如果已给出)。可能的错误原因:主机无效、接口无效、套接字已开始连接。
如果未检测到错误,则此方法将启动异步操作连接后台并立即返回YES。委托回调用于在套接字连接或主机不可访问时通知您。
由于此类支持排队的读取和写入,因此您可以立即开始读取和/或写入。所有读/写操作都将排队,并且在套接字连接后,这些操作将按顺序出队处理。

接口可以选择在字符串末尾包含端口号,用冒号分隔。
这允许您指定应用于传出连接的本地端口。(从头到尾读)
指定接口和本地端口:“en1:8082”或“192.168.4.35:2424”。
仅指定本地端口:“:8082”。
请注意,这是一个高级功能,而且是故意隐藏的。
您应该明白,99.999%的时间不应该为传出连接指定本地端口。
如果你认为你需要,很有可能你在某个地方有一个根本性的误解。
本地端口不需要与远程端口匹配。事实上,他们几乎从不这样做。
此功能是为使用非常先进的技术的网络专业人员提供的。

connectToAddress: error:

- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;

连接到给定的地址,封装在NSData对象中的sockaddr指定结构。
例如,从NSNetService的addresses方法返回的NSData对象。
如果您有一个现有的struct sockaddr,您可以将其转换为NSData对象,如下所示:

struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];

此方法调用connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr

connectToAddress: withTimeout: error:

- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;

此方法与connectToAddress:error:相同,且带有附加超时选项。
要不超时,请使用负时间间隔,或者只使用connectToAddress:error:方法。

connectToAddress:viaInterface:withTimeout:error:

- (BOOL)connectToAddress:(NSData *)remoteAddr
            viaInterface:(nullable NSString *)interface
             withTimeout:(NSTimeInterval)timeout
                   error:(NSError **)errPtr;

使用指定的接口和超时连接到给定地址。
地址为封装在NSData对象中的sockaddr指定结构。

例如,从NSNetService的addresses方法返回的NSData对象。
如果您有一个现有的struct sockaddr,您可以将其转换为NSData对象,如下所示:

struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];

该接口可以是名称(例如“ en1”或“ lo0”)或相应的IP地址(例如“ 192.168.4.35”)。接口也可用于指定本地端口(见下文)。
如果不使用超时,请使用负值的时间间隔。
如果检测到错误,此方法将返回NO,并且赋值错误指针(如果已给出)。可能的错误原因:主机无效、接口无效、套接字已开始连接。
如果未检测到错误,则此方法将启动异步操作连接后台并立即返回YES。委托回调用于在套接字连接或主机不可访问时通知您。
由于此类支持排队的读取和写入,因此您可以立即开始读取和/或写入。所有读/写操作都将排队,并且在套接字连接后,这些操作将按顺序出队处理。

接口可以选择在字符串末尾包含端口号,用冒号分隔。
这允许您指定应用于传出连接的本地端口。(从头到尾读)
指定接口和本地端口:“en1:8082”或“192.168.4.35:2424”。
仅指定本地端口:“:8082”。
请注意,这是一个高级功能,而且是故意隐藏的。
您应该明白,99.999%的时间不应该为传出连接指定本地端口。
如果你认为你需要,很有可能你在某个地方有一个根本性的误解。
本地端口不需要与远程端口匹配。事实上,他们几乎从不这样做。
此功能是为使用非常先进的技术的网络专业人员提供的。

connectToUrl: withTimeout:error:

- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;

使用指定的超时连接到给定url处的unix域套接字。

connectToNetService: error:

- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr;

按顺序遍历给定的NetService地址,并调用connectToAddress:error:。在第一次调用成功时停止并返回YES;否则返回NO。


断开连接

disconnect

- (void)disconnect

立即断开连接(同步)。所有挂起的读取或写入操作都将被丢弃。
如果套接字尚未断开连接,socketDidDisconnect:withError:代理方法将异步排队到delegateQueue(在以前排队的任何delegate方法之后)。换句话说,断开连接的委托方法将在该方法返回后不久调用。

请注意释放GCDAsyncSocket实例的推荐方式(例如,在dealloc方法中)

[asyncSocket setDelegate:nil delegateQueue:NULL];
[asyncSocket disconnect];
[asyncSocket release];

如果你计划断开socket,然后要求它立即重新连接,
你可以这样做:

 [asyncSocket setDelegate:nil];
 [asyncSocket disconnect];
 [asyncSocket setDelegate:self];
 [asyncSocket connect...];

disconnectAfterReading

- (void)disconnectAfterReading

所有未完成的读操作完成后,断开连接。此方法是异步的,并且立即返回(即使没有挂起的读操作)。
调用此方法后,读写方法将不做任何处理。即使仍然有挂起的写操作,套接字也会断开连接。

disconnectAfterWriting

- (void)disconnectAfterWriting

在所有挂起的写操作完成后断开连接。此方法是异步的,并且立即返回(即使没有挂起的写操作)。
调用此方法后,读写方法将不做任何处理。即使仍有待处理的读操作,套接字也会断开连接。

disconnectAfterReadingAndWriting

- (void)disconnectAfterReadingAndWriting

在所有未完成的读取和写入完成之后断开连接。此方法是异步的,并且立即返回(即使没有挂起的读取或写入操作)。
调用此方法后,读写方法将不做任何处理。


诊断

isDisconnected

@property (atomic, readonly) BOOL isDisconnected

如果套接字断开连接,则返回YES。
断开的socket可能会被回收。也就是说,它可以再次用于连接或收听。
如果一个socket处于正在连接中,它既不是已断开也不是已连接。

isConnected

@property (atomic, readonly) BOOL isConnected

如果套接字已连接,则返回YES。
如果一个socket处于正在连接中,它既不是已断开也不是已连接。

connectedHost

@property (atomic, readonly, nullable) NSString *connectedHost;

以字符串格式返回连接的(远程)主机的IP地址。
如果未连接套接字,则返回nil。

connectedPort

@property (atomic, readonly) uint16_t  connectedPort

返回连接的(远程)主机的端口号。
如果未连接套接字,则返回0。

connectedUrl

@property (atomic, readonly, nullable) NSURL    *connectedUrl;

返回连接的(远程)主机的URL。
如果未连接套接字,则返回nil。

localHost

@property (atomic, readonly, nullable) NSString *localHost;

返回用于连接的本地接口的IP地址。例如,这可能类似于“ 192.168.0.4”。
如果未连接套接字,则返回nil。

localPort

@property (atomic, readonly) uint16_t  localPort;

返回用于连接的端口号。
如果未连接套接字,则返回0。

connectedAddress

@property (atomic, readonly, nullable) NSData *connectedAddress

返回连接的(远程)主机的地址。这是包装在NSData对象中的“ struct sockaddr”值。如果套接字为IPv4,则数据的类型为“ struct sockaddr_in”。如果套接字为IPv6,则数据的类型为“ struct sockaddr_in6”。
如果未连接套接字,则返回nil。

localAddress

@property (atomic, readonly, nullable) NSData *localAddress

返回用于连接的本地接口的地址。这是包装在NSData对象中的“ struct sockaddr”值。如果套接字为IPv4,则数据的类型为“ struct sockaddr_in”。如果套接字为IPv6,则数据的类型为“ struct sockaddr_in6”。
如果未连接套接字,则返回nil。

isIPv4

@property (atomic, readonly) BOOL isIPv4

如果套接字为IPv4,则返回YES。
对于客户端套接字(那些通过connectTo ...连接到另一主机的套接字),该套接字将为IPv4或IPv6。
对于服务器套接字(那些通过accept ...接受传入连接的套接字),套接字可以同时是IPv4和IPv6。这允许服务器自动支持两种协议。

isIPv6

@property (atomic, readonly) BOOL isIPv6;

如果套接字为IPv6,则返回YES。
对于客户端套接字(那些通过connectTo ...连接到另一主机的套接字),该套接字将为IPv4或IPv6。
对于服务器套接字(那些通过accept ...接受传入连接的套接字),套接字可以同时是IPv4和IPv6。这允许服务器自动支持两种协议。

isSecure

@property (atomic, readonly) BOOL isSecure;

判断套接字是否已通过SSL/TLS进行安全保护。
另请参见startTLS方法。


读数据

读取方法不会阻塞(它们是异步的)。读取完成后,将调用socket:didReadData:withTag:委托方法。

您可以选择为读取操作设置超时。(若不使用超时,请使用负值的时间间隔。)如果设置读取操作超时,则可调用相应的socket:shouldTimeoutReadWithTag ...委托方法延长超时时间(可选)。超时后,将调用socketDidDisconnect:withError:方法。

标签是为了您的方便。传递给读取操作的标记将在socket:didReadData:withTag:委托回调中传递回给您。您可以将其用作状态ID、数组索引、小部件编号、指针等。

您可以同时调用多个读取方法。读取将按请求顺序排队,出队时将串行执行。

readDataWithTimeout:tag:

- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag

读取套接字上可用的第一个可用字节。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,套接字上如果没有立即可用的数据,则读取操作将超时处理。
标签是为了您的方便。传递给读取操作的标记会在socket:didReadData:withTag:委托回调中传递回给您。

readDataWithTimeout:buffer:bufferOffset:tag:

- (void)readDataWithTimeout:(NSTimeInterval)timeout
                     buffer:(NSMutableData *)buffer
               bufferOffset:(NSUInteger)offset
                        tag:(long)tag;

读取套接字上可用的第一个可用字节。字节将从给定的偏移量(offset)开始添加到给定的字节缓冲区(buffer)。如果需要,给定的缓冲区大小将自动增加。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,套接字上如果没有立即可用的数据,则读取操作将超时处理。
如果缓冲区为零,套接字将自动管理缓冲区。
如果bufferOffset大于给定缓冲区的长度,则该方法将不执行任何操作,并且不会调用委托。
如果设置缓冲区,则在GCDAsyncSocket使用缓冲区时,不得以任何方式对其进行更改。完成后,在socket:didReadData:withTag:中返回的数据将是给定缓冲区的子集。也就是说,它将通过[NSData dataWithBytesNoCopy:length:freewendone:NO]方法添加到给定缓冲区。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。

readDataWithTimeout: buffer: bufferOffset: maxLength: tag:

- (void)readDataWithTimeout:(NSTimeInterval)timeout
                     buffer:(NSMutableData *)buffer
               bufferOffset:(NSUInteger)offset
                  maxLength:(NSUInteger)length
                        tag:(long)tag;

读取套接字上可用的第一个可用字节。字节将从给定的偏移量(offset)开始附加到给定的字节缓冲区(buffer)。如果需要,给定的缓冲区大小将自动增加。将读取最大长度的字节。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,套接字上如果没有立即可用的数据,则读取操作将超时处理。
如果缓冲区为零,套接字将自动管理缓冲区。如果maxLength为零,则不执行长度限制。
如果bufferOffset大于给定缓冲区的长度,则该方法将不执行任何操作,并且不会调用委托。
如果设置缓冲区,则在GCDAsyncSocket使用缓冲区时,不得以任何方式对其进行更改。完成后,在socket:didReadData:withTag:中返回的数据将是给定缓冲区的子集。也就是说,它将通过[NSData dataWithBytesNoCopy:length:freewendone:NO]方法添加到给定缓冲区。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。

readDataToLength: withTimeout: tag:

- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag

读取给定的字节长度。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果长度为0,则此方法不执行任何操作,并且不调用委托。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。

readDataToLength: withTimeout: buffer: bufferOffset: tag:

- (void)readDataToLength:(NSUInteger)length
             withTimeout:(NSTimeInterval)timeout
                  buffer:(NSMutableData *)buffer
            bufferOffset:(NSUInteger)offset
                     tag:(long)tag;

读取给定的字节长度。字节将从给定的偏移量(offset)开始添加到给定的字节缓冲区(buffer)。如果需要,给定的缓冲区大小将自动增加。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果缓冲区为零,套接字将自动管理缓冲区。
如果长度为0,则此方法不执行任何操作,并且不调用委托。如果bufferOffset大于给定缓冲区的长度,则该方法将不执行任何操作,并且不会调用委托。
如果设置缓冲区,则在GCDAsyncSocket使用缓冲区时,不得以任何方式对其进行更改。完成后,在socket:didReadData:withTag:中返回的数据将是给定缓冲区的子集。也就是说,它将通过[NSData dataWithBytesNoCopy:length:freewendone:NO]方法添加到给定缓冲区。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您

readDataToData: withTimeout: tag:

- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag

读取字节,直到(包括)设置的“data”参数为止,该参数用作分隔符。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果将nil或零长度数据作为“data”参数值传递,则该方法将不执行任何操作,并且不会调用委托。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。
*要从套接字读取行,请使用行分隔符(例如,HTTP的CRLF,见下文)作为“data”参数。
如果您正在开发自己的自定义协议,请确保分隔符不能作为分隔符之间数据的一部分自然出现。
例如,假设您想通过一个套接字发送几个小文档。
使用CRLF作为分隔符可能是不明智的,因为CRLF很容易存在于文档中。
在这个特定的例子中,最好使用一个类似于HTTP的协议,其头部包含文档的长度。
还要注意分隔符不能作为字符编码的一部分自然出现。
给定的数据(分隔符)参数应该是不可变的。
出于性能原因,套接字将保留(retain)它,而不是复制(copy)它。
因此,如果它是不可变的,不要在套接字使用它时修改它。

readDataToData: withTimeout: buffer: bufferOffset: tag:

- (void)readDataToData:(NSData *)data
           withTimeout:(NSTimeInterval)timeout
                buffer:(NSMutableData *)buffer
          bufferOffset:(NSUInteger)offset
                   tag:(long)tag;

读取字节,直到(包括)设置的“data”参数为止,该参数用作分隔符。字节将从给定的偏移量开始添加到给定的字节缓冲区。如果需要,给定的缓冲区大小将自动增加。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果缓冲区为nil,将自动为您创建一个缓冲区。
如果bufferOffset大于给定缓冲区的长度,则该方法将不执行任何操作,并且不会调用委托。
如果设置缓冲区,则在GCDAsyncSocket使用缓冲区时,不得以任何方式对其进行更改。完成后,在socket:didReadData:withTag:中返回的数据将是给定缓冲区的子集。也就是说,它将添加到给定缓冲区。
标签是为了您的方便。传递给读取操作的标记将在socket:didReadData:withTag:委托回调中传递回给您。
要从套接字读取行,请使用行分隔符(例如,HTTP的CRLF,见下文)作为“data”参数。
如果您正在开发自己的自定义协议,请确保分隔符不能作为分隔符之间数据的一部分自然出现。
例如,假设您想通过一个套接字发送几个小文档。
使用CRLF作为分隔符可能是不明智的,因为CRLF很容易存在于文档中。
在这个特定的例子中,最好使用一个类似于HTTP的协议,其头部包含文档的长度。
还要注意分隔符不能作为字符编码的一部分自然出现。
给定的数据(分隔符)参数应该是不可变的。
出于性能原因,套接字将保留它,而不是复制它。
因此,如果它是不可变的,不要在套接字使用它时修改它。

readDataToData: withTimeout: maxLength: tag:

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

读取字节,直到(包括)设置的“数据”参数为止,该参数用作分隔符。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果maxLength为零,则不执行长度限制。否则,如果在未完成读取的情况下读取了maxLength个字节,则将其做超时类似地处理-套接字通过GCDAsyncSocketReadMaxedOutError关闭。如果精确地读取了maxLength个字节,并且在末尾找到了给定的数据,则读取将成功完成。
如果将nil或零长度数据作为“data”参数传递,则该方法将不执行任何操作,并且不会调用委托。如果传递的maxLength参数小于data参数的长度,则该方法将不执行任何操作,并且不会调用委托。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。
要从套接字读取行,请使用行分隔符(例如,HTTP的CRLF,见下文)作为“data”参数。
如果您正在开发自己的自定义协议,请确保分隔符不能作为分隔符之间数据的一部分自然出现。
例如,假设您想通过一个套接字发送几个小文档。
使用CRLF作为分隔符可能是不明智的,因为CRLF很容易存在于文档中。
在这个特定的例子中,最好使用一个类似于HTTP的协议,其头部包含文档的长度。
还要注意分隔符不能作为字符编码的一部分自然出现。
给定的数据(分隔符)参数应该是不可变的。
出于性能原因,套接字将保留它,而不是复制它。
因此,如果它是不可变的,不要在套接字使用它时修改它。

readDataToData: withTimeout: buffer: bufferOffset: maxLength: tag:

- (void)readDataToData:(NSData *)data
           withTimeout:(NSTimeInterval)timeout
                buffer:(NSMutableData *)buffer
          bufferOffset:(NSUInteger)offset
             maxLength:(NSUInteger)length
                   tag:(long)tag;

读取字节,直到(包括)设置的“数据”参数为止,该参数用作分隔符。字节将从给定的偏移量开始添加到给定的字节缓冲区。如果需要,给定的缓冲区大小将自动增加。将读取最大长度的字节。
如果超时值为负,则读取操作将不使用超时。如果超时设置为零,在读取操作出队时,如果套接字上没有立即可用的数据,则读取操作将超时处理。
如果缓冲区为nil,将自动为您创建一个缓冲区。
如果maxLength为零,则不执行长度限制。否则,如果在未完成读取的情况下读取了maxLength个字节,则将其做超时类似地处理-套接字通过GCDAsyncSocketReadMaxedOutError关闭。如果精确地读取了maxLength个字节,并且在末尾找到了给定的数据,则读取将成功完成。
如果传递的maxLength参数小于data参数的长度,则该方法将不执行任何操作,并且不会调用委托。如果bufferOffset大于给定缓冲区的长度,则该方法将不执行任何操作,并且不会调用委托。
如果设置缓冲区,则在GCDAsyncSocket使用缓冲区时,不得以任何方式对其进行更改。完成后,在socket:didReadData:withTag:中返回的数据将是给定缓冲区的子集。也就是说,它将添加到给定缓冲区。
标签是为了您的方便。传递给读取操作的标记将在onSocket:didReadData:withTag:委托回调中传递回给您。
要从套接字读取行,请使用行分隔符(例如,HTTP的CRLF,见下文)作为“data”参数。
如果您正在开发自己的自定义协议,请确保分隔符不能作为分隔符之间数据的一部分自然出现。
例如,假设您想通过一个套接字发送几个小文档。
使用CRLF作为分隔符可能是不明智的,因为CRLF很容易存在于文档中。
在这个特定的例子中,最好使用一个类似于HTTP的协议,其头部包含文档的长度。
还要注意分隔符不能作为字符编码的一部分自然出现。
给定的数据(分隔符)参数应该是不可变的。
出于性能原因,套接字将保留它,而不是复制它。
因此,如果它是不可变的,不要在套接字使用它时修改它。

progressOfReadReturningTag:bytesDone: total:

- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;

返回当前读取的进度,从0.0到1.0,如果没有当前读取,则返回NaN(使用isnan()进行检查)。
如果参数“tag”、“done”和“total”不为空,则填写它们。


写数据

write方法不会阻塞(它是异步的)。写入完成后,将调用socket:didWriteDataWithTag:委托方法。

您可以选择设置写操作的超时时间(如果不使用超时,请设置时间间隔为负值)。写操作超时时,则会调用相应的socket:shouldTimeoutWriteWithTag ...委托方法允许您延长超时时间(可选)。超时后,将调用socketDidDisconnect:withError:方法。

标签是为了您的方便。传递给写操作的标记将在socket:didWriteDataWithTag:委托回调中传递回给您。您可以将其用作状态ID、数组索引、小部件编号、指针等。

您可以同时调用多个写入方法。写入将按请求顺序排队,出队时将并串行执行。

writeData: withTimeout: tag:

- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag

将数据写入套接字,并在完成后调用委托。
如果传入nil或零长度数据,则此方法不执行任何操作,并且不会调用该委托。
如果超时值为负,则写操作将不使用超时。如果超时设置为零,则在将写操作出队时,如果不能立即将所有数据写入套接字,写操作将做超时处理。
线程安全注意事项:
如果给定的数据参数是可变的(NSMutableData),则在套接字写入数据时不能更改数据。换句话说,在调用委托方法socket:didWriteDataWithTag:表示此特定写入操作已完成之前,更改数据是不安全的。
这是由于GCDAsyncSocket不复制数据的事实。它只是保留了它。
这是出于性能原因。通常情况下,如果传递了NSMutableData,那是因为在内存中建立了一个请求/响应。复制此数据会增加不需要的/不需要的开销。
如果需要从不可变缓冲区写入数据,并且需要在套接字完成写入字节之前更改缓冲区(不是在该方法返回之后立即更改,而是在稍后委托方法通知您时更改),则应首先复制字节,并将副本传递给该方法。

progressOfWriteReturningTag bytesDone: total:

- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;

返回当前写入的进度,从0.0到1.0,如果没有当前写入,则返回NaN(使用isnan()进行检查)。
如果参数“tag”、“done”和“total”不为空,则填写它们。


代理(部分,客户端使用)

GCDAsyncSocket是异步的。因此,对于大多数方法,当您在套接字上发起操作(连接,接受,读取,写入)时,该方法将立即返回,并且操作的结果将通过相应的委托方法返回给您。

socket: didConnectToHost: port:

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port

当套接字连接并准备开始读取和写入时调用。主机参数将是IP地址,而不是DNS名称。

socket: didConnectToUrl:
- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;

当套接字连接并准备好进行读写时调用。主机参数将是IP地址,而不是DNS名称。

socket: didReadData: withTag:

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

当套接字已完成将请求的数据读入内存时调用。如果有错误,则不调用。
tag参数是您请求读取操作时传递的标签。例如,在readDataWithTimeout:tag:方法中。

socket: didReadPartialDataOfLength: tag:

- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag

当套接字已读入数据但尚未完成读取时调用。如果使用readDataToData:readDataToLength:方法,则会发生这种情况。它可能用于诸如更新进度条之类的事情。
tag参数是您请求读取操作时传递的标签。例如,在readDataToLength:withTimeout:tag:方法中。

socket: shouldTimeoutReadWithTag: elapsed: bytesDone:

- (NSTimeInterval)socket:(GCDAsyncSocket *)sock
shouldTimeoutReadWithTag:(long)tag
                 elapsed:(NSTimeInterval)elapsed
               bytesDone:(NSUInteger)length

如果读取操作未完成就达到超时,则调用此方法。此方法使您可以选择延长超时时间。如果返回正的时间间隔(> 0),则读取的超时将延长给定的时间。如果您未实现此方法,或者返回非肯定的时间间隔(<= 0),则读取将照常超时。
elapsed的参数是原始超时的总和,再加上以前通过此方法添加的所有附加值。length参数是到目前为止已为读取操作读取的字节数。
请注意,如果您返回正数,则一次读取可能会多次调用此方法。

socket: didWriteDataWithTag:

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag

当套接字完成写入请求的数据时调用。如果有错误,则不调用。
tag参数是您请求写操作时传递的标签,例如,在writeData:withTimeout:tag:方法中。

socket: didWritePartialDataOfLength: tag:

- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag

当套接字已写入一些数据但尚未完成整个写入时调用。它可能用于诸如更新进度条之类的事情。
tag参数是您请求写操作时传递的标签,例如,在writeData:withTimeout:tag:方法中。

socket: shouldTimeoutWriteWithTag: elapsed: bytesDone:

- (NSTimeInterval)socket:(GCDAsyncSocket *)sock
shouldTimeoutWriteWithTag:(long)tag
                 elapsed:(NSTimeInterval)elapsed
               bytesDone:(NSUInteger)length;

如果写入操作未完成就达到超时,则调用此方法。此方法使您可以选择延长超时时间。如果返回正的时间间隔(> 0),则写入的超时将延长给定的数量。如果您未实现此方法,或者返回非肯定的时间间隔(<= 0),则写入将照常超时。
elapsed参数是原始超时的总和,再加上以前通过此方法添加的所有附加值。length参数是到目前为止已为写操作写入的字节数。
请注意,如果您返回正数,则一次写入可能会多次调用此方法。

socketDidSecure:

- (void)socketDidSecure:(GCDAsyncSocket *)sock

在套接字成功完成SSL / TLS协商之后调用。除非您使用提供的startTLS方法,否则不会调用此方法。
如果SSL / TLS协商失败(无效的证书等),则套接字将立即关闭,并且将使用特定的SSL错误代码调用socketDidDisconnect:withError:委托方法。
有关SSL错误代码及其含义的列表,请参阅Security.framework中的Apple的SecureTransport.h文件。

socketDidCloseReadStream:

- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock

如果读取流关闭,则有条件地调用,但是写入流可能仍然是可写的。
仅当autoDisconnectOnClosedReadStream设置为NO时,才调用此委托方法。有关更多信息,请参见有关autoDisconnectOnClosedReadStream方法的讨论。

socketDidDisconnect:withError:

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error

当套接字断开连接(有无错误)时调用。
如果调用断开连接方法,并且套接字尚未断开连接,则将在断开连接方法返回之前调用此委托方法。(因为断开连接方法是同步的。)
注意:如果GCDAsyncSocket实例在仍处于连接状态时被释放,并且委托也未被释放,则将调用此方法,但sock参数将为nil。(它必须为零,因为它不再可用。)
这种情况通常很少见,但如果您编写如下代码,则有可能:

asyncSocket=nil;//我正在隐式断开套接字的连接

在这种情况下,它可能更愿意事先将委托人置零,例如:

asyncSocket.delegate=nil;//不调用我的委托方法
asyncSocket=nil;//我正在隐式断开套接字的连接

当然,这取决于你的状态是怎么配置的。

socket: didReceiveTrust: completionHandler:

- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
                                    completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;

允许套接字委托挂接到TLS握手中,并手动验证它连接到的对等方。只有在使用包含以下选项的选项调用startTLS时才会调用此函数:GCDAsyncSocketManuallyEvaluateTrust==YES。通常,委托将使用SecTrustEvaluate(和相关函数)正确验证对等方。
苹果文档中的注释:
因为[SecTrustEvaluate]可能会在网络上查找证书链中的证书,[它]可能会在尝试网络访问时阻塞。永远不要从主线程调用它;只能从调度队列或单独线程上运行的函数中调用它。因此,此方法使用completionHandler块,而不是普通的返回值。completionHandler块是线程安全的,可以从后台队列/线程调用。即使套接字已关闭,调用completionHandler块也是安全的。



服务端

接收

一旦调用了其中一个accept或connect方法,GCDAsyncSocket实例将被锁定,并且必须先断开套接字的连接才能调用其他accept / connect方法。

接受传入连接后,GCDAsyncSocket会调用以下委托方法(按时间顺序):

  1. newSocketQueueForConnectionFromAddress:onSocket:
  2. socket:didAcceptNewSocket:

您的服务器代码需要保留接受的套接字(如果您想接受)。否则,将在委托方法返回后不久释放新接受的套接字(在此期间,可能会触发socketDidDisconnect:withError:)。

acceptOnPort: error:

- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr

告诉套接字开始侦听并接受给定端口上的连接。接受连接后,将产生一个新的GCDAsyncSocket实例来处理它,并调用socket:didAcceptNewSocket:委托方法。
套接字将侦听所有可用接口(例如wifi,以太网等)。
如果套接字能够开始侦听,则此方法返回YES。如果发生错误,则此方法返回NO并设置可选的errPtr变量。错误的原因可能是未设置任何委托,或者套接字已经接受连接。

acceptOnInterface: port: error:

- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr

此方法与acceptOnPort:error:相同,并具有指定监听哪个接口的附加选项。
例如,您可以指定套接字只接受通过以太网的连接,而不接受其他接口(例如wifi)。
该接口可以通过名称(例如“ en1”或“ lo0”)或IP地址(例如“ 192.168.4.34”)指定。您也可以使用特殊字符串“ localhost”或“ loopback”来指定套接字仅接受来自本地计算机的连接。
您可以通过命令行实用程序“ ifconfig”或以编程方式通过getifaddrs()函数查看接口列表。
要接受任何接口上的连接,请传递nil,或简单地使用acceptOnPort:error:方法。
如果套接字能够开始侦听,则此方法返回YES。如果发生错误,则此方法返回NO并设置可选的errPtr变量。错误的原因可能是未设置委托,或者找不到请求的接口。

acceptOnUrl: error:

- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;

告诉套接字开始侦听和接受给定url的unix域上的连接。接受连接后,将产生一个新的GCDAsyncSocket实例来处理它,并调用socket:didAcceptNewSocket:委托方法。
套接字将侦听所有可用接口(例如wifi,以太网等)。


代理

socket: didAcceptNewSocket:

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket

当“服务器”套接字接受传入的“客户端”连接时调用。自动生成另一个套接字来处理它。
如果要处理连接,则必须保留newSocket。否则,将释放newSocket实例,并关闭生成的连接。
默认情况下,新套接字将具有相同的委托和委托队列。当然,您可以随时更改此设置。
默认情况下,套接字将创建自己的内部套接字队列以进行操作。这可以通过实现newSocketQueueForConnectionFromAddress:onSocket:方法来配置。

newSocketQueueForConnectionFromAddress:onSocket:

- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;

socket:didAcceptNewSocket:之前立即调用此方法。它可以允许侦听套接字为新接受的套接字选择指定socketQueue。如果未实现此方法或返回NULL,则新接受的套接字将创建其自己的默认队列。
由于无法自动释放dispatch_queue,因此此方法在名称中使用“ new”前缀来指定已保留返回的队列。
因此,您可以在实现中执行以下操作:

return dispatch_queue_create("MyQueue", NULL);

如果将多个套接字放在同一队列中,则应注意每次调用此方法时都要增加retain计数。
例如,您的实现可能像这样:

dispatch_retain(myExistingQueue);
return myExistingQueue;


安全

startTLS:

- (void)startTLS:(NSDictionary *)tlsSettings

使用SSL / TLS保护连接。
可以随时调用此方法,在所有未完成的读写操作完成后,SSL / TLS握手将发生。您通过此方法就可以发送依赖于协议的StartTLS消息,同时排队升级到TLS,而不必等待写入完成。调用此方法后的任何读取或写入操作都将通过安全连接进行。

==== 可用的TOP-LEVEL 键有:

  • GCDAsyncSocketManuallyEvaluateTrust
    该值必须是NSNumber类型,封装BOOL值。
    如果将其设置为“YES”,则基础SecureTransport系统将不会计算对方的SecTrustRef。

取而代之的是,它通常将暂停在规避会发生的时刻,并允许我们在我们认为合适的时候处理安全评估。
因此GCDAsyncSocket将调用委托方法socket:shouldrustpeer:处理SecTrustRef。
注意,如果设置此选项,则忽略所有其他配置键。
socket:didReceiveTrust:completionHandler:delegate方法期间,评估将完全由您决定。
有关信任评估的更多信息,请参阅:Apple的技术说明TN2232-HTTPS服务器信任评估HTTPS://developer.Apple.com/library/ios/technotes/TN2232/\u index.html

如果未指定,则默认值为“否”。

  • GCDAsyncSocketUseCFStreamForTLS(仅限iOS)
    该值必须是NSNumber类型,封装BOOL值。
    默认情况下,GCDAsyncSocket将使用SecureTransport层执行加密。
    这使我们能够更好地控制安全协议(更多的配置选项),而且它还允许我们优化诸如sys调用和缓冲区分配之类的事情。
    但是,如果必须这样做,可以指示GCDAsyncSocket通过CFStream来使用老式的加密技术。因此,GCDAsyncSocket不会使用SecureTransport,而是设置CFRead/CFWriteStream。然后设置kCFStreamPropertySSLSettings属性(通过CFReadStreamSetProperty/CFWriteStreamSetProperty),并将给定的选项传递给此方法。
    因此,GCDAsyncSocket将忽略给定字典中的所有其他键,并直接传递CFReadStreamSetProperty/CFWriteStreamSetProperty。
    有关这些键的详细信息,请参阅kCFStreamPropertySSLSettings的文档。
    如果未指定,则默认值为“否”。

===可用的配置键有:

  • kCFStreamSSLPeerName
    该值必须是NSString类型。
    它应该与远程方提供的X.509证书中的名称匹配。
    请参阅苹果的SSLSetPeerDomainName文档。

  • KCFStreamslCertificates
    该值必须是NSArray类型。
    有关SSLSetCertificate,请参阅苹果的文档。

  • kCFStreamSSLIsServer
    该值必须是NSNumber类型,封装BOOL值。
    请参阅苹果公司有关iOS版SSLCreateContext的文档。
    这对于iOS是可选的。如果未提供,则默认值为“否”。
    Mac OS X不需要此选项,该值将被忽略。

  • GCDAsyncSocketSSLPeerID
    该值必须是NSData类型。
    如果要使用TLS会话恢复,必须设置此值。
    请参阅苹果的sslsetpeirid文档。

  • GCDAsyncSocketSSLProtocolVersionMin

  • GCDAsyncSocketSSLProtocolVersionMax
    值必须是NSNumber类型,并封装SSLProtocol值。
    请参阅苹果的SSLSetProtocolVersionMin和SSLSetProtocolVersionMax文档。
    另请参见SSLProtocol typedef。

  • GCDAsyncSocketSSLSessionOptionFalseStart
    该值必须是NSNumber类型,封装BOOL值。
    有关ksslsessionoptionfalse启动,请参阅苹果的文档。

  • GCDAsyncSocketSSLSessionOptionSendOneByteRecord
    该值必须是NSNumber类型,封装BOOL值。
    有关ksslsessionoptionsendenebytecord的信息,请参阅苹果的文档。

  • GCDAsyncSocketSSLCipherSuites
    值必须是NSArray类型。
    数组中的每个项都必须是NSNumber,封装一个SSLCipherSuite。
    请参阅苹果的SSLSetEnabledCiphers文档。
    另请参见SSLCipherSuite typedef。

  • GCDAsyncSocketSSLDiffieHellmanParameters(仅限Mac OS X)
    该值必须是NSData类型。
    请参阅苹果的sslsettiffiehellmanparams文档。

===以下不可用的键是:(抛出异常)

  • kCFStreamSSLAllowsAnyRoot(不可用)
    您必须使用手动信任评估(请参阅GCDAsyncSocketManuallyEvaluateTrust)。
    相应的弃用方法:SSLSetAllowsAnyRoot

  • kCFStreamSSLAllowsExpiredRoots(不可用)
    必须使用手动信任评估(请参阅GCDAsyncSocketManuallyEvaluateTrust)。
    相应的已弃用方法:SSLSetAllowsExpiredRoots

  • kCFStreamSSLAllowsExpiredCertificates(不可用)
    您必须使用手动信任评估(请参阅GCDAsyncSocketManuallyEvaluateTrust)。
    相应的已弃用方法:SSLSetAllowsExpiredCerts

  • kCFStreamSSLValidatesCertificateChain(不可用)
    必须使用手动信任评估(请参阅GCDAsyncSocketManuallyEvaluateTrust)。
    相应的弃用方法:SSLSetEnableCertVerify

  • kCFStreamSSLLevel(不可用)
    必须改用GCDAsyncSocketSSLProtocolVersionMin&GCDAsyncSocketSSLProtocolVersionMin。
    相应的弃用方法:SSLSetProtocolVersionEnabled

请参阅Apple文档以获取相关的值以及其他可能的键。

如果您输入nil或空字典,则将使用默认设置。
重要安全说明:
默认设置将检查确保远程方的证书已由受信任的第三方证书颁发机构(例如,verisign)签名,并且证书未过期。您必须通过kCFStreamSSLPeerName密钥给它一个要验证的名称,否则它将不会验证证书上的名称。
注意:这对安全性的影响很重要。
假设您正在尝试建立与MySecureServer.com的安全连接,但是由于DNS服务器被黑,您的套接字被定向到MaliciousServer.com。如果仅使用默认设置,并且MaliciousServer.com拥有有效的证书,则默认设置将不会检测到任何问题,因为该证书有效。为了在这种特定情况下正确保护连接,应将kCFStreamSSLPeerName属性设置为“ MySecureServer.com”。
如果您事先不知道远程主机的对等名称(例如,不确定是“ domain.com”还是“ www.domain.com”),则可以使用默认设置验证证书,然后在固定套接字后使用X509Certificate类验证颁发者。X509Certificate类是CocoaAsyncSocket开源项目的一部分。
您还可以在socketDidSecure中执行附加验证。
注意:SSL / TLS支持是使用Apple的安全传输框架实现的。这意味着您实际上可以访问更广泛的安全选项。除了在iOS上,苹果公司决定将安全传输设为私有框架。因此,在iOS上,您只能使用CFStream提供的功能。



高级

autoDisconnectOnClosedReadStream

@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;

传统上,套接字直到会话结束才关闭。但是,远程端点在技术上可能会关闭其写入流。然后,将通知我们的套接字没有更多数据可读取,但是我们的套接字仍将是可写的,并且远程端点可以继续接收我们的数据。
这种令人困惑的功能的争论源于这样的想法,即客户端可以在向服务器发送请求后关闭其写流,从而通知服务器不再有其他请求。但是实际上,这种技术对服务器开发人员的帮助很小。
更糟糕的是,从TCP的角度来看,无法区分读取流关闭和完全套接字关闭的区别。它们都导致TCP堆栈接收FIN数据包。唯一的告诉方法是继续写入套接字。如果只是关闭读取流,则写入将继续工作。否则,将很快发生错误(当远端向我们发送RST数据包时)。
除了技术挑战和混乱之外,许多高级套接字/流API也未提供处理问题的支持。如果读取流已关闭,则API立即声明套接字已关闭,并且也会关闭写入流。实际上,这就是Apple的CFStream API所做的。乍一看听起来像是糟糕的设计,但实际上它简化了开发。
在大多数情况下,如果关闭读取流,则是因为远程端点关闭了其套接字。因此,实际上在此时关闭插座是有意义的。实际上,这正是大多数网络开发人员想要并期望发生的事情。但是,如果您正在编写与大量客户端交互的服务器,则可能会遇到使用不受欢迎的关闭其写入流技术的客户端。在这种情况下,可以将此属性设置为NO,并使用socketDidCloseReadStream委托方法。
默认值为是。

markSocketQueueTargetQueue & unmarkSocketQueueTargetQueue

- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;

GCDAsyncSocket通过使用内部串行调度队列来维护线程安全。在大多数情况下,实例会自己创建此队列。但是,为了获得最大的灵活性,可以在init方法中传递内部队列。这允许一些高级选项,例如通过目标队列控制套接字优先级。然而,当开始像这样使用目标队列时,它们会打开一些特定死锁问题的大门。

例如,假设有两个队列:

dispatch_queue_t socketQueue;
dispatch_queue_t socketTargetQueue;

如果这样做(伪代码):

socketQueue.targetQueue = socketTargetQueue;

然后,所有socketQueue操作实际上都将在给定的socketTargetQueue上运行。这很好,在大多数情况下都很有效。但是,如果直接从访问套接字的socketTargetQueue中运行代码,则可能会出现死锁。
想象一下下面的代码:

- (BOOL)socketHasSomething
{
     __block BOOL result = NO;
    dispatch_block_t block = ^{
        result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
     }
    if (is_executing_on_queue(socketQueue))
        block();
    else
        dispatch_sync(socketQueue, block);
    
    return result;
 }

如果从socketTargetQueue调用此方法,会发生什么情况?结果是死锁。这是因为GCD API没有提供发现队列目标队列的机制。因此,我们不知道socketQueue是否配置了targetQueue。如果我们掌握了这些信息,就可以轻松避免僵局。但是,由于这些API丢失或不可行,您必须显式地设置它。

如果通过init方法传递socketQueue,并且已将传递的socketQueue配置了target queue,则应在目标层次结构中传递结束队列。

例如,考虑以下队列层次结构:
socketQueue -> ipQueue -> moduleQueue

这个例子演示了一些服务器中的优先级设置。来自同一IP地址的所有传入客户端连接都在同一目标队列上执行。特定模块的所有连接都在同一个目标队列上执行。因此,整个模块的所有联网优先级都可以动态更改。此外,来自单个IP的网络流量不能独占模块。

以下是你将如何完成这样的事情:

- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
{
    dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
    dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
    
    dispatch_set_target_queue(socketQueue, ipQueue);
    dispatch_set_target_queue(iqQueue, moduleQueue);
     
    return socketQueue;
}
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    [clientConnections addObject:newSocket];
    [newSocket markSocketQueueTargetQueue:moduleQueue];
 }

注意:只有当您打算直接在ipQueue或moduleQueue上执行代码时,才需要此解决方案。
通常情况并非如此,因为这样的队列仅用于执行整形。

performBlock:

- (void)performBlock:(dispatch_block_t)block

从套接字内部队列的外部访问某些变量不是线程安全的。
例如,套接字文件描述符。文件描述符只是引用每个进程文件表中索引的整数。当请求一个新的文件描述符(通过打开文件或套接字)时,保证返回的文件描述符是编号最小的未使用的描述符。因此,如果我们不小心,可能会发生以下情况:

  1. 线程A调用一个方法,该方法返回套接字的文件描述符。
  2. 在线程B上通过套接字内部队列关闭套接字。
  3. 线程C打开一个文件,然后接收以前是套接字的FD的文件描述符。
  4. 线程A现在正在访问/更改文件,而不是套接字。

除此之外,其他变量实际上也不是对象,因此无法保留/释放甚至自动释放。一个示例是SSLContextRef类型的sslContext,它实际上是一个malloc结构。
尽管有些内部变量使维护线程安全性变得困难,但确保在多种环境中对此类的这些变量可以访问使用也是很重要。这可以通过在套接字的内部队列上调用一个block来完成。可以从block内调用以下方法,以线程安全的方式访问那些通常是线程不安全的内部变量。给定的block将在套接字的内部队列上同步调用。
如果保存引用任何受保护的变量并在block外使用它们,则后果自负。

socketFD

- (int)socketFD

该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
提供对套接字文件描述符的访问。
此方法通常用于传出客户端连接。如果套接字是服务器套接字(正在接受传入连接),则它实际上可能具有多个内部套接字文件描述符-一个用于IPv4,一个用于IPv6。
如果套接字断开连接,则返回-1。

socket4FD

- (int)socket4FD

该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
提供对套接字文件描述符的访问(如果正在使用IPv4)。
如果套接字是服务器套接字(正在接受传入连接),则它实际上可能具有多个内部套接字文件描述符-一个用于IPv4,一个用于IPv6。
如果套接字已断开连接,或者未使用IPv4,则返回-1。

socket6FD

- (int)socket6FD

该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
提供对套接字文件描述符的访问(如果正在使用IPv6)。
如果套接字是服务器套接字(正在接受传入连接),则它实际上可能具有多个内部套接字文件描述符-一个用于IPv4,一个用于IPv6。
如果套接字已断开连接,或者未使用IPv6,则返回-1。

readStream

- (CFReadStreamRef)readStream

此方法仅在iOS(TARGET_OS_IPHONE)上可用。
该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
提供对套接字内部CFReadStream的访问(如果套接字上已启动SSL / TLS)。
注意:Apple已决定在iOS上将SecureTransport框架保持私有。这意味着提供SSL / TLS的唯一方法是通过CFStream或位于其之上的其他API。因此,为了在iOS上提供SSL / TLS支持,我们不得不依赖CFStream,而不是首选的、更快、更强大的SecureTransport。仅当已调用startTLS来启动SSL / TLS时才创建读/写流。
如果一个套接字没有启用后台运行,并且该套接字在应用程序后台时关闭,苹果只会通过CFStream API通知我们。在这种情况下,更快、更强大的GCD API没有得到正确的通知。
另请参见:enableBackgroundingOnSocket

writeStream

- (CFWriteStreamRef)writeStream

此方法仅在iOS(TARGET_OS_IPHONE)上可用。
该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
提供对套接字内部CFWriteStream的访问(如果套接字上已启动SSL / TLS)。
注意:Apple已决定在iOS上将SecureTransport框架保持私有。这意味着提供SSL / TLS的唯一方法是通过CFStream或位于其之上的其他API。因此,为了在iOS上提供SSL / TLS支持,,我们不得不依赖CFStream,而不是首选的、更快、更强大的SecureTransport。仅当已调用startTLS来启动SSL / TLS时才创建读/写流。
如果一个套接字没有启用后台运行,并且该套接字在应用程序后台时关闭,苹果只会通过CFStream API通知我们。在这种情况下,更快、更强大的GCD API没有得到正确的通知。
另请参见:enableBackgroundingOnSocket

enableBackgroundingOnSocket
- (BOOL)enableBackgroundingOnSocket;

此方法仅在performBlock:调用的上下文中可用。请参阅上面performBlock:方法的文档。将套接字配置为允许它在iOS应用程序进入后台时运行。
换句话说,此方法创建读写流,并调用:
CFReadStreamSetProperty(readStream、kCFStreamNetworkServiceType、kCFStreamNetworkServiceTypeVoIP);

CFWriteStreamSetProperty(writeStream、kCFStreamNetworkServiceType、kCFStreamNetworkServiceTypeVoIP);

如果成功则返回YES,否则返回NO。
注意:苹果官方不支持后台服务器socket。也就是说,如果你的套接字接受传入连接,苹果官方不支持允许iOS应用程序在应用程序后台时接受传入连接。
示例用法:

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    [asyncSocket performBlock:^{
        [asyncSocket enableBackgroundingOnSocket];
    }];
 }

sslContext

- (SSLContextRef)sslContext

此方法仅在Mac OS X(TARGET_OS_MAC)上可用。
该方法仅在performBlock:调用的上下文中可用。请参阅上面的performBlock:方法的文档。
如果已在套接字上启动SSL / TLS,则提供对套接字的SSLContext的访问。



公共

lookupHost: port: error:

+ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr;

类使用的地址查找实用程序。
此方法是同步的,因此建议在后台线程/队列上使用。
特殊字符串“localhost”和“loopback”返回IPv4和IPv6的loopback地址。
@returns
通过getaddrinfo方法返回的所有IPv4和IPv6地址的可变数组。
这些地址专门用于TCP连接。
如果需要,可以使用类提供的其他实用程序方法筛选地址。

hostFromAddress:

+ (NSString *)hostFromAddress:(NSData *)address

从原始地址数据中提取主机IP信息。
该地址应该是包装在NSData中的“ struct sockaddr”。对于IPv4,这将是“ struct sockaddr_in”。对于IPv6,这将是“ struct sockaddr_in6”。
返回的主机将采用演示文稿格式(演示文稿格式???)。(inet_ntop)
如果给出的地址无效,则返回nil。

portFromAddress:

+ (UInt16)portFromAddress:(NSData *)address

从原始地址数据中提取端口。
该地址应该是包装在NSData中的“ struct sockaddr”。对于IPv4,这将是“ struct sockaddr_in”。对于IPv6,这将是“ struct sockaddr_in6”。
返回的端口将从网络订单转换为主机订单。(ntohs)
返回0表示给出了无效的地址。

getHost: port: fromAddress:

+ (BOOL)getHost:(NSString **)hostPtr port:(UInt16 *)portPtr fromAddress:(NSData *)address

从原始地址数据中提取主机IP和端口信息。
该地址应该是包装在NSData中的“ struct sockaddr”。对于IPv4,这将是“ struct sockaddr_in”。对于IPv6,这将是“ struct sockaddr_in6”。
hostPtr(如果给定)将使用inet_ntop设置。portPtr(如果给定)将使用ntohs进行设置。
如果给出的地址无效,则返回NO(在这种情况下,hostPtr和portPtr都不会被修改)。

getHost: port: family: fromAddress:

+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address;

family 协议族 IPV4或IPV6

isIPv4Address
+ (BOOL)isIPv4Address:(NSData *)address;

是否IPv4地址

isIPv6Address
+ (BOOL)isIPv6Address:(NSData *)address;

是否IPv6地址

CRLFData

+ (NSData *)CRLFData

回车,换行。(0x0D0A)
常见的行分隔符,与readDataToData:...方法一起使用。

CRData

+ (NSData *)CRData

回车。(0x0D)
常见的行分隔符,与readDataToData:...方法一起使用。

LFData

+ (NSData *)LFData

换行。(0x0A)
常见的行分隔符,与readDataToData:...方法一起使用。

ZeroData

+ (NSData *)ZeroData

零字节。也称为空字节或空字符(在字符串的末尾)。(0x00)
常见的行分隔符,与readDataToData:...方法一起使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343