Step by step to build your iOS network layer – TCP articles

C: Hello, i’m black-flower, is anybody there
s: Yes, i’m? White-flower, do you still at there
c:? Yes, yes, i’m here! Mr.white-flower.
………… Successful… Connection………………..


This article is based on the establishment of CocoaAsyncSocket from the TCP connection to the results of the request for you to deal with how to build a simple and easy to use iOS network layer, the full text of about five thousand words, is expected to take the time to read for 20 – 30 minutes

PS: due to the follow-up will involve some concepts to explain, reading is boring, so the first release to achieve the ultimate effect jiefa solution:

1 when you are sure that you need to connect to the server address and port number (set in the HHSocketService), you can create a connection in the sub thread and support the broken wire / disconnected network connection Socket connection

Self.socket = [HHSocket socketWithDelegate:self]; or [[HHSocketClient sharedInstance] connect];

2 when you are sure that the rules of the private part of the assembly after the assembly rules (set up in the HHSocketRequest SocketTask:), you can create a NSURLSessionDataTask behavior in accordance with the following exactly the same

Int URL NSDictionary = 123; *requestHeader = nil; PBGeneratedMessage = *requestParams nil; HHSocketRequest *request = [HHSocketRequest normalRequestWithMessageType:url message:requestParams header:requestHeader]; HHSocketTask *task = "HHSocketClient sharedInstance] dataTaskWithRequest:request completionHandler:^ (NSError *error, ID result) {//do something}]; [task resume]; [task cancel];

3 because SocketTask and NSURLSessionDataTask have the same behavior interface, so in the HTTP for the APIManger (recommended the design of distributed network request, DispatchGroup), design such as APIRecorder could also be used for SocketTask, please refer to the specific HTTP


  • Establish a reliable network connection
    1 connection definition
    2 connection establishment and shutdown
    3 automatic reconnection processing
  • Custom network task
    1 custom network protocol
    2 protocol based on the request for the development of
    request for the task of the 3
  • Network tasks are distributed
    1 task distributed
    2 task to cancel the
    3 multi server switch
  • Fair use request distributor

Establish a reliable network connection

The 1 connection defined in
network connection, we first describe the process about the phone before the call. We must first connect to each other, and then began to chat, no one on the phone the first two single paragraph, then dial. Connection, I have to listen to each other’s voice through the phone. Through the microphone to send their own voice. In addition, during the call if the signal fluctuation is disconnected, we usually immediately back in the past to continue the call. Finally, at the end of the call, we will take the initiative to each other or disconnect the call connection.
to a certain extent, the network communication and the phone is almost in the process of communication, dial correspond to establish a network connection, call back and hang on even corresponding and disconnected, the receiver is to receive data, and the microphone is issued According to the above description, our network connection is defined as follows:

@class HHSocket; @protocol HHSocketDelegate < NSObject> - (void) socketCanNotConnectToService: (HHSocket * sock); - (void) socket: (HHSocket * sock) didConnectToHost: (NSString *) host port: (uint16_t) port; (void) - socketDidDisconnect: (HHSocket * sock) error: (NSError * error); - (void) socket: (HHSocket * sock) didReadData: (NSData * data); @end @interface HHSocket: NSObject + (instancetype) socketWithDelegate: (id< HHSocketDelegate> delegate; (instancetype) + socketWithDelegate: (id<); HHSocketDelegate> delegate) delegateQueue: (dispatch_queue_t) - (BOOL) delegateQueue; isConnectd; close; - (void) - (void) connect; disconnect; - (void) - (void) connectWithRetryTime: (NSUInteger) retryTime; (void) - writeData: (NSData * data); - (void) switchService; (void) switchToService: (HHServiceType) serviceType; @end

2 connection to establish and close the definition of
interface to see the meaning of the meaning will not explain, we look at the specific implementation:

- (instancetype) initWithDelegate: (id< HHSocketDelegate> delegate) delegateQueue: (dispatch_queue_t) {if (delegateQueue! Delegate) {return nil;} if (self = [super init]) {const char *delegateQueueLabel = [[NSString stringWithFormat:@%p_socketDelegateQueue, self] cStringUsingEncoding:NSUTF8StringEncoding]; self.service [HHSocketService = defaultService]; self.delegate = delegate; self.reconnectTime = ReconnectTime; self.delegateQueue = delegateQueue? Dispatch_queue_create (delegateQueueLabel, DISPATCH_QUEUE_SERIAL); self.socket [[GCDAsyncSocket = alloc] initWithDelegate:self delegateQueue:delegateQueue]; self.machPort [NSMachPort = port]; self.keepRuning = Y ES; self.socket.IPv4PreferredOverIPv6 = NO; [NSThread detachNewThreadSelector:@selector (configSocketThread) toTarget:self withObject:nil];} return self;}
- (void configSocketThread) {if (self.socketThread = = Nil) {self.socketThread = [NSThread currentThread]; [[NSRunLoop currentRunLoop] addPort:self.machPort forMode:NSDefaultRunLoopMode];} while {[[NSRunLoop (self.keepRuning) currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]] [[NSRunLoop currentRunLoop] removePort:self.machPort forMode:NSDefaultRunLoopMode];}; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; [self.socketThread cancel]; self.socket = nil; self.service = nil; self.machPort = nil; self.socketThread = nil; self.delegateQueue = nil;}

