Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
#### v1.0.0 核心能力:**生产级网络通信架构** | **企业级 VIPER 架构**
#### v1.1.0 核心能力:**多维度指标监控、全链路追踪**

#### v1.2.0 核心能力:TLV协议扩展型增强
#### v1.2.0 核心能力:TLV协议扩展性增强

---

Expand Down Expand Up @@ -139,7 +139,7 @@ client.send(message: imageMessage) { error in
- **v1.0.1**:修复了因libffi编译导致无法在模拟器运行的问题
- **v1.1.0**:新增全链路追踪、关键指标采集(网络质量/成功率/延迟)并添加演示Demo,引入序列号分区机制,整体逻辑优化
- **v1.2.0**:协议改造为TLV结构,支持协议无缝升级,整体逻辑重构,消息构造和解析逻辑发生本质变化,详见Doc
- **v1.2.1**:完善了消息错误机制,遵循单一职责拆分了数据包解析、组装
- **v1.2.1**:完善了消息错误机制,遵循单一职责拆分了数据包解析、组装,抽象了连接管理类,优化了握手交换协议版本信息逻辑

### 版本规划

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// TJPConnectionManager+TJPMetrics.h
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/5/15.
// 连接埋点

#import "TJPConnectionManager.h"

NS_ASSUME_NONNULL_BEGIN

@interface TJPConnectionManager (TJPMetrics)

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
//
// TJPConcreteSession+TJPMetrics.m
// TJPConnectionManager+TJPMetrics.m
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/4/10.
// Created by 唐佳鹏 on 2025/5/15.
//

#import "TJPConcreteSession+TJPMetrics.h"
#import "TJPConnectionManager+TJPMetrics.h"
#import <GCDAsyncSocket.h>
#import <objc/runtime.h>

#import "TJPMetricsCollector.h"


@implementation TJPConcreteSession (TJPMetrics)
@implementation TJPConnectionManager (TJPMetrics)
+ (void)initialize {
[self enableMetricsMonitoring];
}
Expand Down Expand Up @@ -76,12 +76,4 @@ - (void)metrics_socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host
[[TJPMetricsCollector sharedInstance] incrementCounter:TJPMetricsKeyConnectionSuccess];
[self metrics_socket:sock didConnectToHost:host port:port];
}



@end





Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t

