本篇文章主要针对聊天室的Socket连接,基于GCDAsyncSocket实现.对不太了解GCDAsyncSocket的同学请自行咨询度娘.当然一个聊天室的实现并不只有一个简单的Socket,后续还有聊天室的管理,一时整理不好头绪,之后会和大家分享一下思路.
本类只负责了Socket的连接,记录连接状态,收到数据,发送数据,具体的解析数据等在其他的类里.依旧是废话不多说,撸代码,看思路.
SocketLinker.h文件
//首先是定义枚举,记录连接的状态
typedef NS_ENUM(NSUInteger, LINKSTATE)
{
LINKSTATE_UNLINK = 0, // 未连接
LINKSTATE_LINKING = 1, // 连接中
LINKSTATE_LINKED = 2, // 连接成功了
LINKSTATE_LOGOUT = 3 // 退出登录(退出软件用户时的情况,不需要重连)
};
//设置代理方法
@protocol SocketLinkerDelegate <NSObject>
//连接成功
- (void)socketDidConnectToHost:(NSString *)host port:(uint16_t)port;
//连接失败的代理,外界操作处理,比如停止发送心跳包,申请重连
- (void)socketDidDisconnectWithError:(NSError *)error;
//读取Socket数据
- (void)socketDidResponse:(NSData *)data;
@end
@protocol ConnectStateDelegate <NSObject>
//连接状态改变
- (void)connectDidChangeConnectState:(LINKSTATE)newState;
@end
@interface SocketLinker : NSObject
//连接状态,LINKSTATE
@property (nonatomic,assign) LINKSTATE linkState;
@property (nonatomic, weak) id<SocketLinkerDelegate> delegate;
@property (nonatomic, weak) id <ConnectStateDelegate> connectStateDelegate;
//根据服务器主机和端口初始化
- (id)initWithHost:(NSString *)host port:(uint16_t)port;
//连接
- (void)connect;
//断开连接
- (void)disconnect;
// 发送数据包
- (void)sendMsgPacket:(NSData *)packet;
// 读取消息包
- (void)readMsgPacket;
//判断Socket连接状态供外界调用
- (BOOL)isSocketConnected;
@end
以上就是.h中的所有代码了,之后在.m中
@interface SocketLinker()<GCDAsyncSocketDelegate>//遵循GCDAsyncSocketDelegate
//两个线程锁
@property (nonatomic, strong) NSObject *linkLock;
@property (nonatomic, strong) NSObject *lockObject;
//socket连接对象
@property (atomic, strong) GCDAsyncSocket *asyncSocket;
//记录服务器
@property (nonatomic, strong) NSString *host;
//记录端口
@property (nonatomic, assign) uint16_t port;
@end
初始化
- (id)initWithHost:(NSString*)host port:(uint16_t)port
{
self = [super init];
if (self)
{
self.linkLock = [[NSObject alloc] init];
self.lockObject = [[NSObject alloc]init];
self.host = host;
self.port = port;
/**
* LQ~ 初始状态为未连接
*/
self.linkState = LINKSTATE_UNLINK;
}
return self;
}
连接
- (void)connect
{
/**
* LQ~ 由于很可能同时多次进行socket连接,在这里使用线程锁,确保只进行一次连接
*/
@synchronized (self.linkLock){
if (LINKSTATE_LINKING != self.linkState)
{
// 把当前状态改为链接建立中,这里我们让所有的回调执行都发生在主线程的queue里,当然我们可以传一个专用的queue
self.asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
/*LQ~ 连接服务器 */
//CONNECT_TIMEOUT是一个宏定义,定义超时的时间,我用的是30秒
if (![self.asyncSocket connectToHost:self.host onPort:self.port withTimeout:CONNECT_TIMEOUT error:&error])
{
/*LQ~ 如果socktet连接失败,返回NO,记录连接为LINKSTATE_UNLINK */
self.linkState = LINKSTATE_UNLINK;
}
if (error != nil)
{
//当有错误的时候抛出异常错误信息
@throw [NSException exceptionWithName:@"GCDAsyncSocket" reason:[error localizedDescription] userInfo:nil];
}
//当socktet连接成功的时候,记录连接的状态,连接中
self.linkState = LINKSTATE_LINKING;
}
}
}
主动的断开连接
- (void)disconnect
{
@synchronized (self.linkLock)
{
if (self.asyncSocket != nil)
{
[self.asyncSocket disconnect];
}
self.linkState = LINKSTATE_LOGOUT;
}
}
连接成功后GCDAsyncSocket的代理回调
//连接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
//记录连接成功状态
self.linkState = LINKSTATE_LINKED;
if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidConnectToHost:port:)])
{
//代理执行连接成功后的操作
[self.delegate socketDidConnectToHost:host port:port];
}
//对读数据进行设置
[self readMsgPacket];
}
对读取数据进行设置
- (void)readMsgPacket
{
if (self.linkState == LINKSTATE_LINKED && self.asyncSocket.isConnected == YES)
{
//当确认连接成功后,就可以开始读服务器发送的数据了,设置超时的时间和tag值
//告诉asyncSocket可以准备好读信息了
[self.asyncSocket readDataWithTimeout:TIMEOUT_WRITE tag:TAG_READ_STREAM];
}
else
{
//如果连接失败了,要传给外界信号,我连接出错了,其余的事自己搞定
if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
{
[self.delegate socketDidDisconnectWithError:nil];
}
}
}
当收到服务器数据后的GCDAsyncSocket的代理回调
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
//收到了服务器传给的数据,相对应的读数据的类来处理收到的数据
if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidResponse:)])
{
[self.delegate socketDidResponse:[data mutableCopy]];
}
}
给服务器发送数据
- (void)sendMsgPacket:(NSData *)packet
{
if ((packet.length && self.linkState == LINKSTATE_LINKED) && self.asyncSocket.isConnected == YES)
{
//确定当前Socket是连接着的,发送数据
[self.asyncSocket writeData:packet withTimeout:TIMEOUT_WRITE tag:TAG_WRITE_STREAM];
}
else
{
if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
{
/*LQ~ 说明连接失败,要传给外界信号,我连接出错了,其余的事自己搞定
[self.delegate socketDidDisconnectWithError:nil];
}
}
//每次发完之后都要对读取消息进行一次设置
[self readMsgPacket];
}
写完数据之后的回调的GCDAsyncSocket的代理回调
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
//在这里我并没有做其他的操作,各位可以根据需要自己做相应的设置
}
查看scoket的连接状态,供外界调用查看
- (BOOL)isSocketConnected
{
return self.asyncSocket.isConnected;
}
重写linkState的setter方法
- (void)setLinkState:(LINKSTATE)linkState
{
_linkState = linkState;
if (self.connectStateDelegate && [self.connectStateDelegate respondsToSelector:@selector(connectDidChangeConnectState:)])
{
//连接状态发生了改变,外界代理去做相应的处理
[self.connectStateDelegate connectDidChangeConnectState:linkState];
}
}
当socket连接断开的时候GCDAsyncSocket的代理回调
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
self.asyncSocket = nil;
// 判断连接状态不是主动断开连接的时候
if(self.linkState != LINKSTATE_LOGOUT)
{
self.linkState = LINKSTATE_UNLINK;
if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
{
/*LQ~ 说明连接失败,要传给外界信号,我连接出错了,其余的事自己搞定
[self.delegate socketDidDisconnectWithError:err];
}
}
}
以上就是聊天室的Socket连接管理类了,大体思路就是:连接服务器->连接成功了告诉自己代理->准备读写数据,之间加入了对连接状态的判断,确保socket连接.
之后会有通讯管理类的相关写法思路.如果各位同学发现问题,请在下方留言指点,相互帮助提高.
网友评论