在我们平时的开发中,大多使用的是http/https连接,是客户端主动去请求的一对一模式,请求结束后立马断开,在需要数据时需要客户端主动请求,并且是服务器不能主动向客户端发送数据
而socket连接时,一旦建立上连接,服务器和客户端都可以随时进行数据传输
我们常用的第三方,是CocoaAsyncSocket,它是封装好的一个IM框架,里面包含两种
1.GCDAsyncsocket
基于GCD
2.AsyncSocket
基于runloop
iOS的socket原生的类库是CFNetWork,但是不好用
AsyncSocket是对CFNetWork进行了封装的开源库
Demo1(使用GCDAsyncSocket实现最简单的两端通讯)
自己实现服务器和客户端(服务器是本机(IP: 127.0.0.1 断口: 自定义即可))
服务器:开两个socket,为什么要分开开两个我也不太清楚
一个socket负责开通端口,绑定IP等信息,并与客户端建立连接,并监听通讯
另一个负责与客户端进行通讯
客户端:开一个socket
下面看它们两个的连接图
下面用代码讲一下流程
注意每个代理里都有,这句代码是读取数据,读取成功后会回调接收到消息的代理方法
[self.socket readDataWithTimeout:-1 tag:0];
1.服务器端初始socket并开放端口,并监听客户端socket的链接
@property (nonatomic) GCDAsyncSocket *serverSocket;
self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
BOOL result = [self.serverSocket acceptOnPort:8080 error:&error];
if (result && error == nil)
{
//开放成功
}
2.客户端socket初始化并连接服务器端
@property (nonatomic) GCDAsyncSocket *socket;
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[self.socket connectToHost:@"127.0.0.1" onPort:8080 withTimeout:-1 error:nil];
连接成功时服务器端socket和客户端socket的代理会同时收到监听
服务器代理
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
//保存客户端的socket
self.clientSocket = newSocket;
[self showMessageWithStr:@"链接成功"];
[self showMessageWithStr:[NSString stringWithFormat:@"服务器地址:%@ -端口: %d", newSocket.connectedHost, newSocket.connectedPort]];
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
客户端代理
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
[self showMessageWithStr:@"连接成功"];
[self showMessageWithStr:[NSString stringWithFormat:@"服务器IP : %@", host]];
[self.socket readDataWithTimeout:-1 tag:0];
}
3.两端之间消息通讯均是用下列方法,注意是发的data数据
NSData *data = [self.messageTF.text dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:-1 tag:0];
4.两端代理收到消息时
服务器端
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:text];
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
客户端
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:text];
[self.socket readDataWithTimeout:-1 tag:0];
}
Demo2(服务器端还是Demo1的GCDAsyncSocket的例子,这次使用AsyncSocket将客户端进行自定义封装)
实际项目中不可能在页面内写请求,肯定是要封装成单例去请求,然后何时请求,去调用单例方法即可
应该是下面的使用方法
1.在外部时,调用单例连接IP和端口
[Singleton sharedInstance].socketHost = @"127.0.0.1";
[Singleton sharedInstance].socketPort = 8080;
//在连接前先进行手动断开
[Singleton sharedInstance].socket.userData = SocketOfflineByUser;
[[Singleton sharedInstance] cutOffSocket];
//确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
[Singleton sharedInstance].socket.userData = SocketOfflineByServer;
[[Singleton sharedInstance] socketConnectHost];
2.单例中,在连接上的回调中,初始化计时器,每30S去跟服务器心跳连接
#import <Foundation/Foundation.h>
#import "AsyncSocket.h"
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
m文件中
#pragma mark - 外部方法
+(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];
}
// 切断socket
-(void)cutOffSocket
{
self.socket.userData = SocketOfflineByUser;
[self.connectTimer invalidate];
[self.socket disconnect];
}
#pragma mark - AsyncSocketDelegate
//连接成功
-(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];
[self.socket readDataWithTimeout:-1 tag:0];
}
//重新连接
-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
NSLog(@"连接失败,重连 %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
{
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到服务器端传来的消息%@",text);
[self.socket readDataWithTimeout:30 tag:0];
}
#pragma mark - Other Functions
// 心跳连接
-(void)longConnectToSocket
{
NSString *longConnect = @"心跳连接";
NSData *dataStream = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:dataStream withTimeout:1 tag:1];
}
@end
GitHub地址:
https://github.com/CarolineQian/FQSimpleSocketDemo
网友评论