GCDAsyncSocket不通过服务器进行客户端间直接连接—iOS移动开发

之前客户端与客户端间的交流是需要通过服务器的转发来进行
现在该方案是:
1.需要一个公共服务器来刷新在线客户端的列表
2.客户端与客户端的数据传递不再需要通过服务器转发
3.客户端与客户端之间建立一个长连接
(使用OC Object-C实现)
构思示意图如下:

构思图.jpeg
  • 首先,我们需要写一个简单的公共服务器

1.头文件

#import <Foundation/Foundation.h>

@interface ServerSocket : NSObject

/** 端口 */
@property (nonatomic,assign)uint16_t port;

/** 监听地址 */
@property (nonatomic,copy)NSString *listenURL;

/**
 *  单例类方法
 *
 *  @return 单例对象
 */
+(instancetype)shareServerSocket;

/**
 *  开始监听
 */
-(void)startAccept;

@end

2.实现文件

#import "ServerSocket.h"
#import "GCDAsyncSocket.h"
@interface ServerSocket () <GCDAsyncSocketDelegate>
  /** socket */
  @property (nonatomic,strong)GCDAsyncSocket *socket;

  ** 客户端socket数组 */
  @property (nonatomic,strong)NSMutableArray *clientSockets;
@end

@implementation ServerSocket

-(NSMutableArray *)clientSockets{
   if (!_clientSockets) {
        _clientSockets = [NSMutableArray array];
    }
    return _clientSockets;
 }

+(instancetype)shareServerSocket{
  static ServerSocket *serverSocket;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    serverSocket = [[self alloc]init];
  });
   return serverSocket;
}

-(instancetype)init{
  if (self = [super init]) {
    _socket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
  }
    return self;
}

-(instancetype)init{
   if (self = [super init]) {
      _socket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    }
    return self;
  }
-(void)startAccept{   
   NSError *error = nil;
  [self.socket acceptOnInterface:self.listenURL port:self.port error:&error];

  if (error) {
      NSLog(@"开启监听失败 : %@",error);
  }else{
      NSLog(@"开启监听成功");
  }
}

#pragma mark - GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{

  //存放客户端的socket对象。
  [self.clientSockets addObject:newSocket];
  [newSocket readDataWithTimeout:-1 tag:0];

  //向每一个客户端发送给在线客户端列表
  [self sendClientList];
}

#pragma mark - GCDAsyncSocketDelegate
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{

  //每当有客户端断开连接的时候,客户端数组移除该socket
  [self.clientSockets removeObject:sock];

  //向每一个客户端发送给在线客户端列表
  [self sendClientList];
}

-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

  [sock readDataWithTimeout:-1 tag:tag];

}

/**
 *  向每一个连接的客户端发送所有
 */
-(void)sendClientList{
  //把socket对象中的host和post转化成字符串,存放到数组中
  NSMutableArray *hostArrM = [NSMutableArray array];
  for (GCDAsyncSocket *clientSocket in self.clientSockets) {
      NSString *host_port = [NSString stringWithFormat:@"%@:%d",clientSocket.connectedHost,clientSocket.connectedPort];
      [hostArrM addObject:host_port];
  }

  //再把数组发送给每一个连接的客户端
  NSData *clientData = [NSKeyedArchiver archivedDataWithRootObject:hostArrM];  
  for (GCDAsyncSocket *clientSocket in self.clientSockets) {
      [clientSocket writeData:clientData withTimeout:-1 tag:0];
  }
}

@end
  • 由于客户端的代码较多,所以这里只写核心
工程文件.png
  • ServerSocket.m 主要是客户端作为同时进行监听的服务,跟公共服务器的代码差不多,不同的是要增加一个协议和代理
@protocol ServerSocketDelegate <NSObject>

-(void)ServerSocket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;

@end


/** 代理 */
@property (nonatomic,weak)id<ServerSocketDelegate> delegate;

/** 接收到其他客户端发来的信息,要把信息传递给代理,通知外界 */
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

    [sock readDataWithTimeout:-1 tag:0];

    if ([self.delegate respondsToSelector:@selector(ServerSocket:didReadData:withTag:)]) {
        [self.delegate ServerSocket:sock didReadData:data withTag:tag];
    }
}
  • ListenDelegate.m 是用来接收自身客户端连接到其他客户端时候的代理,其实可以用当前的controller做为代理,但为了区分跟公共服务器的代理,所以这里另外新建一个类
