CocoaAsyncSocket
系统提供的实现socket的库是 <sys/socket.h>。
CocoaAsyncSocket是对socket的封装。有两个类GCDAsyncSocket和GCDAsyncUdpSocket,分别是基于TCP和UDP传输协议的。这里只用GCDAsyncSocket实现tcp长链接基本的聊天功能。
引入CocoaAsyncSocket
pod 'CocoaAsyncSocket'
开启一个服务
终端中使用命令:nc -lk 123 开启一个服务,其中123是端口号,这个可自己设置。
移动端代码
MMAsyncSocket内部引用GCDAsyncSocket,结合业务进行了简单的封装。
Demo
MMAsyncSocket.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, MMSocketConnectStatus) {
MMSocketConnectStatusDisconnect,
MMSocketConnectStatusConnecting,
MMSocketConnectStatusConnected,
};
@class MMAsyncSocket;
@protocol MMAsyncSocketDelegate <NSObject>
- (void)mmAsyncSocket:(MMAsyncSocket *)socket connectStatusDidChanged:(MMSocketConnectStatus)status;
- (void)mmAsyncSocket:(MMAsyncSocket *)sock didWriteDataWithTag:(long)tag;
- (void)mmAsyncSocket:(MMAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag;
@end
@interface MMAsyncSocket : NSObject
@property (nonatomic, assign, readonly) MMSocketConnectStatus status;
+ (instancetype)sharedInstance;
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
- (void)disconnect;
- (void)sendMsg:(NSString *)msg;
- (void)sendPicture:(NSURL *)fileURL;
- (void)addDelegate:(id<MMAsyncSocketDelegate>)delegate;
- (void)removeDelegate:(id<MMAsyncSocketDelegate>)delegate;
@end
NS_ASSUME_NONNULL_END
MMAsyncSocket.m
#import "MMAsyncSocket.h"
#import "GCDAsyncSocket.h"
@interface MMAsyncSocket ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *socket;
@property (nonatomic, assign, readwrite) MMSocketConnectStatus status;
@property (nonatomic, strong) NSHashTable *delegates;
@end
@implementation MMAsyncSocket
+ (instancetype)sharedInstance {
static MMAsyncSocket *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[MMAsyncSocket alloc] init];
_sharedInstance.status = MMSocketConnectStatusDisconnect;
});
return _sharedInstance;
}
- (GCDAsyncSocket *)socket {
if (!_socket) {
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[_socket setDelegate:self];
[_socket setAutoDisconnectOnClosedReadStream:NO];
}
return _socket;
}
- (NSHashTable *)delegates {
if (!_delegates) {
_delegates = [NSHashTable weakObjectsHashTable];
}
return _delegates;
}
- (void)setStatus:(MMSocketConnectStatus)status {
_status = status;
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:@selector(mmAsyncSocket:connectStatusDidChanged:)]) {
[delegate mmAsyncSocket:self connectStatusDidChanged:_status];
}
}
}
#pragma mark public
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr {
self.status = MMSocketConnectStatusConnecting;
return [self.socket connectToHost:host onPort:port error:errPtr];
}
- (void)disconnect {
self.socket.delegate = nil;
[self.socket disconnect];
self.socket = nil;
self.status = MMSocketConnectStatusDisconnect;
}
- (void)sendMsg:(NSString *)msg {
if (msg && _socket) {
[_socket writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:1 tag:1];
}
}
- (void)sendPicture:(NSURL *)fileURL {
if (fileURL && _socket) {
}
}
- (void)addDelegate:(id<MMAsyncSocketDelegate>)delegate {
if (![self.delegates containsObject:delegate]) {
[self.delegates addObject:delegate];
}
}
- (void)removeDelegate:(id<MMAsyncSocketDelegate>)delegate {
if ([self.delegates containsObject:delegate]) {
[self.delegates removeObject:delegate];
}
}
#pragma mark GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
self.status = MMSocketConnectStatusConnected;
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:@selector(mmAsyncSocket:didWriteDataWithTag:)]) {
[delegate mmAsyncSocket:self didWriteDataWithTag:tag];
}
}
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err {
self.status = MMSocketConnectStatusDisconnect;
}
- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:@selector(mmAsyncSocket:shouldTimeoutWriteWithTag:)]) {
[delegate mmAsyncSocket:self shouldTimeoutWriteWithTag:tag];
}
}
return -1;
}
@end
ViewController.m
#import "ViewController.h"
#import "MMAsyncSocket.h"
@interface ViewController ()<MMAsyncSocketDelegate>
@property (nonatomic, strong) MMAsyncSocket *socket;
@property (weak, nonatomic) IBOutlet UITextField *ipTextField;
@property (weak, nonatomic) IBOutlet UITextField *portTextField;
@property (weak, nonatomic) IBOutlet UIButton *connectBtn;
@property (weak, nonatomic) IBOutlet UITextField *connetTextField;
@property (weak, nonatomic) IBOutlet UIButton *sendBtn;
@property (weak, nonatomic) IBOutlet UILabel *msgLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.socket = [MMAsyncSocket sharedInstance];
[self.socket addDelegate:self];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.ipTextField.text = @"127.0.0.1";
self.portTextField.text = @"123";
}
- (IBAction)connectClick:(UIButton *)sender {
if (self.socket.status == MMSocketConnectStatusDisconnect) {
NSString *ip = self.ipTextField.text;
int port = [self.portTextField.text intValue];
if (ip && ip.length && port & (port > 0)) {
NSError *error = nil;
BOOL result = [self.socket connectToHost:ip onPort:port error:&error];
if (result) {
[self.connectBtn setTitle:@"断开" forState:UIControlStateNormal];
} else {
NSLog(@"连接失败!");
}
}
}
else {
[self.socket disconnect];
[self.connectBtn setTitle:@"连接" forState:UIControlStateNormal];
}
}
- (IBAction)sendClick:(UIButton *)sender {
[self.socket sendMsg:self.connetTextField.text];
}
#pragma mark MMAsyncSocketDelegate
- (void)mmAsyncSocket:(MMAsyncSocket *)socket connectStatusDidChanged:(MMSocketConnectStatus)status {
switch (status) {
case MMSocketConnectStatusDisconnect:
[self.connectBtn setTitle:@"连接" forState:UIControlStateNormal];
break;
case MMSocketConnectStatusConnecting:
[self.connectBtn setTitle:@"断开" forState:UIControlStateNormal];
break;
case MMSocketConnectStatusConnected:
[self.connectBtn setTitle:@"断开" forState:UIControlStateNormal];
break;
}
NSLog(@"连接状态:%ld",(long)status);
}
@end
效果如下
连接并发送消息
点击连接按钮,连接成功后,输入内容,点击发送按钮,发送消息,终端中会打印出客户端应用程序发送的消息。 这样长连接就建立起来了。
报错问题处理
Error Domain=GCDAsyncSocketErrorDomain Code=1 "Attempting to connect without a delegate queue. Set a delegate queue first." UserInfo={NSLocalizedDescription=Attempting to connect without a delegate queue. Set a delegate queue first.}
初始化CocoaAsyncSocket对象的时候未设置delegate queue,采用如下的初始化方法:
- (GCDAsyncSocket *)socket {
if (!_socket) {
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[_socket setDelegate:self];
[_socket setAutoDisconnectOnClosedReadStream:NO];
}
return _socket;
}
其他
除此之外,代码中应该设置心跳连接,以此保证长连接的连接状态,还应该考虑自动重连等情况,可以根据不同的业务场景对代码进行封装。整体上还是比较简单的,使用起来很方便。
|