Recently, I was pressed by Boge, and the business time was forced to advance, so I searched through the code warehouse that I had not seen for decades, and found a practice method in the development of long links on the mobile terminal many years ago, which can make the business code of long links as efficient and concise as that of short links, and used it to deal with the difference.

This article will not elaborate on TCP, nor will it explain how to use the Socket library, protocol format definition, how to transfer, etc., are not within the scope of this article, this article is concerned with how to efficiently and concisely write tcp-based business code on the mobile end. Make long-linked business code as efficient to write as short-linked business code.

Let’s start with the basics:

Remember the key points:

  • R:Q = N:N = 1; R:Q = N:N = 1; R:Q = N:N = 1
  • Long link: send and receive only write data to cache or read data from cache. There is no clear concept of request & respones. Their relationship is: R:Q = M:N

Because of this difference, TCP brings many advantages such as link reuse, active push, high efficiency and speed, but also introduces more complexity. One of the problems is that it is cumbersome to write business code based on TCP.

This article will take iOS development as an example, to explain the problem, students in other languages can do the same.

Short link example

It is very common to make a short link on the App. Network requests are made in the form of blocks. For example, AFNetworking library is widely used in this way:

NSString *url = @"http://api.xxx.com/method"; [[self shareAFNManager] GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"responseObject-->%@",responseObject);  UpdateUI() } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"error-->%@",error); }];Copy the code

Block is a function pointer in Objective-C, which can be used to directly handle business in the corresponding block. For example: This operation brings a very convenient effect to write business, essentially because the form of the short link satisfies the relation of R:Q = N:N = 1

Long links

So in the long link to achieve the above function is very troublesome, in addition to the basic Socket function, response to data update UI has to deal with the network thread switch UI thread, UI life cycle and data life cycle matching, data notification and many other problems.

In the implementation of handle or Dispatch methods, many students will fall into a trap, or the implementation is particularly troublesome, or the implementation is full of loopholes. In infrequent cases, they may choose Notification or similar crude schemes to take the lead, even though there will be many holes in writing: Notice registration, management, destruction and so on are the contents to be dealt with carefully, which is also the problem of many students who cannot write long link business code.

plan

Since there is a problem, in line with “whatever is painful is where the demand is”, according to the idea of a short link to carry out a simple combing:

Extract request & respones, the convention relationship is: R:Q = N:N = 1

The core problem is this one, as long as can abstract out this relation, so long link can be as convenient as short link, including many students think TCP cache module is difficult to achieve the problem will become very simple.

In TCP protocol customization, the convention message{} & Message_reply {} corresponds to a network request, such as defining a ping packet in a protobuf

message ping{
    string request_id = 1;
}

message ping_reply{
    string request_id = 1;
}

Copy the code

The R:Q = N:N = 1 problem is realized according to this convention in protocol design

request_id

Request_id is a more “dirty” operation that leads to a field that perfectly solves a big problem.

In the process, keep in mind the characteristics of short links, come and go, come and go (no bar, there will be no network timeout or network link error return).

A request_ID is created and maintained by the client and sent to the server in a request. The server returns the value intact to the client, which then finds the original location of the network request based on the request_ID.

For example, a name called SendCore is used to implement the network layer. The main structure is as follows:


typedef void (^SocketDidReadBlock)(NSError *__nullable error, id __nullable data);

@interface SendCore : NSObject
+ (nullable SendCore *)sharedManager;

- (void) sendEnterChatRoom:(nullable EnterChatRoom *) data completion:(__nullable SocketDidReadBlock)callback;

- (void) CloseSocket;
@end

Copy the code

The form of SocketDidReadBlock convention function pointer must meet the requirements of simplicity and extensibility.

Take the function to enter a chat room:

- (void) sendEnterChatRoom:(nullable EnterChatRoom *) data completion:(__nullable SocketDidReadBlock)callback { if (data  == nil ) { return; } NSString * blockRequestID =[self createRequestID]; data.requestId = blockRequestID; if (callback) { [self addRequestIDToMap:blockRequestID block:callback]; } [self sendProtocolWithCmd:CmdType_Enter1V1MovieRoom withCmdData:[data data] completion:callback]; }Copy the code

The client generates a request_ID, stores the callback to the block_table, extracts the request_ID from the data returned by the server, and executes the function pointer in the block_table.