#import "ListenDelegate.h"
#import <GCDAsyncSocket.h>

@interface ListenDelegate ()

@end

@implementation ListenDelegate

-(void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{

}

-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
     NSLog(@"客户端与客户端连接成功");
    [sock readDataWithTimeout:-1 tag:0 ];
}

-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"客户端与客户端连接失败 error:%@",err);
}

@end
  • ViewController.m 主要界面和逻辑的实现 简单实现界面如下:
界面.png
  • 主要代码如下:
#pragma mark - GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

    NSArray *clientOnLine = [NSKeyedUnarchiver unarchiveObjectWithData:data];

    //根据最新的在线列表来删除自身在线列表字典已经下线的客户端
    NSMutableArray *deleteKeys = [self.dataSoures mutableCopy];
    [deleteKeys removeObjectsInArray:clientOnLine];

    for (NSString * key in deleteKeys) {
        [self.mySocketsDict removeObjectForKey:key];
    }

    //获取最新的客户端在线列表
    self.dataSoures = [clientOnLine mutableCopy];

    dispatch_sync(dispatch_get_main_queue(), ^{
        [self.tableView reloadData];
    });

    [sock readDataWithTimeout:-1 tag:0];

}

-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
    NSLog(@"连接成功");
    //更新选中的socket
    self.activeSocket = sock;

    dispatch_sync(dispatch_get_main_queue(), ^{
        self.connectBtn.enabled = NO;
        self.disConnect.enabled = YES;
    });

    //开启自身的监听
    ServerSocket *listenSocket = [ServerSocket shareServerSocket];
    listenSocket.port = sock.localPort;
    listenSocket.listenURL = ListenHost;
    self.listenSocket = listenSocket;
    self.listenSocket.delegate = self;
    [listenSocket startAccept];


    [sock readDataWithTimeout:-1 tag:0];
}

-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{

    //断开连接时,清空数据源
    [self.dataSoures removeAllObjects];

    dispatch_sync(dispatch_get_main_queue(), ^{
        self.connectBtn.enabled = YES;
        self.disConnect.enabled = NO;
        [self.tableView reloadData];
    });

}

#pragma mark - UITableViewDataSource

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.dataSoures.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString * cellID = @"cellID";
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if (!cell) {
        cell = [[MyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault     reuseIdentifier:cellID];
    }
    if (self.dataSoures.count) {
        cell.textLabel.text = self.dataSoures[indexPath.row];
    }
    return cell;
}

#pragma mark - UITableViewDelegate

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    ListenDelegate *ltnDelegate = [[ListenDelegate alloc]init];
    self.ltnDelegate = ltnDelegate;

    //根据选中的客户端进行长连接
    //查看已经保存的其他客户端列表字段中是否已经存在相应的socket
    NSString *hostStr = self.dataSoures[indexPath.row];
    GCDAsyncSocket *activeSocket = self.mySocketsDict[hostStr];

    //如果字典中没有对应的socket,则创建新的socket,并且存进字段
    if (!activeSocket) {
        activeSocket = [[GCDAsyncSocket alloc]initWithDelegate:ltnDelegate delegateQueue:dispatch_get_global_queue(0, 0)];
        self.mySocketsDict[hostStr] = activeSocket;
    }

    //更新选中的socket
    self.activeSocket = activeSocket;

    //行进socket连接
    NSArray *arr = [hostStr componentsSeparatedByString:@":"];

    NSError *error = nil;

    NSString *portStr = arr[1];

    [activeSocket connectToHost:arr[0] onPort:portStr.integerValue error:&error];

}

#pragma mark ServerSocketDelegate

-(void)ServerSocket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    //接受其他客户端发送的消息,并且显示
    NSString *receiveStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSString *resultStr = [NSString     stringWithFormat:@"%@\n%@",self.receiveDataLabel.text,receiveStr] ;
        self.receiveDataLabel.text = resultStr;
    });

}

完整项目下载地址:
https://github.com/kinglchristina/ClientSocket.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容