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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
## 项目进度与规划
### ✅ **已发布版本**
#### v1.0.0 核心能力:**生产级网络通信架构** | **企业级 VIPER 架构**
#### v1.1.0 核心能力:**多维度指标监控、全链路追踪**
#### v1.1.0 核心能力:**多维度指标监控、全链路追踪**

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

---

Expand Down Expand Up @@ -130,7 +132,7 @@ client.send(message: imageMessage) { error in


#### 已知问题
- AOP切面日志:多参数方法监听崩溃,问题已定位,后期修复。
- AOP切面日志(非重点):多参数方法监听崩溃,问题已定位,后期修复。

### 版本记录
- **v1.0.0**:网络框架基础核心功能基本完成、生产级VIPER架构演示完成
Expand Down Expand Up @@ -202,7 +204,7 @@ Socket通信模块架构
+-------------------------+
```
### TLV数据区格式
- **Tag**:业务标识(如 0x1001=用户ID),大端字节序。
- **Tag**:业务标识(如 0x1001=文字消息),大端字节序。
- **Length**:Value 部分长度(不含 Tag 和 Length),大端字节序。
- **Value**:原始数据或嵌套 TLV(用保留 Tag 0xFFFF 标记)。
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ - (void)setupNetwork {

self.client = [TJPIMClient shared];
[self.client connectToHost:host port:port];


}

- (void)setupLogTextView {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// UIImage+TJPImageOrientation.h
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/5/14.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIImage (TJPImageOrientation)

/// 修正图片方向(解决拍照图片旋转问题)
- (UIImage *)fixOrientation;


@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// UIImage+TJPImageOrientation.m
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/5/14.
//

#import "UIImage+TJPImageOrientation.h"

@implementation UIImage (TJPImageOrientation)

- (UIImage *)fixOrientation {
// 方向正常的图片直接返回
if (self.imageOrientation == UIImageOrientationUp) {
return self;
}

// 计算变换矩阵
CGAffineTransform transform = CGAffineTransformIdentity;

switch (self.imageOrientation) {
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;

case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;

case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, self.size.height);
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;

default:
break;
}

// 处理镜像情况
switch (self.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;

case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, self.size.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;

default:
break;
}

// 应用变换
CGContextRef ctx = CGBitmapContextCreate(
NULL,
self.size.width,
self.size.height,
CGImageGetBitsPerComponent(self.CGImage),
0,
CGImageGetColorSpace(self.CGImage),
CGImageGetBitmapInfo(self.CGImage)
);

CGContextConcatCTM(ctx, transform);

switch (self.imageOrientation) {
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
CGContextDrawImage(ctx, CGRectMake(0, 0, self.size.height, self.size.width), self.CGImage);
break;

default:
CGContextDrawImage(ctx, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
break;
}

CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
UIImage *result = [UIImage imageWithCGImage:cgimg];

CGContextRelease(ctx);
CGImageRelease(cgimg);

return result ?: self;
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// TJPErrorDefine.h
// iOS-Network-Stack-Dive
//
// Created by 唐佳鹏 on 2025/5/10.
//


#ifndef TJPErrorUtil_h
#define TJPErrorUtil_h

#import <Foundation/Foundation.h>

/**
* 网络错误代码枚举
* 错误域: "com.tjp.network.error"
*/
typedef NS_ENUM(NSInteger, TJPNetworkError) {
// 一般错误 (0-999)
TJPErrorNone = 0, // 无错误
TJPErrorUnknown = 1, // 未知错误
TJPErrorTimeout = 2, // 超时
TJPErrorCancelled = 3, // 操作被取消

// 连接相关错误 (1000-1999)
TJPErrorConnectionFailed = 1000, // 连接失败
TJPErrorConnectionTimeout = 1001, // 连接超时
TJPErrorConnectionLost = 1002, // 连接丢失
TJPErrorConnectionRefused = 1003, // 连接被拒绝
TJPErrorNetworkUnavailable = 1004, // 网络不可用
TJPErrorServerUnavailable = 1005, // 服务器不可用
TJPErrorTLSHandshakeFailed = 1006, // TLS握手失败

// 消息传输错误 (2000-2999)
TJPErrorMessageSendFailed = 2000, // 消息发送失败
TJPErrorMessageReceiveFailed = 2001, // 消息接收失败
TJPErrorMessageTimeout = 2002, // 消息超时未收到响应
TJPErrorMessageTooLarge = 2003, // 消息体过大
TJPErrorMessageFormatInvalid = 2004, // 消息格式无效
TJPErrorMessageACKMissing = 2005, // 未收到ACK确认
TJPErrorMessageRetryExceeded = 2006, // 超过最大重试次数

// 协议解析错误 (3000-3999)
TJPErrorProtocolVersionMismatch = 3000, // 协议版本不匹配
TJPErrorProtocolMagicInvalid = 3001, // 魔数无效
TJPErrorProtocolChecksumMismatch = 3002, // 校验和不匹配
TJPErrorProtocolHeaderInvalid = 3003, // 协议头无效
TJPErrorProtocolPayloadLengthMismatch = 3004, // 负载长度不匹配
TJPErrorProtocolUnsupportedEncryption = 3005, // 不支持的加密类型
TJPErrorProtocolUnsupportedCompression= 3006, // 不支持的压缩类型
TJPErrorProtocolTimestampInvalid = 3007, // 时间戳无效

// TLV解析错误 (4000-4999)
TJPErrorTLVParseError = 4000, // TLV解析错误
TJPErrorTLVIncompleteTag = 4001, // 不完整的Tag
TJPErrorTLVIncompleteLength = 4002, // 不完整的Length
TJPErrorTLVIncompleteValue = 4003, // 不完整的Value
TJPErrorTLVDuplicateTag = 4004, // 重复的Tag
TJPErrorTLVNestedTooDeep = 4005, // 嵌套深度过大

// 安全相关错误 (5000-5999)
TJPErrorSecurityEncryptionFailed = 5000, // 加密失败
TJPErrorSecurityDecryptionFailed = 5001, // 解密失败
TJPErrorSecurityUnauthorized = 5002, // 未授权
TJPErrorSecurityReplayAttackDetected = 5003, // 检测到重放攻击
TJPErrorSecurityInvalidSignature = 5004, // 签名无效

// 会话相关错误 (6000-6999)
TJPErrorSessionExpired = 6000, // 会话过期
TJPErrorSessionInvalid = 6001, // 会话无效
TJPErrorSessionLimitExceeded = 6002, // 超出会话限制
TJPErrorSessionHeartbeatTimeout = 6003, // 心跳超时
TJPErrorSessionStateError = 6004, // 会话状态错误

// 业务逻辑错误 (7000-7999)
TJPErrorBusinessLogicFailed = 7000, // 业务逻辑失败

// 系统错误 (8000-8999)
TJPErrorSystemMemoryLow = 8000, // 系统内存不足
TJPErrorSystemDiskFull = 8001, // 磁盘空间不足
TJPErrorSystemIOFailure = 8002 // IO操作失败
};

// 错误域常量
FOUNDATION_EXPORT NSString * const TJPNetworkErrorDomain;



#endif /* TJPErrorUtil_h */
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
#define kHeartbeatTimeoutNotification @"kHeartbeatTimeoutNotification"


#define TJPMAX_BODY_SIZE (10 * 1024 * 1024) // 10MB 最大消息体大小
#define TJPMAX_BUFFER_SIZE (20 * 1024 * 1024) // 20MB 最大缓冲区大小
#define TJPMAX_TIME_WINDOW 60 // 60秒时间窗口,防重放攻击



#define TJPSCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
uint32_t seq = ntohl(header.sequence);
uint32_t bodyLength = ntohl(header.bodyLength);
uint16_t msgType = ntohs(header.msgType);
TJPEncryptType encryptType = header.encrypt_type;
TJPCompressType compressType = header.compress_type;
uint16_t sessionId = ntohs(header.session_id);
uint32_t timestamp = ntohl(header.timestamp);

NSLog(@"[MOCK SERVER] 接收到的消息: 类型=%hu, 序列号=%u, 时间戳=%u, 会话ID=%hu, 加密类型=%d, 压缩类型=%d",
msgType, seq, timestamp, sessionId, encryptType, compressType);


// 读取完整消息体
NSData *payload = [data subdataWithRange:NSMakeRange(kHeaderLength, bodyLength)];
Expand All @@ -106,7 +114,7 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
if (self.didReceiveDataHandler) {
self.didReceiveDataHandler(payload, seq);
}
[self sendACKForSequence:seq toSocket:sock];
[self sendACKForSequence:seq sessionId:sessionId toSocket:sock];
}
break;

Expand All @@ -116,7 +124,7 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
if (self.didReceiveDataHandler) {
self.didReceiveDataHandler(payload, seq);
}
[self sendHeartbeatACKForSequence:seq toSocket:sock];
[self sendHeartbeatACKForSequence:seq sessionId:sessionId toSocket:sock];
}

break;
Expand All @@ -134,46 +142,70 @@ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
}

#pragma mark - Response Methods
- (void)sendACKForSequence:(uint32_t)seq toSocket:(GCDAsyncSocket *)socket {
- (void)sendACKForSequence:(uint32_t)seq sessionId:(uint16_t)sessionId toSocket:(GCDAsyncSocket *)socket {
NSLog(@"[MOCK SERVER] 收到普通消息,序列号: %u", seq);

// 使用与客户端相同的时间戳生成ACK响应
uint32_t currentTime = (uint32_t)[[NSDate date] timeIntervalSince1970];


TJPFinalAdavancedHeader header = {0};
header.magic = htonl(kProtocolMagic);
header.version_major = 1;
header.version_minor = 0;
header.version_major = kProtocolVersionMajor;
header.version_minor = kProtocolVersionMinor;
header.msgType = htons(TJPMessageTypeACK);
header.sequence = htonl(seq);
header.bodyLength = 0; // ACK没有数据体
header.timestamp = htonl(currentTime); // 使用当前时间戳
header.encrypt_type = TJPEncryptTypeNone;
header.compress_type = TJPCompressTypeNone;
header.session_id = htons(sessionId); // 保持与请求相同的会话ID

header.bodyLength = 0; // ACK没有数据体
// ACK包没有数据体,checksum设为0
header.checksum = 0;

NSData *ackData = [NSData dataWithBytes:&header length:sizeof(header)];
NSLog(@"[MOCK SERVER] 普通消息响应包字段:magic=0x%X, msgType=%hu, sequence=%u, checksum=%u",
ntohl(header.magic), ntohs(header.msgType), ntohl(header.sequence), ntohl(header.checksum));
NSLog(@"[MOCK SERVER] 普通消息响应包字段:magic=0x%X, msgType=%hu, sequence=%u, timestamp=%u, sessionId=%hu",
ntohl(header.magic), ntohs(header.msgType), ntohl(header.sequence), ntohl(header.timestamp), ntohs(header.session_id));

[socket writeData:ackData withTimeout:-1 tag:0];
}


- (void)sendHeartbeatACKForSequence:(uint32_t)seq toSocket:(GCDAsyncSocket *)socket {
- (void)sendHeartbeatACKForSequence:(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 = 1;
reply.version_minor = 0;
reply.version_major = kProtocolVersionMajor;
reply.version_minor = kProtocolVersionMinor;
reply.msgType = htons(TJPMessageTypeACK);
reply.sequence = htonl(seq);
reply.timestamp = htonl(currentTime); // 使用当前时间戳
reply.encrypt_type = TJPEncryptTypeNone;
reply.compress_type = TJPCompressTypeNone;
reply.session_id = htons(sessionId); // 保持与请求相同的会话ID
reply.bodyLength = 0;

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

NSData *ackData = [NSData dataWithBytes:&reply length:sizeof(reply)];
NSLog(@"[MOCK SERVER] 心跳响应包字段:magic=0x%X, msgType=%hu, sequence=%u, checksum=%u",
ntohl(reply.magic), ntohs(reply.msgType), ntohl(reply.sequence), 0);
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 toSocket:(nonnull GCDAsyncSocket *)socket {
}

- (void)sendACKForSequence:(uint32_t)seq toSocket:(nonnull GCDAsyncSocket *)socket {
}

@end

This file was deleted.

Loading