iOS - AsyncSocket 的使用
发布日期:2021-08-25 00:12:11 浏览次数:1 分类:技术文章

本文共 20633 字,大约阅读时间需要 68 分钟。

1、AsyncSocket

  • 基于 CFSocket、GCD 进行的封装(OC)。

  • 支持 TCP 和 UDP。

  • 完整的回调函数(用于处理各种回调事件,连接成功,断开连接,收到数据等)。

  • 需要注意的问题:

    • 1、Socekt 连接成功回调方法中主动调用:[self.socket readDataWithTimeout:-1 tag:0];,相当于主动添加一个读取请求,不然不会执行读取信息回调方法。
    • 2、读取信息回调方法中,读取完信息后,主动调用一下 [self.socket readDataWithTimeout:-1 tag:0];,读取完信息后,重新向队列中添加一个读取请求,不然当收到信息后不会执行读取回调方法。

2、基本使用

2.1 Client 客户端

  • TCP 客户端

    #import "GCDAsyncSocket.h"    @interface ViewController () 
    @property (weak, nonatomic) IBOutlet UITextField *addrTF; @property (weak, nonatomic) IBOutlet UITextField *portTF; @property (weak, nonatomic) IBOutlet UITextField *msgTF; @property (weak, nonatomic) IBOutlet UITextView *logTV; @property (nonatomic, strong) GCDAsyncSocket *clientSockfd; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建 Socket,在主队列中处理,所有的回执都在主队列中执行。 self.clientSockfd = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } - (IBAction)connectBtnClick:(id)sender { NSError *error = nil; // Socket 连接 if (![self.clientSockfd isConnected]) { [self.clientSockfd connectToHost:_addrTF.text onPort:_portTF.text.intValue error:&error]; } if (error != nil) { [self showLogMessage:@"连接失败..."]; } } #pragma mark - GCDAsyncSocketDelegate 协议方法 // 连接成功,协议方法 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { // 读取数据,必须添加,相当于主动添加一个读取请求,不然不会执行读取信息回调方法 [self.clientSockfd readDataWithTimeout:-1 tag:0]; [self showLogMessage:[NSString stringWithFormat:@"连接服务器地址:%@, 端口:%d 成功", host, port]]; [self showLogMessage:[NSString stringWithFormat:@"本地地址:%@, 端口:%d", sock.localHost, sock.localPort]]; } // 已经断开链接,协议方法 - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { [self showLogMessage:@"socket 断开连接..."]; } // 读取到数据,协议方法 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { // 注意:要想长连接,必须还要在 DidReceiveData 的 delegate 中再写一次 [_udpSocket receiveOnce:&error] // 读取数据,读取完信息后,重新向队列中添加一个读取请求,不然当收到信息后不会执行读取回调方法。 [self.clientSockfd readDataWithTimeout:-1 tag:0]; NSString *strMsg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self showLogMessage:[NSString stringWithFormat:@"recv:%@", strMsg]]; } #pragma mark - 发送数据 - (IBAction)sendBtnClick:(id)sender { // Socket 发送数据 [self.clientSockfd writeData:[_msgTF.text dataUsingEncoding:NSUTF8StringEncoding] withTimeout:30 tag:0]; [self showLogMessage:[NSString stringWithFormat:@"send:%@", _msgTF.text]]; } // 显示信息 - (void)showLogMessage:(NSString *)msg { _logTV.text = [_logTV.text stringByAppendingFormat:@"%@\n", msg]; } // 键盘回收 - (void)touchesBegan:(NSSet
    *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end

    Socket11

2.2 Server 服务端

  • TCP 服务端

    #import 
    #import
    #import "GCDAsyncSocket.h" @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UITextField *addrTF; @property (weak, nonatomic) IBOutlet UITextField *portTF; @property (weak, nonatomic) IBOutlet UITextField *msgTF; @property (weak, nonatomic) IBOutlet UITextView *logTV; @property (nonatomic, strong) GCDAsyncSocket *serverSocketfd; @property (nonatomic, strong) GCDAsyncSocket *clientSocketfd; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSString *ip_addr = [self getIPAddress]; _addrTF.text = ip_addr; // 创建 Socket,在主队列中处理,所有的回执都在主队列中执行。 self.serverSocketfd = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } - (IBAction)listenBtnClick:(id)sender { NSError *error = nil; // Socket 监听 [self.serverSocketfd acceptOnPort:_portTF.text.intValue error:&error]; if (error != nil) { NSLog(@"监听出错:%@", error); } else{ [self showLogMessage:@"正在监听..."]; } } #pragma mark - GCDAsyncSocketDelegate 协议方法 // 接收到连接请求,协议方法 - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { // 读取数据,必须添加,相当于主动添加一个读取请求,不然不会执行读取信息回调方法 [newSocket readDataWithTimeout:-1 tag:0]; [self showLogMessage:@"收到客户端连接...."]; [self showLogMessage:[NSString stringWithFormat:@"客户端地址:%@, 端口:%d", newSocket.connectedHost, newSocket.connectedPort]]; self.clientSocketfd = newSocket; } // 已经断开链接,协议方法 - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { [self showLogMessage:@"socket 断开连接..."]; } // 读取到数据,协议方法 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { // 读取数据,读取完信息后,重新向队列中添加一个读取请求,不然当收到信息后不会执行读取回调方法。 [sock readDataWithTimeout:-1 tag:0]; NSString *strMsg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self showLogMessage:[NSString stringWithFormat:@"recv:%@",strMsg]]; } #pragma mark - 发送数据 - (IBAction)sendBtnClick:(id)sender { // Socket 发送数据 [self.clientSocketfd writeData:[_msgTF.text dataUsingEncoding:NSUTF8StringEncoding] withTimeout:30 tag:0]; [self showLogMessage:[NSString stringWithFormat:@"send:%@", _msgTF.text]]; } #pragma mark - 获取本地 IP 地址 - (NSString *)getIPAddress { NSString *address = @"error"; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; while (temp_addr != NULL) { if (temp_addr->ifa_addr->sa_family == AF_INET) { // Check if interface is en0 which is the wifi connection on the iPhone if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { // Get NSString from C String address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); return address; } // 显示信息 - (void)showLogMessage:(NSString *)msg { _logTV.text = [_logTV.text stringByAppendingFormat:@"%@\n", msg]; } // 键盘回收 - (void)touchesBegan:(NSSet
    *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end

    Socket12

2.3 UDP 通信

  • UDP 通信

    #import 
    #import
    #import "GCDAsyncUdpSocket.h" @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UITextField *desAddrTF; @property (weak, nonatomic) IBOutlet UITextField *desPortTF; @property (weak, nonatomic) IBOutlet UITextField *locAddrTF; @property (weak, nonatomic) IBOutlet UITextField *locPortTF; @property (weak, nonatomic) IBOutlet UITextField *msgTF; @property (weak, nonatomic) IBOutlet UITextView *logTV; @property (nonatomic, strong) GCDAsyncUdpSocket *udpSocketfd; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSString *ip_addr = [self getIPAddress]; _locAddrTF.text = ip_addr; // 创建 Socket,在主队列中处理,所有的回执都在主队列中执行 self.udpSocketfd = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } - (IBAction)connectBtnClick:(id)sender { NSError *error = nil; // 绑定端口 [self.udpSocketfd bindToPort:_locPortTF.text.intValue error:&error]; if (error != nil) { NSLog(@"绑定端口出错:%@", error); return; } else{ [self showLogMessage:[NSString stringWithFormat:@"绑定端口 %d 成功...", _locPortTF.text.intValue]]; } // 开始接收数据 [self.udpSocketfd beginReceiving:&error]; if (error != nil) { NSLog(@"开始接收数据出错:%@", error); } else{ [self showLogMessage:@"开始接收数据..."]; } } #pragma mark - GCDAsyncUdpSocketDelegate 协议方法 // 接收到的数据,协议方法 - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext { NSString *strMsg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self showLogMessage:[NSString stringWithFormat:@"recv:%@", strMsg]]; } #pragma mark - 发送数据 - (IBAction)sendBtnClick:(id)sender { // Socket 发送数据 [self.udpSocketfd sendData:[_msgTF.text dataUsingEncoding:NSUTF8StringEncoding] toHost:_desAddrTF.text port:_desPortTF.text.intValue withTimeout:30 tag:0]; [self showLogMessage:[NSString stringWithFormat:@"send:%@", _msgTF.text]]; } #pragma mark - 获取本地 IP 地址 - (NSString *)getIPAddress { NSString *address = @"error"; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; while (temp_addr != NULL) { if (temp_addr->ifa_addr->sa_family == AF_INET) { // Check if interface is en0 which is the wifi connection on the iPhone if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { // Get NSString from C String address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); return address; } // 显示信息 - (void)showLogMessage:(NSString *)msg { _logTV.text = [_logTV.text stringByAppendingFormat:@"%@\n", msg]; } // 键盘回收 - (void)touchesBegan:(NSSet
    *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end

    Socket13

2.4 Socket 长连接

  • PublicTool.h

    @interface PublicTool : NSObject    // 字典转换成 JSON 字符串    + (NSString *)JSONStringWithDic:(NSDictionary *)dic;    // JSON 字符串转换成字典    + (NSDictionary *)dictionaryWithJSON:(NSString *)json;    @end
  • PublicTool.m

    @implementation PublicTool    // 字典转成成 JSON 字符串    + (NSString *)JSONStringWithDic:(NSDictionary *)dic {        NSError *error = nil;        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic                                                           options:0                                                             error:&error];        if (jsonData == nil) {            NSLog(@"fail to get JSON from dictionary: %@, error: %@", self, error);            return nil;        }        NSString *jsonString = [[NSString alloc] initWithData:jsonData                                                     encoding:NSUTF8StringEncoding];        return jsonString;    }    // JSON 字符串转换成字典    + (NSDictionary *)dictionaryWithJSON:(NSString *)json {        NSError *error = nil;        NSData *jsonData = [json dataUsingEncoding:NSUTF8StringEncoding];        NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData                                                                 options:NSJSONReadingMutableContainers |                                                                         NSJSONReadingAllowFragments                                                                   error:&error];        if (jsonDict == nil) {            NSLog(@"fail to get dictioanry from JSON: %@, error: %@", json, error);            return nil;        }        return jsonDict;    }    @end
  • SocketSingleTon.h

    @interface SocketSingleTon : NSObject    @property (nonatomic, copy) NSString *hostAddr;    @property (nonatomic, copy) NSString *port;    @property (nonatomic, copy) void(^msgLog)(NSString *);    + (instancetype)shareInstance;    // 连接到服务器    - (void)connectToServer;    // 断开连接    - (void)cutOffSocket;    // 发送数据到服务器    - (void)sendDataToServer:(NSData*)data;    @end
  • SocketSingleTon.m

    #import "GCDAsyncSocket.h"    #import 
    #import
    #import "PublicTool.h" typedef NS_ENUM(NSInteger, SocketOffline) { SocketOfflineByServer, SocketOfflineByUser }; @interface SocketSingleTon ()
    @property (nonatomic, strong) GCDAsyncSocket *socket; @property (nonatomic, strong) NSTimer *beatTimer; @end @implementation SocketSingleTon + (instancetype)shareInstance { static SocketSingleTon *shareInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[self alloc] init]; }); return shareInstance; } // 连接到服务器 - (void)connectToServer { NSError *error = nil; if (self.socket != nil) { if ([self.socket isConnected]) { // 断开后再连接 self.socket.userData = @(SocketOfflineByUser); [self cutOffSocket]; [self.socket connectToHost:self.hostAddr onPort:self.port.intValue error:&error]; } else { [self.socket connectToHost:self.hostAddr onPort:self.port.intValue error:&error]; } } else { self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; [self.socket connectToHost:self.hostAddr onPort:self.port.intValue error:&error]; } if (error != nil) { NSLog(@"socket 连接失败:%@", error); } else { NSLog(@"socket 连接成功"); } } // 断开连接 - (void)cutOffSocket { self.socket.userData = @(SocketOfflineByUser); [self.socket disconnect]; } #pragma mark - GCDAsyncSocketDelegate 协议方法 // 连接成功,协议方法 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { [sock readDataWithTimeout:-1 tag:0]; NSString *logStr = [NSString stringWithFormat:@"连接主机:%@:%d 成功\n", host, port]; NSLog(@"%@", logStr); if (self.msgLog) { self.msgLog(logStr); } // 创建定时器,定时发送 beat 包 self.beatTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(longConnectToServer) userInfo:nil repeats:YES]; } // 连接断开,协议方法 - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { self.socket = nil; [self.beatTimer invalidate]; self.beatTimer = nil; if ([sock.userData isEqual: @(SocketOfflineByUser)]) { NSLog(@"the socket have been cutted off by user"); if (self.msgLog) { self.msgLog(@"the socket have been cutted off by user"); } } else if (sock.userData == SocketOfflineByServer) { NSLog(@"the socket have been cutted off by server"); if (self.msgLog) { self.msgLog(@"the socket have been cutted off by server"); } // reconnect [self connectToServer]; } else { NSLog(@"%@", err.localizedDescription); if (self.msgLog) { self.msgLog([NSString stringWithFormat:@"%@", err.localizedDescription]); } [self connectToServer]; } } // 收到消息,协议方法 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { [sock readDataWithTimeout:-1 tag:0]; char buf[1024]; [data getBytes:buf length:1024]; NSString *receivedData = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding]; NSLog(@"receivedData:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); if (receivedData.length > 0) { NSDictionary *dataDic = [PublicTool dictionaryWithJSON:receivedData]; if ([dataDic[@"msgType"] isEqualToString:@"beta"]) { NSString *strMsg = [NSString stringWithFormat:@"收到心跳确认的数据:%@\n", receivedData]; if (self.msgLog) { self.msgLog(strMsg); } } else if ([dataDic[@"msgType"] isEqualToString:@"normal"]) { NSString *strMsg = [NSString stringWithFormat:@"收到正常的数据:%@\n", receivedData]; if (self.msgLog) { self.msgLog(strMsg); } } else if ([dataDic[@"msgType"] isEqualToString:@"exit"]) { NSString *strMsg = [NSString stringWithFormat:@"收到关闭的数据:%@\n", receivedData]; if (self.msgLog) { self.msgLog(strMsg); } [self cutOffSocket]; } } } // 发送数据 - (void)longConnectToServer { [self sendDataToServer:[@"hello" dataUsingEncoding:NSUTF8StringEncoding]]; } // 发送数据到服务器 - (void)sendDataToServer:(NSData*)data { NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSMutableDictionary *dicParams = [NSMutableDictionary dictionary]; if ([dataStr isEqualToString:@"hello"]) { [dicParams setValue:dataStr forKey:@"msg"]; [dicParams setValue:@"beta" forKey:@"msgType"]; } else { [dicParams setValue:dataStr forKey:@"msg"]; [dicParams setValue:@"normal" forKey:@"msgType"]; } NSData *sendData = [[PublicTool JSONStringWithDic:dicParams] dataUsingEncoding:NSUTF8StringEncoding]; NSString *strMsg = [NSString stringWithFormat:@"发送数据: %@\n", [PublicTool JSONStringWithDic:dicParams]]; if (self.msgLog) { self.msgLog(strMsg); } [self.socket writeData:sendData withTimeout:30 tag:0]; } @end
  • ViewController.m

    #import "SocketSingleTon.h"    #import "PublicTool.h"    @interface ViewController ()    @property (weak, nonatomic) IBOutlet UITextField *addressTF;    @property (weak, nonatomic) IBOutlet UITextField *portTF;    @property (weak, nonatomic) IBOutlet UITextField *msgTF;    @property (weak, nonatomic) IBOutlet UITextView *logTV;    @end    @implementation ViewController    - (IBAction)connectToServer:(id)sender {        // 创建 Socket        SocketSingleTon *socketInstance = [SocketSingleTon shareInstance];        socketInstance.hostAddr = _addressTF.text;        socketInstance.port = _portTF.text;        __weak __typeof (self)weakSelf = self;        socketInstance.msgLog = ^(NSString *log){            dispatch_async(dispatch_get_main_queue(), ^{                weakSelf.logTV.text = [weakSelf.logTV.text stringByAppendingString:log];            });        };        // 连接到服务器        [socketInstance connectToServer];    }    - (IBAction)cutOffConnect:(id)sender {        SocketSingleTon *socketInstance = [SocketSingleTon shareInstance];        // 断开连接        [socketInstance cutOffSocket];    }    - (IBAction)sendDataToServer:(id)sender {        SocketSingleTon *socketInstance = [SocketSingleTon shareInstance];        // 发送数据到服务器        [socketInstance sendDataToServer:[_msgTF.text dataUsingEncoding:NSUTF8StringEncoding]];    }    - (IBAction)sendBetaDataToServer:(id)sender {        SocketSingleTon *socketInstance = [SocketSingleTon shareInstance];        NSMutableDictionary *dicParams = [NSMutableDictionary dictionary];        [dicParams setValue:@"beta" forKey:@"msgType"];        [dicParams setValue:@"hello" forKey:@"msg"];        NSString *strMsg = [PublicTool JSONStringWithDic:dicParams];        // 发送心跳数据到服务器        [socketInstance sendDataToServer:[strMsg dataUsingEncoding:NSUTF8StringEncoding]];    }    - (IBAction)clearLog:(id)sender {        _logTV.text = nil;    }    // 键盘回收    - (void)touchesBegan:(NSSet
    *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end

    Socket14

转载地址:https://blog.csdn.net/weixin_33836223/article/details/90226034 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C# List 作为参数传递的值变化
下一篇:WannaCry勒索比特币蠕虫病毒解决方案

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月02日 14时26分07秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章