IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> socket聊天 -> 正文阅读

[移动开发]socket聊天

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;
/// 收到消息回调
// Code...

@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) {
        /// 这样直接发送图片,在终端收到之后是一堆乱码。一般的发送图片,需要先上传图片到服务,之后再发一个上传成功的消息。
//        dispatch_async(dispatch_get_global_queue(0, 0), ^{
//            NSData *data = [NSData dataWithContentsOfURL:fileURL];
//            dispatch_async(dispatch_get_main_queue(), ^{
//                [self.socket writeData:data withTimeout:1 tag:1];
//            });
//        });
    }
}

- (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 {
    // 收到消息后,对消息做解析,然后判断是哪种类型的消息,再回调出去。根据不同的业务做封装即可。
    // Code ...
}
- (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];
    // Do any additional setup after loading the view.
    
    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];
    
    // 发送图片
//    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"button_production@2x" ofType:@".png"];
//    [self.socket sendPicture:[NSURL fileURLWithPath:filePath]];
}


#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] initWithSocketQueue:dispatch_get_main_queue()];
        _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        [_socket setDelegate:self];
        [_socket setAutoDisconnectOnClosedReadStream:NO];
    }
    return _socket;
}

其他

除此之外,代码中应该设置心跳连接,以此保证长连接的连接状态,还应该考虑自动重连等情况,可以根据不同的业务场景对代码进行封装。整体上还是比较简单的,使用起来很方便。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-26 10:17:52  更:2021-09-26 10:20:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:38:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码