iOS即时通讯(1)--Socket
Socket简介
Socket是应用层和TCP/IP协议族通信的中间软件抽象层,它是一组接口。
TCP和UDP的区别
TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。
UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
TCP的三次握手和四次握手
TCP三次握手
所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
首先了解一下几个标志,SYN(synchronous),同步标志,ACK (Acknowledgement),即确认标志,seq应该是Sequence Number,序列号的意思,另外还有四次握手的fin,应该是final,表示结束标志。
第一次握手:客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的序列号加1以,即X+1。
第三次握手:客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1。并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写序列号的+1。
TCP四次握手
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
其实有个问题,为什么连接的时候是三次握手,关闭的时候却是四次挥手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
TCPSocket和UDPSocket的具体实现
基本TCP客户—服务器程序设计基本框架基本UDP客户—服务器程序设计基本框架流程图
Socket常用函数:
创建套接字Socket(af, type, protocol)
建立地址和套接字的联系bind(sockid, local addr, addrlen)
服务器端侦听客户端的请求listen(sockid, quenlen)
建立服务器/客户端的连接(面向连接TCP)
客户端请求连接Connect(sockid, destaddr, addrlen)
服务器端等待从编号为Sockid的Socket上接收客户连接请求newsockid=accept(Sockid,Clientaddr, paddrlen)
发送/接收数据
面向连接:
send(sockid, buff, bufflen)
recv()
面向无连接:
sendto(sockid, buff, …, addrlen)
recvfrom()
释放套接字close(sockid)
CocoaAsyncSocket的学习
初始化环境
RunLoop文件夹下加入工程,添加CFNetwork.framework,再导入以下文件头:
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
详细使用
一个iOS项目一般只有一个正在连接的Socket,所以需要使用单例或者AppDelegate数据共享,在使用结束后,要将Socket关闭,减轻服务器负荷。
单例类:
#import <Foundation/Foundation.h>
#import "AsyncSocket.h"
#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
static dispatch_once_t onceToken = 0; \
__strong static id sharedInstance = nil; \
dispatch_once(&onceToken, ^{ \
sharedInstance = block(); \
}); \
return sharedInstance; \
enum{
SocketOfflineByServer, //服务器掉线,默认为0
SocketOfflineByUser, //用户主动cut
};
@interface Singleton : NSObject <AsyncSocketDelegate>
@property (nonatomic, strong) AsyncSocket *socket; //socket
@property (nonatomic, copy) NSString *socketHost; //socket的Host
@property (nonatomic, assign) UInt16 socketPort; //socket的prot
@property (nonatomic, retain) NSTimer *connectTimer; //计时器
+ (Singleton *)sharedInstance;
-(void)socketConnectHost;// socket连接
-(void)cutOffSocket;// 断开socket连接
@end
#import "Singleton.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
@implementation Singleton
+ (Singleton *)sharedInstance{
static Singleton *sharedInstace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstace = [[self alloc] init];
});
return sharedInstace;
}
// socket连接
- (void)socketConnectHost{
self.socket = [[AsyncSocket alloc] initWithDelegate:self];
NSError *error = nil;
[self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];
}
// 连接成功回调
#pragma mark - 连接成功回调
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
NSLog(@"socket连接成功");
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
[self.connectTimer fire];
}
// 心跳连接
- (void)longConnectToSocket{
// 根据服务器要求发送固定格式的数据,假设为指令@"longConnect",但是一般不会是这么简单的指令
NSString *longConnect = @"longConnect";
NSData *dataStream = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:dataStream withTimeout:1 tag:1];
}
// 切断socket
- (void)cutOffSocket{
self.socket.userData = SocketOfflineByUser;
[self.connectTimer invalidate];
[self.socket disconnect];
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
NSLog(@"sorry the connect is failure %ld",sock.userData);
if (sock.userData == SocketOfflineByServer) {
// 服务器掉线,重连
[self socketConnectHost];
}
else if (sock.userData == SocketOfflineByUser) {
// 如果由用户断开,不进行重连
return;
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
// 对得到的data值进行解析与转换即可
[self.socket readDataWithTimeout:30 tag:0];
}
@end
简单的使用
[Singleton sharedInstance].socketHost = @"192.186.100.21";// host设定
[Singleton sharedInstance].socketPort = 10045;// port设定
// 在连接前先进行手动断开
[Singleton sharedInstance].socket.userData = SocketOfflineByUser;
[[Singleton sharedInstance] cutOffSocket];
// 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
[Singleton sharedInstance].socket.userData = SocketOfflineByServer;
[[Singleton sharedInstance] socketConnectHost];