The internal connection process we use GCDAsyncSocket to complete the actual, the establishment of a GCDAsyncSocket to a delegate and delegateQueue, we set delegate for yourself, delegateQueue is set to the Senate. In addition GCDAsyncSocket runloop queue or self built data processing based on reading, we don’t want this process to affect the UI reading fluency, so a socketThread to deal with these things.

With socket, we can connect:

- (void connect) {if (self.isConnecting ||! [HHAppContext sharedInstance].isReachable) {return}; BOOL isConnectd = self.socket.isConnected; [self = disconnect]; self.isConnecting YES; int64_t delayTime = isConnectd? (arc4random) 10 + 1.5 (%): 1; dispatch_after (dispatch_time (DISPATCH_TIME_NOW (int64_t) (delayTime * NSEC_PER_SEC) dispatch_get_global_queue). (2, 0), ^{[self performSelector:@selector (connectOnSocketThread) onThread: self.socketThread withObject:nil waitUntilDone:YES];}});
- (void) connectOnSocketThread [self.socket setDelegate:self delegateQueue:self.delegateQueue] BOOL {isSuccess = [self.socket; onPort:self.service.port error:nil]; self.isConnecting = NO; isSuccess [self reconnect];}?
#pragma mark - GCDAsyncSocketDelegate - (void) socket: (GCDAsyncSocket * sock) didConnectToHost: (NSString *) host port: (uint16_t port) {if ([self.delegate respondsToSelector:@selector (socket:didConnectToHost:port:)] [self.delegate socket:self didConnectToHost:host) {port:port]}; self.reconnectTime = ReconnectTime; [self.socket readDataWithTimeout:-1 tag:SocketTag];} - (void) socketDidDisconnect: (GCDAsyncSocket * sock withError: (NSError) * (if error) {[self.delegate respondsToSelector:@selector] (socketDidDisconnect:error:)) {[self.delegate socketDidDisconnect:self error:error];}} - (void) socket: (GCDAsyncSocket *) sock didWriteDataWithTag: (long) tag readDataWithTimeout:-1 tag:Sock {[self.socket EtTag];} - (void) socket: (GCDAsyncSocket * sock) didReadData: (NSData *) data withTag: (long tag) {if ([self.delegate respondsToSelector:@selector (socket:didReadData:)] [self.delegate) {socket:self didReadData:data];} [self.socket readDataWithTimeout:-1 tag:SocketTag];}

Before connecting us to disconnect before. You need a server address and port connection, this part HTTP and a connection is the same, no longer. In addition, the connection method of GCDAsyncSocket is a synchronous execution process, so we put it into the connection before the establishment of the socketThread. Finally, if the connection fails, we will call reconnect reconnection, a successful connection began to read the data and send the data to the outside. As for the code inside the delayTime will introduce below.

3 automatic reconnection

- (void reconnect) {if (self.isConnecting ||! [HHAppContext sharedInstance].isReachable) {return}; self.reconnectTime = 1; if (self.reconnectTime > = 0; [self) {connect]}; else if ([self.delegate respondsToSelector:@selector (socketCanNotConnectToService:)])} {[self.delegate socketCanNotConnectToService:self];} - (void) connectWithRetryTime: (NSUInteger) {retryTime self.reconnectTime = retryTime > 0? RetryTime: ReconnectTime; [self connect];}

In the automatic reconnection before, we first introduce the heart keep alive mechanism. The general heart two, one-way and two-way Ping mechanism ping-pong mechanism. The specific
, Ping timing mechanism refers to the other party to the sender sends a heartbeat packet, the receiver will know each other online to receive, at the same time a reply a heartbeat packet, the sender receives the reply can identify each other online, or that the other is not online, then the sender will automatically disconnect. This is the sender client.
is one-way heartbeat, the receiver is in a passive position, after receiving the heartbeat later until the next heartbeat before the arrival interval time and it could not determine whether the other is online, typically elevator tunnel like a scene, but although the connection signal is too weak, not enough to carry on the network This leads to ping-pong. The communication mechanism, the server will require the reply to a heartbeat in a message, if there is no reply within the prescribed time, so that the message failed to send off the invalid connection waiting for the client to reconnect, if it is important to consider a message by APNS pushed to the client. Similarly, if is the client sends a request, the server does not receive a reply within the stipulated time, it’s a timeout error is returned to the calling page, many overtime also need to take the initiative to disconnect and reconnect.