// 处理消息
switch (msgType) {
case TJPMessageTypeNormalData: // TJPMessageTypeNormalData
case TJPMessageTypeNormalData: // 普通数据消息
{
if (self.didReceiveDataHandler) {
self.didReceiveDataHandler(payload, seq);
Expand All @@ -119,13 +119,61 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
break;


case TJPMessageTypeHeartbeat: // TJPMessageTypeHeartbeat
case TJPMessageTypeHeartbeat: // 心跳消息
{
if (self.didReceiveDataHandler) {
self.didReceiveDataHandler(payload, seq);
}
[self sendHeartbeatACKForSequence:seq sessionId:sessionId toSocket:sock];
}
break;
case TJPMessageTypeControl: // 控制消息
{
if (self.didReceiveDataHandler) {
self.didReceiveDataHandler(payload, seq);
}

//解析TLV数据,获取版本信息
if (payload.length >= 12) {
//Tag(2) + Length(4) + Value(2) + Flags(2)
uint16_t tag;
uint32_t length;
uint16_t value;
uint16_t flags;

const void *bytes = payload.bytes;
memcpy(&tag, bytes, sizeof(uint16_t));
memcpy(&length, bytes + 2, sizeof(uint32_t));
memcpy(&value, bytes + 6, sizeof(uint16_t));
memcpy(&flags, bytes + 8, sizeof(uint16_t));

// 转换网络字节序到主机字节序
tag = ntohs(tag);
length = ntohl(length);
value = ntohs(value);
flags = ntohs(flags);

NSLog(@"[MOCK SERVER] 版本协商:Tag=%u, Length=%u, Value=0x%04X, Flags=0x%04X",
tag, length, value, flags);

if (tag == 0x0001) { // 版本标签
uint8_t clientMajorVersion = (value >> 8) & 0xFF;
uint8_t clientMinorVersion = value & 0xFF;

NSLog(@"[MOCK SERVER] 客户端版本: %u.%u", clientMajorVersion, clientMinorVersion);

NSLog(@"[MOCK SERVER] 客户端特性: %@", [self featureDescriptionWithFlags:flags]);


// 发送版本协商响应
[self sendVersionNegotiationResponseForSequence:seq sessionId:sessionId clientVersion:value
supportedFeatures:flags toSocket:sock];
}
}

//发送控制消息ACK
[self sendControlACKForSequence:seq sessionId:sessionId toSocket:sock];
}

break;

Expand All @@ -137,6 +185,18 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
[sock readDataWithTimeout:-1 tag:0];
}

- (NSString *)featureDescriptionWithFlags:(uint16_t)flags {
NSMutableString *desc = [NSMutableString string];

if (flags & 0x0001) [desc appendString:@"基本消息 "];
if (flags & 0x0002) [desc appendString:@"加密 "];
if (flags & 0x0004) [desc appendString:@"压缩 "];
if (flags & 0x0008) [desc appendString:@"已读回执 "];
if (flags & 0x0010) [desc appendString:@"群聊 "];

return desc.length > 0 ? desc : @"无特性";
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
[self.connectedSockets removeObject:sock];
}
Expand Down Expand Up @@ -171,6 +231,33 @@ - (void)sendACKForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId toSocket:
[socket writeData:ackData withTimeout:-1 tag:0];
}

- (void)sendControlACKForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId toSocket:(GCDAsyncSocket *)socket {
NSLog(@"[MOCK SERVER] 收到控制消息,序列号: %u", seq);

// 使用当前时间戳
uint32_t currentTime = (uint32_t)[[NSDate date] timeIntervalSince1970];

TJPFinalAdavancedHeader reply = {0};
reply.magic = htonl(kProtocolMagic);
reply.version_major = kProtocolVersionMajor;
reply.version_minor = kProtocolVersionMinor;
reply.msgType = htons(TJPMessageTypeACK); // 仍然使用ACK类型,但可以考虑使用TJPMessageTypeControl
reply.sequence = htonl(seq);
reply.timestamp = htonl(currentTime);
reply.encrypt_type = TJPEncryptTypeNone;
reply.compress_type = TJPCompressTypeNone;
reply.session_id = htons(sessionId);
reply.bodyLength = 0;

// 没有数据体,checksum设为0
reply.checksum = 0;

NSData *ackData = [NSData dataWithBytes:&reply length:sizeof(reply)];
NSLog(@"[MOCK SERVER] 控制消息响应包字段:magic=0x%X, msgType=%hu, sequence=%u, timestamp=%u, sessionId=%hu",
ntohl(reply.magic), ntohs(reply.msgType), ntohl(reply.sequence), ntohl(reply.timestamp), ntohs(reply.session_id));
[socket writeData:ackData withTimeout:-1 tag:0];
}


- (void)sendHeartbeatACKForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId toSocket:(GCDAsyncSocket *)socket {
NSLog(@"[MOCK SERVER] 收到心跳包,序列号: %u", seq);
Expand Down Expand Up @@ -200,6 +287,61 @@ - (void)sendHeartbeatACKForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId
[socket writeData:ackData withTimeout:-1 tag:0];
}

- (void)sendVersionNegotiationResponseForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId clientVersion:(uint16_t)clientVersion supportedFeatures:(uint16_t)features toSocket:(GCDAsyncSocket *)socket {
NSLog(@"[MOCK SERVER] 收到控制消息,序列号: %u", seq);

// 使用当前时间戳
uint32_t currentTime = (uint32_t)[[NSDate date] timeIntervalSince1970];

// 服务器选择的版本和功能
uint8_t serverMajorVersion = kProtocolVersionMajor;
uint8_t serverMinorVersion = kProtocolVersionMinor;
uint16_t serverVersion = (serverMajorVersion << 8) | serverMinorVersion;
uint16_t agreedFeatures = features & 0x0003; // 仅支持客户端请求的部分功能

// 构建TLV数据
NSMutableData *tlvData = [NSMutableData data];

// 版本协商响应TLV
uint16_t versionResponseTag = htons(0x0002); // 响应标签
uint32_t versionResponseLength = htonl(4);
uint16_t versionResponseValue = htons(serverVersion);
uint16_t agreedFeaturesValue = htons(agreedFeatures);

[tlvData appendBytes:&versionResponseTag length:sizeof(uint16_t)];
[tlvData appendBytes:&versionResponseLength length:sizeof(uint32_t)];
[tlvData appendBytes:&versionResponseValue length:sizeof(uint16_t)];
[tlvData appendBytes:&agreedFeaturesValue length:sizeof(uint16_t)];

// 计算校验和
uint32_t checksum = [TJPNetworkUtil crc32ForData:tlvData];

// 构建响应头
TJPFinalAdavancedHeader responseHeader = {0};
responseHeader.magic = htonl(kProtocolMagic);
responseHeader.version_major = serverMajorVersion;
responseHeader.version_minor = serverMinorVersion;
responseHeader.msgType = htons(TJPMessageTypeControl);
responseHeader.sequence = htonl(seq + 1); // 响应序列号+1
responseHeader.timestamp = htonl(currentTime);
responseHeader.encrypt_type = TJPEncryptTypeNone;
responseHeader.compress_type = TJPCompressTypeNone;
responseHeader.session_id = htons(sessionId);
responseHeader.bodyLength = htonl((uint32_t)tlvData.length);
responseHeader.checksum = htonl(checksum);

// 构建完整响应
NSMutableData *responseData = [NSMutableData dataWithBytes:&responseHeader
length:sizeof(responseHeader)];
[responseData appendData:tlvData];

NSLog(@"[MOCK SERVER] 发送版本协商响应:服务器版本 %u.%u,协商功能 0x%04X",
serverMajorVersion, serverMinorVersion, agreedFeatures);

[socket writeData:responseData withTimeout:-1 tag:0];

}


//旧方法 已废弃目前单元测试在用 后续移除
- (void)sendHeartbeatACKForSequence:(uint32_t)seq toSocket:(nonnull GCDAsyncSocket *)socket {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
/// 组装数据包
+ (NSData *)buildPacketWithMessageType:(TJPMessageType)msgType sequence:(uint32_t)sequence payload:(NSData *)payload encryptType:(TJPEncryptType)encryptType compressType:(TJPCompressType)compressType sessionID:(NSString *)sessionID;

+ (uint16_t)sessionIDFromUUID:(NSString *)uuidString;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ + (NSData *)buildPacketWithMessageType:(TJPMessageType)msgType sequence:(uint32_
header.bodyLength = htonl((uint32_t)payload.length);

// 计算数据体的CRC32
uint32_t checksum = [TJPNetworkUtil crc32ForData:payload];
header.checksum = htonl(checksum); // 注意要转换为网络字节序
uint32_t checksum = 0;
if (payload.length > 0) {
checksum = [TJPNetworkUtil crc32ForData:payload];
}
header.checksum = htonl(checksum); // 注意要转换为网络字节序

// 构建完整协议包
NSMutableData *packet = [NSMutableData dataWithBytes:&header length:sizeof(header)];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// TJPConnectionManager.h
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/5/15.
//

#import <Foundation/Foundation.h>
#import "TJPConnectionDelegate.h"
#import "TJPCoreTypes.h"


NS_ASSUME_NONNULL_BEGIN

@interface TJPConnectionManager : NSObject

@property (nonatomic, weak) id<TJPConnectionDelegate> delegate;
/// 内部状态
@property (nonatomic, readonly) TJPConnectionState internalState;
/// 当前主机
@property (nonatomic, readonly) NSString *currentHost;
/// 当前端口
@property (nonatomic, readonly) uint16_t currentPort;
/// 断开原因
@property (nonatomic, readonly) TJPDisconnectReason disconnectReason;
/// 使用TLS 默认为NO方便单元测试
@property (nonatomic, assign) BOOL useTLS;
/// 连接时限窗口 默认30秒
@property (nonatomic, assign) NSTimeInterval connectionTimeout;

/// 标志位
@property (nonatomic, readonly) BOOL isConnected;
@property (nonatomic, readonly) BOOL isConnecting;

/// 初始化方法
- (instancetype)initWithDelegateQueue:(dispatch_queue_t)delegateQueue;
/// 连接方法
- (void)connectToHost:(NSString *)host port:(uint16_t)port;
/// 断开连接方法
- (void)disconnect;
/// 断开连接原因
- (void)disconnectWithReason:(TJPDisconnectReason)reason;
/// 发送消息
- (void)sendData:(NSData *)data;
/// 带超时的发送消息
- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/// 开始TLS
- (void)startTLS:(NSDictionary *)settings;

/// 首次握手版本协商
- (void)setVersionInfo:(uint8_t)majorVersion minorVersion:(uint8_t)minorVersion;


@end

NS_ASSUME_NONNULL_END
Loading