-(void) handerProtocol:(CmdType) protocolID packet:(NSData *) packet
{
    NSError *error = nil;
    id reslutData = nil;
    NSString *requestId = nil;
    switch (protocolID) {
    	case CmdType_Enter1V1MovieRoomReply:
            error = nil;
            syncObjInfo =[SyncObjInfo parseFromData:packet error:&error];
            reslutData = syncObjInfo;
            
            requestId = syncObjInfo.requestId;
            didReadBlock = [self getBlockWith:requestId];
            
        break;
    }

    if (didReadBlock) {
        didReadBlock(error, reslutData);
    }

    if (requestId != nil &&
        requestId.length > 0) {
        [self removeRequestIDFormMap:requestId];
    }    
}

Copy the code

After execution, remember to clean up related content and concurrent locking information.

At this point, the main functions are complete, with the following effects:

[[SendCore sharedManager] sendEnterChatRoom:room completion:^(NSError * _Nullable error, Id _Nullable data) {NSLog (@ "% @", the error); the if (error = = nil) {NSLog (@ "enter the chat room success");}}];Copy the code

In this way, the long-linked business code is as simple and convenient as the short-linked business code.

timeout

There are also some unexpected cases to deal with, the above example only works under normal network conditions, such as timeout, TCP does not return messages, so there is more to do in SendCore.

So let’s go to the addRequestIDToMap function above:

- (void) addRequestIDToMap:(NSString *) requestID block:(nullable SocketDidReadBlock)callback withTime:(BOOL) timeout{

    if (requestID == nil ||
        requestID.length == 0) {
        return;
    }
    
    if (callback == nil) {
        return;
    }
    
    [self.requestsMapLock lock];
    [self.requestsMap setObject:callback forKey:requestID];
    
    if (timeout) {
        [self.requestsTimeMap setObject:[NSDate date] forKey:requestID];
    }
    
    [self.requestsMapLock unlock];
}
Copy the code

There is a requestsTimeMap, which records the time when the Request_id request was initiated, so that timeout errors can be handled locally

-(void) checkRequestProcessTimeout { NSError * Socket_WAIT_PROCESS_TIMEOUT_SECOND = [NSError errorWithDomain:@"_Socket_WAIT_PROCESS_TIMEOUT_SECOND_" code:408 userInfo:nil]; NSMutableArray * timeoutRequestIDs = [NSMutableArray array]; NSDate * now = [NSDate date]; for (NSString * requestID in [self.requestsTimeMap allKeys]) { if (requestID == nil || [requestID length] <= 0) { continue; } NSDate * fireDate = [self.requestsTimeMap objectForKey:requestID]; NSDate * timeOutTime = [NSDate dateWithTimeInterval:_Socket_WAIT_PROCESS_TIMEOUT_SECOND_ sinceDate:fireDate]; if ([timeOutTime compare:now] == NSOrderedAscending) { [timeoutRequestIDs addObject:requestID]; } } for (NSString * requestID in timeoutRequestIDs) { if (requestID == nil || [requestID length] <= 0) { continue; } SocketDidReadBlock didReadBlock = [self getBlockWith:requestID]; didReadBlock(Socket_WAIT_PROCESS_TIMEOUT_SECOND, nil); [self removeRequestIDFormMap:requestID]; }}Copy the code

By timer, a second execution checkRequestProcessTimeout function, found that the timeout request directly to adjust its block, and return Socket_WAIT_PROCESS_TIMEOUT_SECOND errors.

Through the form of double table, this problem is well solved.

The cache

In the same way, caching is relatively easy to implement. AFNetworking library can be used to implement a set of useful caching functions.

The magic of request_id

Request_id is generated and maintained by the client, usually as follows:

- (NSString *)createRequestID {

    NSInteger timeInterval = [NSDate date].timeIntervalSince1970 * 1000000;
    NSString *randomRequestID = [NSString stringWithFormat:@"%ld%d", timeInterval, arc4random() % 100000];

    return randomRequestID;
}
Copy the code

There are some fixed network requests in a certain scenario, or to match the cache library, you can define some fixed or specific format ids.

constUSER_INFO_GET_REQUEST_ID = @"04e1c446-b918-40f7- 9061.-d06b569a9cf0 "@"/big/small/XXX"Copy the code

The clever definition of request_id can lead to unexpected results and students are free to play with it.

The end of the

At this point, all the functions are over. From the business code, you can’t tell whether the code is a long link or a short link, which well achieves the expected purpose. The business code efficiency of the long link is as simple and efficient as that of the short link.

More exciting, please pay attention to our public number “100 bottle technology”, there are not regular benefits!