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;
}

其他

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
欢迎!对于socket聊天室的问题,我可以给你一些基本的解释和示例代码。Socket是一种用于网络通信的编程接口,它可以在不同计算机之间建立连接并进行数据交换。聊天室则是基于Socket实现的一种即时通讯应用。 在创建一个socket聊天室之前,你需要选择一个编程语言和相应的网络库来实现。常见的选择有Python的socket库、Java的Socket和ServerSocket类、C/C++的socket函数等。 以下是一个简单的Python socket聊天室示例代码: ```python import socket import threading def handle_client(client_socket, client_address): print(f"New connection from {client_address}") while True: data = client_socket.recv(1024).decode() if not data: break print(f"Received message: {data}") # 在这里处理收到的消息,比如广播给其他客户端或者进行其他逻辑操作 response = "Server received: " + data client_socket.send(response.encode()) client_socket.close() print(f"Connection closed from {client_address}") def start_server(): host = '127.0.0.1' # 服务器IP地址 port = 8000 # 服务器端口号 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind((host, port)) server_socket.listen(5) # 最多同时接受5个客户端连接 print(f"Server started on {host}:{port}") while True: client_socket, client_address = server_socket.accept() client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) client_thread.start() start_server() ``` 上述代码创建了一个简单的服务器,它绑定到本地IP地址和指定端口上,并使用多线程处理客户端的连接。在`handle_client`函数中,我们可以编写逻辑来处理客户端发送的消息,并回复给客户端。 请注意,这只是一个基本的示例代码,实际应用中可能需要处理更多的异常情况和安全性问题。此外,你可能还需要实现客户端代码以连接到服务器并发送消息。 希望这个示例能对你有所帮助!如有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morris_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值