socket 研究
ChenghuiBai Lv3
什么是socket?

网络上两个程序通过一个双向通信连接实现数据交互,这种双向通信的连接叫做Socket.

本质上,Socket 是一组对TCP/UDP协议封装的api接口,处于应用层与传输层之间.

连接过程

建立Socket连接至少需要一对套接字,分别运行于服务端和客户端。套接字直接的连接需要三个步骤:

1、服务器监听

服务端Socket始终处于等待连接状态,实时监听是否有客户端请求连接。

2、客户端请求

客户端Socket提出连接请求,指定服务端Socket的ip地址和端口号,这时就可以向对应的服务端提出Socket连接请求。

3、连接确认

当服务端Socket监听到客户端Socket提出的连接请求时作出响应,建立一个新的进程,把服务端Socket的描述发送给客户端,该描述得到客户端确认后就可建立起Socket连接。而服务端Socket则继续处于监听状态,继续接收其他客户端Socket的请求。

CocoaAsyncSocket

iOS 开发中socket有一个第三方库CocoaAsyncSocket很好用.

使用的时候传递host和端口即可进行连接,

- (void)connectToServerWithCommand:(NSString *)command
{
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
[_socket setUserData:command];

NSError *error = nil;
[_socket connectToHost:host onPort:端口号 error:&error];
if (error) {
NSLog(@"连接 error:%@",error.userInfo);
}

[_socket writeData:[command dataUsingEncoding:NSUTF8StringEncoding] withTimeout:10.0f tag:6];
}

然后实现代理

#pragma mark - Socket Delegate

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"Socket连接成功:%s",__func__);
}

-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
if (err) {
NSLog(@"连接失败");
}else{
NSLog(@"正常断开");
}
}

-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"数据发送成功:%s",__func__);
//发送完数据手动读取,-1不设置超时
[sock readDataWithTimeout:-1 tag:tag];
}

-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"读取数据:%s %@",__func__,receiverStr);
}
TCP粘包、半包
什么是粘包?

由于TCP默认会使用优化方法(Nagle算法)。
将多次间隔较小且数据量小的数据,合并成一个大的数据块,进行封包,然后依次发送。
这么做优点就是为了减少广域网的小分组数目,可以减小网络拥塞的出现,同时也可以节省宽带。

什么是半包?

当发送一条很大的数据包,类似音视频,大图等,一次发送或者读取数据的缓冲区大小是有限的,所以会分段去发送或者读取数据。当多个包发送的时候,有些包可能就发送失败了,接收方接收数据不全就出现了半包的问题。

怎么处理?

如果我们要正确解析数据,那么必须要使用一种合理的机制去解包。这个机制的思路其实很简单:

在设计数据结构时记录包长和包的起始位置,一般就是每个包都设计一个包头和包体,包头包含包长等信息,包体就是具体传递的数据。

基于 CocoaAsyncSocket 处理

首先需要了解几个方法:

//读取数据,有数据就会触发代理
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
//直到读到这个长度的数据,才会触发代理
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
//直到读到data这个边界,才会触发代理
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;

包读取:


// socket连接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
// 非相关代码移除
...

//连接成功就去读取包头的数据,并设置tag,方便解析时使用
[_socket readDataToLength:LENGTH_HEAD withTimeout:-1 tag:TAG_HEAD];
}


// socket读取成功
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (tag == TAG_HEAD) {//包头的数据
//保存包头数据,等包体数据接收到时合并
self.dataHead = data;

//从包中读取包长度信息
UInt16 lengthTotal;
[data getBytes:&lengthTotal range:RANGE_PACKET_LENGTH];
lengthTotal = CFSwapInt16BigToHost(lengthTotal);

//包体长度=总长度-包头长度
UInt16 lengthBody = lengthTotal - LENGTH_HEAD;

//读取指定长度的包体数据
[sock readDataToLength:lengthBody withTimeout:-1 tag:TAG_BODY];
} else if (tag == TAG_READ_BODY) {//包体的数据

//整个包数据=包头+包体
NSMutableData *mPacketData = [NSMutableData dataWithData:self.dataHead];
[mPacketData appendData:data];

//清空包体变量
self.dataHead = nil;

//解析整个包
SocketResponse *response = [SocketResponse responseFromData:[mPacketData copy]]

//再次读取包头数据,以便后续包的读取
[sock readDataToLength:LENGTH_HEAD withTimeout:-1 tag:TAG_HEAD];
} else {
NSLog(@"Socket: DidReadData: withTag: 没有相应的 TAG");
}
}

解析:

SocketResponse.m

+ (instancetype)responseFromData:(NSData *)respData
{
// 解压包头
SocketPacketHead *currentHead = [[SocketPacketHead alloc] initWithData:respData];

// 包体数据
NSData *bodyData = [respData subdataWithRange:NSMakeRange(LENGTH_HEAD, respData.length - LENGTH_HEAD)];

//包解析
SocketResponse *resp = [self finalBodyDataWithHead:currentHead bodyData:bodyData];
return resp;
}

  • Post title:socket 研究
  • Post author:ChenghuiBai
  • Create time:2020-06-08 19:29:34
  • Post link:https://baichenghui.github.io/2020/06/08/socket-研究/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.