前言
TCP通道的建立
自定义应用层协议
请求体
响应体
请求和响应的序列化
序列化器
请求的序列化
响应的序列化
任务机制
KTTCPSocketTask
任务超时
管理器
KTTCPSocketManager
请求的发送
响应的接收
将响应派发给对应任务
Demo
参考资料
本文的起因是希望像《美团点评移动网络优化实践》中的方案一样、建设一个可以将HTTP请求转化成二进制数据包、并且在自建的TCP长连接通道上传输。当然、直接TCP双向通讯也是没有问题的。
以前用的Websocket、简单粗暴。如果你只想要一个全双工的TCP长连接、Websocket作为和HTTP一样的应用层协议完全够用。
但本文主要是尝试自己用socket(虽然并不是完全原生)构建一个能够像HTTP请求一样使用的TCP通道。并且最终、将HTTP请求放在自建的TCP加密通道上传输。
关于网络层一些基础知识、或许《当被尬聊网络协议、我们可以侃点什么?》可以帮到你。
自己对Socket通道的建设一开始也不太懂、所以有很多地方借鉴了《一步一步构建你的iOS网络层 - TCP篇》的思路。十分感谢
首先、我们需要一个类似websocket的应用层协议。
参照SRWebSocket来看、除了全双工通信之外。我们还需要处理心跳、重连、粘包这三个特殊的概念(SSL在CocoaAsyncSocket下已经封装了实现)。
此外。由于原生socket比较麻烦、所以借助了一个开源框架CocoaAsyncSocket来操作scoket(类似NSLayoutConstraint与Masonry的关系)。具体使用的是基于GCD的GCDAsyncSocket(似乎以前还有个基于Runloop的AsyncSocket、但是我用的时候已经没有了。大概和NSURLCollection被NSURLSession淘汰了一样)。
CocoaAsyncSocket初始状态下就具备连接、断开、发送以及读取等基本功能。
这里主要对CocoaAsyncSocket添加了重连、专属线程等易用性的封装、并且将scoket事件通过代理进行回调。
@class KTTCPSocket;
@protocol KTTCPSocketDelegate <NSObject>
@optional
/**
链接成功
@param sock KTTCPSocket
@param host 主机
@param port 端口
*/
- (void)socket:(KTTCPSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
/**
最终链接失败
连接失败 + N次重连失败
@param sock KTTCPSocket
*/
- (void)socketCanNotConnectToService:(KTTCPSocket *)sock;
/**
链接失败并重连
@param sock KTTCPSocket
@param error error
*/
- (void)socketDidDisconnect:(KTTCPSocket *)sock error:(NSError *)error;
/**
接收到了数据
@param sock KTTCPSocket
@param data 二进制数据
*/
- (void)socket:(KTTCPSocket *)sock didReadData:(NSData *)data;
@end
/**
对GCDAsyncSocket进行封装的工具类。
具备自动重连、读写数据等基础操作
*/
@interface KTTCPSocket : NSObject
@property (nonatomic,readonly) NSString *host;//主机
@property (nonatomic,readonly) uint16_t port;//端口
@property (nonatomic) NSUInteger maxRetryCount;//重连次数
@property (nonatomic, weak) id delegate;
- (instancetype)init NS_UNAVAILABLE;
/**
构造方法
@param host 主机号
@param port 端口号
@return KTTCPSocket实例
*/
- (instancetype)initSocketWithHost:(NSString *)host port:(uint16_t)port NS_DESIGNATED_INITIALIZER;
/**
关闭连接--注意关闭之后就没办法再次开启了。不然没办法判断socke对象该何时销毁
*/
- (void)close;
/**
连接
*/
- (void)connect;
/**
重连并且重置次数
*/
- (void)reconnect;
/**
链接状态
@return 是否已经链接
*/
- (BOOL)isConnected;
/**
写入数据
@param data 二进制数据
*/
- (void)writeData:(NSData *)data;
@end
- (void)writeData:(NSData *)data {
if (data.length == 0) { return; }
[self.socket writeData:data withTimeout:-1 tag:socketTag];
}
由于TCP面向字节流、所以并不需要我们调用发送之类的方法、他会按照顺序一个字节一个字节的把数据进行传输。
- (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];
}
readDataWithTimeout方法会持续监听一次缓存区、当接收到数据立刻通过代理交付。这里也就相当于递归调用了。
//链接失败
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error {
// NSLog(@"TCPSocket--连接已断开.error:%@", error);
if ([self.delegate respondsToSelector:@selector(socketDidDisconnect:error:)]) {
[self.delegate socketDidDisconnect:self error:error];
}
[self tryToReconnect];
}
//尝试自动重连
- (void)tryToReconnect {
if (self.isConnecting || !self.isNetworkReachable) {
return;
}
self.currentRetryCount -= 1;
//如果还有尝试次数就自动重连
if (self.currentRetryCount >= 0) {
NSLog(@"尝试重连");
[self connect];
} else if ([self.delegate respondsToSelector:@selector(socketCanNotConnectToService:)]) {
//自动重连失败
NSLog(@"重连失败");
[self.delegate socketCanNotConnectToService:self];
}
}
连接失败会监听重连次数、超过次数则宣告失败
//网络波动
- (void)didReceivedNetworkChangedNotification:(NSNotification *)notif {
[self reconnectIfNeed];
}
//切换到后台
- (void)didReceivedAppBecomeActiveNotification:(NSNotification *)notif {
[self reconnectIfNeed];
}
- (void)reconnectIfNeed {
if (self.isConnecting || self.isConnected) { return; }
[self reconnect];
}
网络波动会重置连接次数并重连
- (void)socketWillBeConnect {
if (self.socketThread == nil) {
//保存异步线程
self.socketThread = [NSThread currentThread];
[[NSRunLoop currentRunLoop] addPort:self.machPort forMode:NSDefaultRunLoopMode];
while (self.machPort) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
}
由于为长连接新开辟了一个线程、所以需要使用Runloop来维持线程的生存。
这里需要解释一下TCP的两个概念
TCP协议将数据看做有序排列的二进制位、并按照8位分割成有序的字节流。
本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/ios-conglingkaishizijiantcptongdao-2.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:8 月 27, 2018 at 07:54 下午