Back to the reconnection problem, as the recipient of the client server active if it is broken, this shows that the network may be a problem, even later. But if the client is disconnected in addition to network problems there may be already server overload or hang up, unable to reply heartbeat. Then if all the client immediately after. At the same time to connect, so just to restore the server at the same time in the face of the hundreds of thousands of connections will soon be destroyed, vicious spiral. This is the reason why in the connect method for connecting delayTime will have a random number.

Two. Custom network tasks

After the above process is finished we can have a self managed socket connection, therefore, we don’t have to deal with all kinds of attention connection logic, but can focus on the network to send and receive data.
data is in the form of stream transmission in TCP, a plurality of data packets are transmitted at the same end to end. A stream, such data flow even at the receiving end and cannot be resolved properly (that is, therefore, sticky package) before the data package sent, we need to mark the assembled data packets, each packet to distinguish the boundary of this marker is packet based HTTP is TCP in Baotou. In the time before the request is automatically add these data, so usually we only need to provide the requested data does not need to care about the data in the HTTP head, because it has already been dealt with. But when I When we face TCP, these need to deal with our own

1 custom
network protocol as described above, the definition of network requests will be divided into two parts, first request and request, the General Assembly requested at the request of the back of the head, the parameters of the request body is corresponding to the request / data, and the data packet request header is described, the necessary fields include: the requested operation (messageType, HTTP, URL analog serial number (messageSerialNum) request, request identifier, the request body length (messageContentLength), anti stick package). The other field is directly linked with the company business, such as for data check checkSum or request tail, automatic login HTTP sessionId is often used in identifying resources change the state of ETag and Last-Modified and so on… Also assembled
headers and the request body also has two kinds of direct assembling and separator assembly.
The following is the following format:

Header Body
MsgType-msgSN-msgCL-blabla… MsgBody

The separator assembly has the following format (delimiter = = /r/n):

Header Body
MsgType-msgSN-msgCL-blabla… -/r/n MsgBody-/r/n

The common point: length and head inside each field request first two assembling the position and length is constant, so the party can receive data analysis. The two different
: direct way is to take to the requested assembly head, according to the request of the head of the Length field to intercept the back of the request body, finally through the checkSum request or tail check data, there are problems on the abandoned Length field for this process must correctly get the request header and the request body also received complete (i.e. not otherwise, subsequent analysis of packet loss) all have problems, only reconnection. And separator assembly usually is the first to read the first /r/n to request a /r/n read head, get the request body, both by comparing the problem away, no problem, even if the loss, as long as not lose /r/n, The subsequent analysis will have no problems, not even
. Our use is directly assembled, as to why I came to the company before they did. But the TCP itself is a reliable connection, there are a variety of mechanisms to protect the integrity of data arrival, so the packet loss probability is very small, it is not out of what problem.

2 according to the protocol
request according to the above description, we need a custom class request network, provide the request header and the request body corresponding to it, the output arrangement request data packet. The good data request as an example:

