BOOL result = [_socket connectToHost:@"www.baidu.com" onPort:80 error:nil];
if (result) {
NSLog(@"连接成功");
}else{
NSLog(@"连接失败");
}
- (BOOL)connectToHost:(NSString *)inHost
onPort:(uint16_t)port
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
NSString *host = [inHost copy];
NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *preConnectErr = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
preConnectErr = [self badParamError:msg];
return_from_block;
}
//zc read:确认有代理,有代理队列,否则...
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
return_from_block;
}
flags |= kSocketStarted;
NSString *hostCpy = [host copy];
int aStateIndex = stateIndex;
__weak GCDAsyncSocket *weakSelf = self;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//zc read:确认地址信息是IPv4还是IPv6
NSError *lookupErr = nil;
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
if (lookupErr)
{
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
[strongSelf lookup:aStateIndex didFail:lookupErr];
}});
}
else
{
NSData *address4 = nil;
NSData *address6 = nil;
for (NSData *address in addresses)
{
if (!address4 && [[self class] isIPv4Address:address])
{
address4 = address;
}
else if (!address6 && [[self class] isIPv6Address:address])
{
address6 = address;
}
}
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//zc read:给的地址与配置是否符合(如:IPv4Enabled==NO,给的地址却是IPv4的就不OK了).符合就开始连接
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
}
#pragma clang diagnostic pop
}});
[self startConnectTimeout:timeout];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr) *errPtr = preConnectErr;
return result;
}
确认host没问题;
确认有代理,有代理队列;
走DNS的lookup,
+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
DNS有可能返回不止一个ip地址;所以才会有接下来的参数既有address4又有address6的方法.
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr;//bsd socket创建
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex;/bsd socket连接
IPv6的情况我们不多说,接着看- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
.
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
//zc read1:创建socket
int socketFD = socket(family, SOCK_STREAM, 0);
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return socketFD;
}
//zc read:客户端不走bind
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
[self closeSocket:socketFD];
return SOCKET_NULL;
}
// Prevent SIGPIPE signals
int nosigpipe = 1;
//zc read:设置与某个套接字关联的选项
/*
1.套接字描述符
2.级别
3.选项名
4.读取指针
5.读取长度
SO_NOSIGPIPE是为了避免网络错误,而导致进程退出。用这个来避免系统发送signal
*/
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
return socketFD;
}
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
// If there already is a socket connected, we close socketFD and return
if (self.isConnected)
{
[self closeSocket:socketFD];
return;
}
// Start the connection process in a background queue
__weak GCDAsyncSocket *weakSelf = self;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//zc read2:socket连接
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
if (strongSelf.isConnected)
{
[strongSelf closeSocket:socketFD];
return_from_block;
}
if (result == 0)
{
[self closeUnusedSocket:socketFD];
[strongSelf didConnect:aStateIndex];
}
else
{
[strongSelf closeSocket:socketFD];
// If there are no more sockets trying to connect, we inform the error to the delegate
if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
{
NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
[strongSelf didNotConnect:aStateIndex error:error];
}
}
}});
#pragma clang diagnostic pop
});
LogVerbose(@"Connecting...");
}
到此为止,bsd socket
级别的int socket(int, int, int);
和int connect(int, const struct sockaddr *, socklen_t);
都被调用了.
int socketFD = socket(family, SOCK_STREAM, 0);
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
连接成功后调用:
- (void)didConnect:(int)aStateIndex
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring didConnect, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
flags |= kConnected;
[self endConnectTimeout];
#if TARGET_OS_IPHONE
// The endConnectTimeout method executed above incremented the stateIndex.
aStateIndex = stateIndex;
#endif
// Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
//
// Note:
// There may be configuration options that must be set by the delegate before opening the streams.
// The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
//
// Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
// This gives the delegate time to properly configure the streams if needed.
dispatch_block_t SetupStreamsPart1 = ^{
#if TARGET_OS_IPHONE
if (![self createReadAndWriteStream])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
if (![self registerForStreamCallbacksIncludingReadWrite:NO])
{
[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
return;
}
#endif
};
dispatch_block_t SetupStreamsPart2 = ^{
#if TARGET_OS_IPHONE
if (aStateIndex != stateIndex)
{
// The socket has been disconnected.
return;
}
if (![self addStreamsToRunLoop])
{
[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
return;
}
if (![self openStreams])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
#endif
};
// Notify delegate
NSString *host = [self connectedHost];
uint16_t port = [self connectedPort];
NSURL *url = [self connectedUrl];
__strong id theDelegate = delegate;
if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
{
SetupStreamsPart1();
//zc read:delegateQueue与socketQueue的分工可见一斑
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToHost:host port:port];
dispatch_async(socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});
}
else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
{
SetupStreamsPart1();
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToUrl:url];
dispatch_async(socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});
}
else
{
SetupStreamsPart1();
SetupStreamsPart2();
}
// Get the connected socket
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
// Enable non-blocking IO on the socket
//zc read:使socket支持非阻塞IO
int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (result == -1)
{
NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
[self closeWithError:[self otherError:errMsg]];
return;
}
// Setup our read/write sources
[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
// Dequeue any pending read/write requests
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
1.根据传入的是host+port还是url进行不同的回调;
2.开启[dispatch_source readSource对socketFD有数据可读的监听]和[dispatch_source writeSource对socketFD有空间可写的监听];
3.maybeDequeueRead与maybeDequeueWrite看是否有数据可以读或者可以写;
4.用socketFD创建readStream和writeStream,两个Stream加入RunLoop,持续监听socket.
<<持续监听socket>>
如果你对AFN2.0由一定了解,对开一个专属线程来供网络收发数据的做法一定不陌生.这里也需要持续监听socket,用的也是类似的方法.
创建一个新的线程,线程加入timer(一直存在),保证线程一直存在.这样线程对应的RunLoop也会一直存在;
用socketFD创建readStream和writeStream,两个Stream加入RunLoop;
这样就完成了对socket的持续监听.
<
>
未用到CFStream for TLS
的情况下,readStream
与writeStream
作用只是为了串起socketFD和RunLoop,没有其他作用.如果你对CFStream
有一定了解,CFStream
是有方法告诉外围内部封装好的bsd socket
有空间可写或者有数据可读的,但是未用到CFStream for TLS
的情况下CFStream
的代理方法是没有用的,bsd socket
有空间可写或者有数据可读都是依靠dispatch_source
告诉外围.方法- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
很好的说明了这一点.CFStream for TLS
的内容我们会在后说.
- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
{
LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
streamContext.version = 0;
streamContext.info = (__bridge void *)(self);
streamContext.retain = nil;
streamContext.release = nil;
streamContext.copyDescription = nil;
//zc read:set Client readStream
CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (includeReadWrite)
readStreamEvents |= kCFStreamEventHasBytesAvailable;
if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
{
return NO;
}
//zc read:set Client writeStream
CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (includeReadWrite)
writeStreamEvents |= kCFStreamEventCanAcceptBytes;
if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
{
return NO;
}
return YES;
}
到此为止,CocoaAsyncSocket
的创建与连接的过程就全部结束.