Typedef enum: NSUInteger {HHSocketRequestHeader0 = 1, HHSocketRequestHeader1, HHSocketRequestHeader2} / /... HHSocketRequestHeader; @interface: HHSocketRequest NSObject @property (assign, nonatomic) NSUInteger timeoutInterval; (instancetype) + heartbeatRequestWithSerialNum: (int) serialNum; /**< * / heartbeat task request (instancetype) + normalRequestWithMessageType: (HHSocketMessageType) type message: (PBGeneratedMessage * message header: (NSDictionary) header / * *); *< data requests / + (instancetype) cancelRequestWithMessageType: (HHSocketMessageType) type message: (PBGeneratedMessage * message); /**< cancel task request - * / (NSNumber *) requestIdentifier; @end

Because the type of request (URL) is very important, so singled out as a parameter of non transmission can remind, request the head of other non essential fields are declared in the HHSocketRequestHeader to header to the Senate, as for the request of our body, is used in the Google ProtocolBuffers, many online information, not to repeat them.
defines three types of data interface according to the operation request, because the heartbeat request is not carrying any information, so only need a serial number, only the actual data requests will need three parameters, to cancel the request below. To look at the specific implementation:

@interface (HHSocketRequest) @property (strong, nonatomic) NSNumber *requestIdentifier @property (strong, nonatomic); NSMutableData *formattedData; @end #define TimeoutInterval 8 #define HHSocketTaskInitialSerialNumber 50 implementation @ HHSocketRequest - init (instancetype) {if (self = [super init]) {self.formattedData [NSMutableData = data]; self.timeoutInterval = TimeoutInterval;} return self;} + (int) currentRequestIdentifier int static {static currentRequestIdentifier; dispatch_semaphore_t lock; static dispatch_once_t onceToken; dispatch_once (& onceToken, ^{currentRequestIdentifier = HHSocketTaskInitialSerialNumber; lock = dispatch_semaphore_create (1);}); Dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); currentRequestIdentifier = 1; dispatch_semaphore_signal (lock); return currentRequestIdentifier;} #pragma mark - Interface (Public) + (instancetype) heartbeatRequestWithSerialNum: (int serialNum) {int messageType = HHSocketRequestTypeHearbeat; HHSocketRequest = *request [HHSocketRequest new]; request.requestIdentifier = @-1; [request.formattedData appendData:[HHDataFormatter msgTypeDataFromInteger:messageType]]; [request.formattedData appendData:[HHDataFormatter msgSerialNumberDataFromInteger:serialNum]]; [request.formattedData appendData:[HHDataFormatter msgContentLengthDataFromInteger:0]]; [request.formattedData appendData:[HHDataFormatt Er adler32ToDataWithProtoBuffByte:nil length:0]]; [request.formattedData appendData:[HHDataFormatter msgTypeDataFromInteger:messageType]]; return request;} + (instancetype) normalRequestWithMessageType: (HHSocketMessageType) messageType message: (PBGeneratedMessage * message) header: (NSDictionary * header) {int requestIdentifier = [self currentRequestIdentifier]; int messageLength = (int); HHSocketRequest *request [HHSocketRequest = new]; request.requestIdentifier = @ (requestIdentifier); [request.formattedData appendData:[HHDataFormatter msgTypeDataFromInteger:messageType]] [request.formattedData; appendData:[HHDataFormatter msgSerialNumberDataFromInteger:requestIdentifier]]; request.f OrmattedData appendData:[HHDataFormatter msgContentLengthDataFromInteger:messageLength]]; / / [request.formattedData appendData:[header[@ (HHSocketRequestHeader0)] dataUsingEncoding:NSUTF8StringEncoding]]; / / [request.formattedData appendData:[header[@ (HHSocketRequestHeader1)] dataUsingEncoding:NSUTF8StringEncoding]]; / / [request.formattedData appendData:[header[@ (HHSocketRequestHeader2)] dataUsingEncoding:NSUTF8StringEncoding]]; [request.formattedData]; request.formattedData appendData:[HHDataFormatter [adler32ToDataWithProtoBuffByte: (Byte *) length:messageLength]]; [request.formattedData appendData:[HHDataFormatter msgTypeDataFromInteger:messageType]]; return request;} + (instancetype) cancelRequestWithMessageType: (HHSocketMessageType) canceledType message: (PBGeneratedMessage * message) {int messageType = HHSocketRequestTypeCancel; int = canceledMessageLength (int); HHSocketRequest * request = [HHSocketRequest new]; request.requestIdentifier = @-1; / / return request #pragma} ibid; Mark - Interface (Friend) - (NSData * requestData) {return self.formattedData}; @end
Typedef enum: NSUInteger {HHSocketRequestTypeHearbeat = 0, HHSocketRequestTypePush, HHSocketRequestTypeCancel} HHSocketRequestType;

We refer to NSURLSessionTask for each request an incremental identifier request sequence number, the Identifier logo, all from the request sent by the client, and like the heartbeat and online push these requests are not included, but the definition in the HHSocketRequestType enumeration, so when parsing out the request sequence number we can according to the serial number of the defined rules this is the judgment data request Response or heartbeat or server push, here we reserved 50 serial number.
HHDataFormatter is convenient for expanding the tools of a int/data conversion, internal go is realized with a given external, multiple interfaces to improve readability and expand, also pay attention to int data has a size end (i.e. network byte order and host byte order), the other is not what to say.

3 requests for task
the above request only on a network operation description, it knows only what they want to do, but do not know what time will be launched, what time was cancelled after the completion of the operation and what to do. So to request specific management, we define a HHSocketTask:

Typedef enum: NSUInteger {HHSocketTaskStateSuspended = 0, HHSocketTaskStateRunning = 1, HHSocketTaskStateCanceled = 2, HHSocketTaskStateCompleted = 3} HHSocketTaskState; typedef void (^HHNetworkTaskCompletionHander) (NSError *error, ID result); @interface: NSObject + HHSocketTask (NSError *) taskErrorWithResponeCode: (NSUInteger) code; (instancetype) + taskWithRequest: (HHSocketRequest * request completionHandler: (HHNetworkTaskCompletionHander) completionHandler - (void)); cancel; resume; - (void) - (HHSocketTaskState) - state (NSNumber *); taskIdentifier; @end

HHSocketTask as a network task abstraction, responsible for the management of internal task state, task execution results callback, external exposure task distribution and cancellation of the interface. Now just waiting for someone to call these interfaces.

Three. Distribution of network tasks

Now we have HHSocketRequest and HHSocketTask, the next routine and HTTP routines, we need a distributed device to distribute tasks, keep the implementation of the tasks in the task distribution prior to processing tasks need to cancel the case, the task is removed in a distributed. As usual, the definition of a single case:

#import "HHSocketTask.h" @interface HHSocketClient: NSObject + (instancetype) sharedInstance - (void); connect; disconncet; - (void) - (HHSocketTask) dataTaskWithRequest: (HHSocketRequest *) request completionHandler: (HHNetworkTaskCompletionHander) completionHandler (HHSocketTask * dataTaskWithMessgeType:); - (HHSocketMessageType) type message: (PBGeneratedMessage * message) messageHeader: (NSDictionary *) messageHeader completionHandler: (HHNetworkTaskCompletionHander) completionHandler; - (NSNumber *) dispatchTask: (HHSocketTask * task); - (NSNumber) dispatchTaskWithMessgeType: (HHSocketMessageType) type message: (PBGeneratedMessage * message) messageHeader: (NSDictionary *) messageHeader completionHandler: (HHNetworkTaskCompletionHander) completionHandler - (void) cancelAllTasks; (void) cancelTaskWithTaskIdentifier: (NSNumber *) taskIdentifier; @end
@interface HHSocketClient (<); HHSocketDelegate> @property (strong, nonatomic) HHSocket *socket @property (strong, nonatomic); NSMutableData *readData; @property (strong, nonatomic) NSMutableDictionary< NSNumber * HHSocketTask, *> *dispathTable; @property (assign, nonatomic) CGFloat totalTaskCount; @property (assign, nonatomic) CGFloat (strong, errorTaskCount; @property nonatomic HHSocketHeartbeat *heatbeat; @end)

The design here is actually a problem, according to a socket connection should be corresponding to a data receiver and data sent, but our only one for the data connection request, so I have a lazy defined attributes. Each attribute name see knowledge meaning, not to repeat them. Look at the interface under the surface:

//HHSocketClient.m - (HHSocketTask) dataTaskWithRequest: (HHSocketRequest *) request completionHandler: (HHNetworkTaskCompletionHander) completionHandler *taskIdentifier arrayWithObject:@-1] {NSMutableArray = [NSMutableArray; HHSocketTask = [HHSocketTask * task taskWithRequest:request completionHandler:^ (NSError *error, ID result) {dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); [self checkSeriveWithTaskError:error]; [self.dispathTable removeObjectForKey: taskIdentifier.firstObject]; dispatch_semaphore_signal (lock); completionHandler (error, completionHandler? Result): Nil;}]; task.client = self; taskIdentifier[0] = task.taskIdentifier; dispatch_semaphore_wait (lock, D ISPATCH_TIME_FOREVER; [self.dispathTable setObject:task forKey:taskIdentifier.firstObject]; dispatch_semaphore_signal) (lock); return task;} - (NSNumber *) dispatchTask: (HHSocketTask * task) {if (task = = Nil) {return} [task @-1; resume]; return task.taskIdentifier;} - (NSNumber *) dispatchTaskWithMessgeType: (HHSocketMessageType) type message: (PBGeneratedMessage *) message messageHeader: (NSDictionary *) messageHeader completionHandler: (HHNetworkTaskCompletionHander) completionHandler return [self dispatchTask:[self dataTaskWithMessgeType:type message:message {messageHeader:messageHeader completionHandler:completionHandler]];}

This part of the code and HTTP are basically the same, only the task.client = self this line, this will be described below, the task distribution is a direct call to the task.resume method. Then take a look at the implementation of resume: a
1 task

//HHSocketTask.m @interface (HHSocketTask) @property (weak, nonatomic) id client @property (strong, nonatomic); NSTimer *timer; @property (assign, nonatomic) HHSocketTaskState state; @property (strong, nonatomic) HHSocketRequest *request @property (copy, nonatomic); HHNetworkTaskCompletionHander completionHandler; @end
//HHSocketTask.m (void resume) {if (self.state = = HHSocketTaskStateSuspended) {self.timer = [NSTimer scheduledTimerWithTimeInterval:self.request.timeoutInterval target:self selector:@selector (requestTimeout) userInfo:nil repeats:NO] NSRunLoop mainRunLoop] addTimer:self.timer "; forMode:NSRunLoopCommonModes]; self.state = HHSocketTaskStateRunning; [self.client resumeTask:self];}}
//HHSocketClient.m #pragma mark - Interface (Friend) - (void) resumeTask: (HHSocketTask *) task if (self.socket.isConnectd) {{[self.socket} {NSError writeData:task.taskData]; else *error; if ([HHAppContext sharedInstance].isReachable) {error = HHError (HHNetworkErrorNotice, HHNetworkTaskErrorDoNotConnectedToHost);} else {error = HHError (HHNetworkErrorNotice, HHNetworkTaskErrorCannotConnectedToInternet);} [self reconnect]; [task completeWithResponseData:nil error:error];}}
//HHSocketClient.m - (void) socket: (HHSocket * sock) didReadData: (NSData * data) {[self.heatbeat reset]; if (data.length = > HHMaxResponseLength) {return}; [self.readData appendData:data]; NSData *responseData = [self getParsedResponseData]; if (responseData) {NSNumber *taskIdentifier = @ ([HHSocketResponseFormatter responseSerialNumberFromData:responseData]); HHSocketTask *task = self.dispathTable[taskIdentifier]; if (task {dispatch_async (dispatch_get_global_queue) (2, 0), ^{[task completeWithResponseData:responseData error:nil];});} else {/ / switch ([taskIdentifier integerValue]) {/ / case HHSocketTaskPush: {// Push break}; / / case / / [self.heatbeat respondToServerWithSerialNum:[taskIdentifier intValue]] HHSocketTaskHearbeat: {// heartbeat; / / break / / default:}}}}}; / / - (NSData * getParsedResponseData) {NSData *responseData; NSData * totalReceivedData = self.readData; if (totalReceivedData.length = > HHMaxResponseLength * 2) {[self reconnect]; //socket parsing error, break even else (if totalReceivedData.length >} = MsgResponsePrefixLength) {HHSocketResponseFormatter *formatter = [HHSocketResponseFormatter formatterWithResponseData:totalReceivedData]; int msgContentLength = formatter.responseContentLength; Int msgResponseLength = msgContentLength + MsgResponseHeaderLength; if (msgResponseLength = = totalReceivedData.length) {responseData = totalReceivedData; self.readData = [NSMutableData data] else;} if (msgContentLength < totalReceivedData.length) {responseData [totalReceivedData = subdataWithRange:NSMakeRange (0, msgResponseLength); self.readData [[totalReceivedData = subdataWithRange:NSMakeRange (msgResponseLength, totalReceivedData.length - msgResponseLength)] mutableCopy] return responseData;}}};
//HHSocketTask.m #pragma mark - Interface (Friend) - (NSData * taskData) {return self.request.requestData;} - (void) completeWithResponseData: (NSData * responseData) error: (NSError * error) {if (self.state = < HHSocketTaskStateRunning) {NSData *responseContent; if (responseData = = Nil) {error} else {[self = taskErrorWithResponeCode:HHSocketTaskErrorUnkonwn]; int responseCode = [HHSocketResponseFormatter responseCodeFromData:responseData]; int responseContentLength = [HHSocketResponseFormatter responseContentLengthFromData:responseData]; NSData *responseAdler = [HHSocketResponseFormatter responseAdlerFromData:responseData]; responseContent [HHSocketResponseFormatter = responseContentFromData:responseData]; NSData = *adler [HHDataFormatter adler32ToDataWithProtoBuffByte: (Byte *) responseContent.bytes length:responseContentLength]; error = [self (taskErrorWithResponeCode: responseAdler isEqual:adler] responseCode HHSocketTaskErrorNoMatchAdler: [?]);} [self completeWithResult:responseContent error:error]; error? NSLog (@ socket request failed:%ld, error.code, error.domain% @ "): Nil;}} - (void) completeWithResult: (ID) result error: (NSError * error) {[self.timer invalidate]; dispatch_async (dispatch_get_global_queue (2, 0), ^{self.completionHandler? Self.completionHandler (error, result): Nil; self.compl EtionHandler = nil;}}); + (NSError *) taskErrorWithResponeCode: (NSUInteger code) {#define HHTaskErrorCase (responeCode, errorDomain) case responeCode: return HHError (errorDomain, code) switch (code) {HHTaskErrorCase (HHNetworkTaskErrorDefault, HHDefaultErrorNotice); HHTaskErrorCase (HHSocketTaskErrorInvalidMsgLength @ "message length illegal" (HHSocketTaskErrorLostPacket, HHTaskErrorCase); @ "background Adler verification message (packet loss)"); HHTaskErrorCase (HHSocketTaskErrorInvalidMsgFormat @ "message format is not legitimate"); HHTaskErrorCase (HHSocketTaskErrorUndefinedMsgType @ "message type not found"); HHTaskErrorCase (HHSocketTaskErrorEncodeProtobuf @ protobuf parsing failure "); HHTaskErrorCase (@ HHSocketTaskErrorDatabaseException, "database operation exception"); HHTaskErrorCase (HHSocketTaskErrorUnkonwn, @ "unknown error"); HHTaskErrorCase (HHSocketTaskErrorNoPermission @ "no authority"); HHTaskErrorCase (HHNetworkTaskErrorCannotConnectedToInternet, HHNetworkErrorNotice); HHTaskErrorCase (HHNetworkTaskErrorDoNotConnectedToHost @ "long connection failure"); HHTaskErrorCase (HHSocketTaskErrorLostConnection @ long connection fault open the connection "); HHTaskErrorCase (HHNetworkTaskErrorTimeOut, HHTimeoutErrorNotice); HHTaskErrorCase (HHNetworkTaskErrorCanceled @" task has been canceled "); HHTaskErrorCase (HHSocketTaskErrorNoMatchAdler @ Adler32 front end Failed to verify "); HHTaskErrorCase (HHSocketTaskErrorNoProtobuf, protobufBody @" empty "); HHTaskErrorCase (HHNetworkTaskErrorNoData, HHNoDataErrorNotice); HHTaskErrorCase (HHNetworkTaskErrorNoMoreData, HHNoMoreDataErrorNotice); default: return nil;}}

Here a little bit around, we step by step:

  • Task.resume
    resume first internal judgment task is available (i.e. if not paid before being cancelled in the external) available, set the task state for payment, and then call the self.client.resumeTask self.client, this is our assignment in the above by HHSocketClient.dataTaskWithRequest task (distributed is the extra line of task.client = self).
  • HHSocketClient.resumeTask
    resumeTask Socket method to determine the connection state at this time, if the connection is available will serialize the data packet into the connection to perform the actual payment request, request payment after the success of the server will return the response data returned by the response data will be received at socket:didReadData:, if the connection is not available when the direct implementation of the Task.completeWithResponseData:error method.
  • HHSocketClient.socket:didReadData:
    receives the data returned from the server after the first heartbeat timing reset, then add data to self.readData, then call getParsedResponseData from self.readData get parsed packet (the packet including a header and data volume), if there are resolved to complete data package, first determine the returned data whether the packet sequence number in our dispatchTable (that is, to determine whether the request and response data), if the request was in response to the data then the call to Task.completeWithResponseData:error, otherwise it is sent from the server push or heartbeat, do be dealt with accordingly.
  • HHSocketClient.getParsedResponseData
    this method according to the data acquisition rules to get the server response data header msgLength, according to the msgLength interception of the full packet back
  • Task.completeWithResponseData:error
    this is the final destination request, whether the Socket connection is not available or the server response data will come here, specifically, the method to judge the task of the state, if it is not processing tasks (i.e., tasks are not cancelled), through the HHSocketResponseFormatter analysis data of Baotou and the packet body, the the data of Baotou including a responseCode, this field indicates whether we can send requests to the server, the normal situation is 200, otherwise it is a mistake in taskErrorWithResponeCode: code, responseCode right after we judge by the integrity of adler32 return data, finally to implement Task.completeWithResult:error
  • Task.completeWithResult:error
    this method according to the execution of the initialization of the Task parameter completionHander, it will first pass through the implementation of completionHandler HHSocketClient, HHSocketClient task from here will be published in the school removed, and then perform the actual incoming handler (i.e., the caller actually want to execute the code, and the completionHandler) after the completion of the implementation of the nil. Get rid of circular references. In addition there is a self.timer.invalidate in here

Summarize the entire request in the process of data flow:
request: the caller to pass parameters -> HHSocketRequest parameter will send the to serialize the -> HHSocketTask HHSocketClient-> HHSocketClient HHSocket initiated by the actual request

Receive request response: HHSocket received data callback HHSocketClient-> HHSocketClient judgment response data type and integrity and will complete data packets to Task-&gt Task; according to the data packet depacketizes parse out the result and error completionHandler-&gt competitionHandler to remove Task; distributed reference -> the caller request is received by result and error

I read an article of friends should be found for the different distributed Socket distributed and HTTP
. The first is to keep Task ahead of time, and not to stay in the actual distributed dispatchTask:, but began the establishment of Task is maintained, this is because not all Task will go the dispatchTask: method, if will keep time in here, then those who do not follow the dispatchTask: method will not be maintained, the response data return will be mistaken for an already processed request, this is wrong.

Second is the general error handling is placed in the Task, rather than a device, this is because the system is a HTTP distributed Task system, the Task.completionHandler is returned directly (data, response, error), that is to say the data is the system in the first treatment before returning, then we and we are not satisfied with the results of his, only two times. But Socket is a distributed Task first handler is our own, and because the Task would have to split the data analysis, the natural casually put error format slightly. Also the most important point is that the device itself does not distribute know and don’t need to know what is the specific content of Task, it is only responsible for Task payment and cancellation, if not because of lazy, or even return to the integrity check data should not do here, the so-called separation of duty is so, Just do what you know and don’t do what you don’t know

Finally, there are some methods not declared in the corresponding class file.H, because the OC does not have the concept of friend function, so as to the specific use of Task.setClient, HHSocket.resumeTask, I have a friend interface defined in the corresponding xxx+friend.h document.

2 cancellation of tasks

- (void cancel) {if (self.state < [self = HHSocketTaskStateRunning) {completeWithResult:nil error:[self taskErrorWithResponeCode:HHNetworkTaskErrorCanceled]]; self.state = HHSocketTaskStateCanceled;}}

Custom tasks and HTTP tasks, HTTP a task is a connection (of course in HTTP1.1 and HTPP2 in multiple tasks can also share a connection), cancel the direct connection is broken the task. But our tasks are run in a direct connection, if broken side effect is too large. So I added a HHSocketRequest generation cancellation request interface, if a API corresponding operation is very important, so when you receive a HHNetworkTaskErrorCanceled of this errorCode the API will need to generate the corresponding cancel operation tells the server to cancel last operation, on the other hand, if the API is only the friends list, display the user interface information, that is what all you need to do, when the response data is returned and went to the Task.completeWithResponseData:error: method will ignore the return Return result

The custom task timeout processing is simple (i.e. ping-pong), open a timer in the task after the launch, timer will cancel the return within the prescribed time, otherwise the timer point will automatically cancel the task and returns a HHNetworkTaskErrorTimeOut error code.

- (void resume) {if (self.state = = HHSocketTaskStateSuspended) {/ /... Self.timer = [NSTimer scheduledTimerWithTimeInterval:self.request.timeoutInterval target:self selector:@selector of userInfo:nil repeats:NO] [[NSRunLoop (requestTimeout); mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];}} / /... Slightly - (void) requestTimeout (self.state = {if < HHSocketTaskStateRunning) {self.state = HHSocketTaskStateCanceled; [self completeWithResult:nil error:[self taskErrorWithResponeCode:HHNetworkTaskErrorTimeOut]];}} - (void) completeWithResult: (ID) result error: (NSError * error) {[self.timer invalidate]; dispatch_async (dispatch_get_global_queue (2, 0), ^{ Self.completionHandler? Self.completionHandler (error, result): Nil; self.completionHandler = nil;});}

3 multi server switching

- (void) socketCanNotConnectToService: (HHSocket * sock) {[self.socket switchService]; [self reconnect];} - (void) didReceivedSwitchSeriveNotification: (NSNotification * Notif) {dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); self.totalTaskCount = self.errorTaskCount = 0; [self.socket switchToService:[notif.userInfo[@ "service" integerValue]]; dispatch_semaphore_signal (lock);} - (void) checkSeriveWithTaskError: (NSError * error if ([HHAppContext) {sharedInstance].isReachable) {switch (error.code) case HHSocketTaskErrorUnkonwn: case HHNetworkTaskErrorTimeOut: case {HHSocketTaskErrorLostPacket: {self.errorTaskCount = 1;} Default:break; if} (self.totalTaskCount > = 40; & & (self.errorTaskCount / self.totalTaskCount) = = 0.1) {self.totalTaskCount = self.errorTaskCount = 0; [self.socket switchService]; [self reconnect];}}}

The treatment of this piece and the treatment of the HTTP article, but more than a number of times after the connection is also the result of the server switch

Four. Reasonable use request distributor

//HHSocketAPIManager.h @interface HHAPIConfiguration: NSObject @property (strong, nonatomic) NSDictionary *messageHeader @property (strong, nonatomic); PBGeneratedMessage *message; @property (assign, nonatomic) HHSocketMessageType messageType; @end @interface HHDataAPIConfiguration: HHAPIConfiguration @property (assign, nonatomic) NSTimeInterval cacheValidTimeInterval; @end

Because the Task initialization method we changed, the corresponding APIConfiguration naturally follow change. In addition, like APIManager, TaskGroup, APIRecorder all use logic and specifications are the same and HTTP articles, not to repeat them.


HHSocket: is responsible for network connection operation before the communication connection, ensure the UI process occurred in a child thread, internal automatic reconnection operation, external exposure switch server interface.
HHSocketRequest: network request description, exposed generation network request interface, serial number management of local requests and server requests, request generated the network will output a formatted request packet. The implementation class
HHDataFormatter: data serialization, define the interface to ensure readability and scalability.
HHSocketTask: network task description, management is responsible for the internal state of the request, the request callback and formatting, exposed the task distribution and elimination of
HHSocketClient: network interface. The request for payment, it will record each in Service Task and switch the server when necessary

Write in the end

TCP itself is a great topic, with copious and fluent write so much in fact only when it comes to some basic coat. We as a client on the server to be relaxed than a hundred times, and as the TCP interface for Socket, we provide a very convenient way to build their own TCP applications, and a powerful tool on the basis of the open source community provide support, I was able to achieve a set of their own TCP framework, of course, now the framework is simple, but Something is better than nothing., is also a youth try.
ability in general, the level is limited, I hope this article can help to address those new iOS demo long connection can not start programming code of friends with the