前言

最近在做一套点对点传输的软件, 需要用到Socket进行设备间通讯. 去网上查了查, 对Socket分装比较好的就是目前特别火的GCDAsyncSocket这个类了, 这篇文章就GDCAsyncSocket与GCDAsyncUdpSocket进行单例封装, 一台设备通过UDP广播, 对外发送自己的IP地址与端口号, 另一台设备做接收, 接收后连接到IP地址与端口号, 从而进行TCP连接进行数据传输. 说明一下, SocketHelper单例类是Server与Client两用的, 使用时需指定设备类型. 下面先来看一下SocketHelper.

SocketHelper.h

#import <Foundation/Foundation.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import "GCDAsyncSocket.h"
#import "GCDAsyncUdpSocket.h"#define TAG 999 // 用于设备间通信
#define BROADCAST_HOST  @"255.255.255.255"
#define CLIENT_UDP_PORT 7890
#define TCP_PORT        45000
#define kDATACONVERSION @"Dictionary To Data"typedef enum : NSUInteger {Server,Client,
} Type;@interface SocketHelper : NSObject <GCDAsyncSocketDelegate, GCDAsyncUdpSocketDelegate> {dispatch_queue_t  _tcpSocketQueue;dispatch_queue_t  _udpSocketQueue;dispatch_source_t _source;  // 定时起源
}@property (nonatomic, strong) GCDAsyncSocket    *tcpSocket;    // Server | Client 进行TCP连接使用
@property (nonatomic, strong) GCDAsyncUdpSocket *udpSocket;    // Server 通过广播向 Client 发送 Server 的IP地址@property (nonatomic, strong, readonly) NSMutableArray    *connectedSocekts;  // 存放已连接 Socket 的数组
@property (nonatomic, strong, readonly) NSDictionary      *readUdpDataDic;    // @{host:@(port)}
@property (nonatomic, assign) long                tag;                        // 对数据进行区分
@property (nonatomic, assign, readonly) BOOL      isListening;                // Server 开始监听 Client
@property (nonatomic, assign) Type                type;@property (nonatomic, copy)   NSString *tcpHost;
@property (nonatomic, assign) UInt16    tcpPort;@property (nonatomic, copy)   NSString *udpHost;
@property (nonatomic, assign) UInt16    udpPort;#pragma mark - SHARED HELPER
+ (SocketHelper *)sharedHelper;#pragma mark - BROADCAST
/*** 允许 Socket 发送广播* @param flag YES or NO* @reutrn YES or NO*/
- (BOOL)enableBroadcast:(BOOL)flag;#pragma mark - BING
/*** 绑定 UDP Socket 的端口* @param port 端口号* @reutrn YES or NO*/
- (BOOL)bindToPort:(uint16_t)port;#pragma mark - RECEIVING
/*** 成功开启后可连续接收数据* @return YES or NO*/
- (BOOL)beginReceiving;/*** 成功开启后只接收一次数据, 开在之后添加 - (BOOL)beginingReceiving 做转换*/
- (BOOL)receiveOnce;#pragma mark - UDP CONNECT
/*** 通过广播对外发送数据* @param data 广播的数据* @param host 向 host 所在的地址进行广播 @"255.255.255.255"* @param port 广播的端口号, 填写 Client 绑定的端口*/
- (void)broadcastData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTag:(long)tag;/*** 开始进行广播*/
- (void)startedBroadcasting;
/*** 开始循环发送广播数据*/
- (void)startCycle;#pragma mark - UDP DATA PRPGRESSING
/*** 向指定 host:port 发送数据* @param data 发送的数据* @param host 指定的 IP 地址* @param port 指定端口* @param tag  通过 tag 的值对传输数据进行分类*/
- (void)sendData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTag:(long)tag;#pragma mark - TCP CONNECT
/*** Server 对指定端口进行监听*/
- (void)startListeningPort;/*** Client 通过 Host:Port 与 Server 进行连接*/
- (void)startConnect;#pragma mark - TCP DATA PRPGRESSING
/*** TCP 连接时发送数据* @param data 需要传送的数据* @param tag  通过 tag 的值对传输数据进行分类*/
- (void)writeData:(NSData *)data withTag:(long)tag;#pragma mark - GET IP ADDRESS
/*** 获取本机的IP地址
 @return IP地址的字符串*/
- (NSString *)getIpAddress;#pragma mark - DATA PRPGRESSING
/*** 将 id 类型转化为 NSData* @param  object 带转化 object* @return 返回转化后 NSData*/
- (NSData *)returnDataWithObject:(id)object;/*** 将 NSData 类型转化为 id 类型* @param  data 待转换数据* @return 返回 id 类型*/
- (id)returnDictionaryWithData:(NSData *)data;@end

SocketHelper.m

#import "SocketHelper.h"@implementation SocketHelper#pragma mark - SHARED HELPER
+ (SocketHelper *)sharedHelper {static dispatch_once_t predictate;static SocketHelper *_socketHelper;dispatch_once(&predictate, ^{_socketHelper = [SocketHelper new];[_socketHelper setupSocket];});return _socketHelper;
}#pragma mark - SET UP- (instancetype)init
{self = [super init];if (self) {self.connectedSocekts = [[NSMutableArray alloc] initWithCapacity:1];self.tcpPort          = TCP_PORT;self.tag              = TAG;self.isListening      = NO;}return self;
}- (void)setupSocket {_tcpSocketQueue = dispatch_queue_create("socketQueue", DISPATCH_QUEUE_CONCURRENT);self.tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_tcpSocketQueue];_udpSocketQueue = dispatch_queue_create("socketQueue", DISPATCH_QUEUE_CONCURRENT);self.udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:_udpSocketQueue];
}- (void)setIsListening:(BOOL)isListening {if (_isListening != isListening) {_isListening = isListening;}
}- (void)setConnectedSocekts:(NSMutableArray *)connectedSocekts {@synchronized (self) {_connectedSocekts = connectedSocekts;}
}- (void)setReadUdpDataDic:(NSDictionary *)readUdpDataDic {@synchronized (self) {_readUdpDataDic = readUdpDataDic;}
}#pragma mark - BROADCAST
- (BOOL)enableBroadcast:(BOOL)flag {NSError *error = nil;if (![self.udpSocket enableBroadcast:flag error:&error]) {NSLog(@"Broadsast error: %@", [error  description]);return NO;}return YES;
}#pragma mark - BING
- (BOOL)bindToPort:(uint16_t)port {NSError *error = nil;if (![self.udpSocket bindToPort:port error:&error]) {NSLog(@"Bind error: %@", [error description]);return NO;}return YES;
}#pragma mark - RECEIVING
- (BOOL)beginReceiving {NSError *error = nil;if (![self.udpSocket beginReceiving:&error]) {NSLog(@"Socket beginReveiving error: %@", [error description]);return NO;}return YES;
}- (BOOL)receiveOnce {NSError *error = nil;if (![self.udpSocket receiveOnce:&error]) {NSLog(@"Receive error: %@", [error description]);}return YES;
}#pragma mark - UDP CONNECT
- (void)broadcastData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTag:(long)tag {[self.udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:tag];
}- (void)startedBroadcasting {NSString *host = [NSString stringWithFormat:@"%@", [self getIpAddress]];UInt16 port = _tcpPort;NSDictionary *dic = @{host:@(port)};NSData *data = [self returnDataWithObject:dic];[self broadcastData:data toHost:BROADCAST_HOST port:CLIENT_UDP_PORT withTag:TAG];
}- (void)startCycle {dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//创建一个定时起源dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);_source = source;//设置回调时间间隔int64_t interval = (int64_t)(5 * NSEC_PER_SEC);//设置定时器开始时间dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC));//启动计时器//参数1:timer//参数2:开始时间//参数3:时间间隔//参数4:0dispatch_source_set_timer(source, start, interval, 0);//设置回调事件,即每次定时器触发的处理时间dispatch_source_set_event_handler(source, ^{static int number = 0;NSLog(@"%d", number);number++;//运行到第6秒则取消计时器if (_isListening) {
//            dispatch_source_cancel(source);NSLog(@"Cancle timer.");}[self startedBroadcasting];});//启动定时器dispatch_resume(source);
}#pragma mark - UDP DATA PRPGRESSING
- (void)sendData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTag:(long)tag {[self.udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:tag];
}#pragma mark - UDP DELEGATE
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {}- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {}- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {}- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {}- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {self.readUdpDataDic = [self returnDictionaryWithData:data];NSLog(@"%@", _readUdpDataDic);self.tcpHost = [_readUdpDataDic allKeys][0];[self startConnect];
}#pragma mark - TCP CONNECT
- (void)startListeningPort {NSError *error = nil;if (![self.tcpSocket acceptOnPort:_tcpPort error:&error]) {NSLog(@"Error starting server: %@", [error description]);return ;}NSLog(@"Echo server started on port %hu", _tcpPort);
}- (void)startConnect {NSError *error = nil;if (![self.tcpSocket connectToHost:_tcpHost onPort:_tcpPort error:&error]) {NSLog(@"Connect error: %@", [error description]);}
}#pragma mark - TCP DATA PRPGRESSING
- (void)writeData:(NSData *)data withTag:(long)tag {if (_type == Server) {if (_isListening == YES) {GCDAsyncSocket *socket = _connectedSocekts[0];[socket writeData:data withTimeout:-1 tag:tag];} else return;} else {[self.tcpSocket writeData:data withTimeout:-1 tag:tag];}
}#pragma mark - TCP DELEGATE
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {@synchronized (_connectedSocekts) {[_connectedSocekts addObject:newSocket];}NSString *host = [newSocket connectedHost];UInt16    port = [newSocket connectedPort];NSLog(@"Accepted client %@:%hu", host, port);self.isListening = YES;dispatch_source_cancel(_source);[newSocket readDataWithTimeout:-1 tag:_tag];
}- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {NSLog(@"Clinet 连接到 %@:%hu", host, port);[sock readDataWithTimeout:-1 tag:_tag];
}#warning Server & Client 1. Type 类型为 Server, 此处使用 [sock readDataWithTimeout:-1 tag:tag] 2. Type 类型为 Client, 此处使用 [sock writeData:data withTimeout:-1 tag:tag]
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {NSLog(@"ReadData: %@, tag: %ld", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], tag);if (_type == Server) {[sock readDataWithTimeout:-1 tag:tag];} else if (_type == Client) {[sock writeData:data withTimeout:-1 tag:tag];}
}- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {[sock readDataWithTimeout:-1 tag:tag];
}-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {if (sock != _tcpSocket) {@synchronized (_connectedSocekts) {[self.connectedSocekts removeObject:sock];}self.isListening = NO;[self startCycle];}
}#pragma mark - GET IP ADDRESS
- (NSString *)getIpAddress {NSString *address = @"error";struct ifaddrs *interfaces = NULL;struct ifaddrs *temp_addr = NULL;int success = 0;// retrieve the current interfaces - returns 0 on successsuccess = getifaddrs(&interfaces);if (success == 0) {// Loop through linked list of interfacestemp_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 iPhoneif([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {// Get NSString from C Stringaddress = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];}}temp_addr = temp_addr->ifa_next;}}// Free memoryfreeifaddrs(interfaces);return address;
}#pragma mark - DATA PRPGRESSING
- (NSData *)returnDataWithObject:(id)object {NSMutableData *resultData = [[NSMutableData alloc] init];NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:resultData];[archiver encodeObject:object forKey:kDATACONVERSION];[archiver finishEncoding];return resultData;
}- (id)returnDictionaryWithData:(NSData *)data {id result;NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];result = [unarchiver decodeObjectForKey:kDATACONVERSION];return result;
}#pragma mark - DEALLOC
- (void)dealloc
{self.tcpSocket.delegate = nil;self.udpSocket.delegate = nil;
}@end

SocketHelper的使用

初始化

以上就是SocketHelper的.h和.m, GCDAsyncSocket与GCDAsyncUdpSocket这两类大家自己可以去GitHub上下载, 可以使用pod管理下载类库, 也可以直接拖入GCD文件夹内的全部内容.
下面我们来看一下SocketHelper的初始化, 前面说到SocketHelper包含了Server与Client两种情况, 在.h中大家可以看到我用了@property (nonatomic, assign) Type type;来区分Server与Client.
Type实现

    typedef enum : NSUInteger {Server,Client,} Type;

所以我们分别从Server与Client两种情况来看使用方法

Server

首先在ViewController中导入SocketHelper的头文件#import "SocketHelper.h", 然后在延展中指点一个SocketHelper的成员变量, 如下所示:

@interface ViewController () {SocketHelper *_socketHelper;
}@end

-(void)loadView-(void)viewDidLoad中进行初始化, 大家使用loadView的时候千万别忘了调用[super loadView].

- (void)loadView {[super loadView];_socketHelper = [SocketHelper sharedHelper];_socketHelper.type = Server;// 指定设备类型[_socketHelper enableBroadcast:YES];// 是否允许广播[_socketHelper beginReceiving];// 开始进行接收[_socketHelper startCycle];// 循环发送广播数据[_socketHelper startListeningPort];// 开始对指定端口进行监听
}

注意[_SocketHelper startListingPort]我在类中指定了监听端口:

#define TCP_PORT        45000
- (instancetype)init
{self = [super init];if (self) {self.connectedSocekts = [[NSMutableArray alloc] initWithCapacity:1];self.tcpPort          = TCP_PORT;self.tag              = TAG;self.isListening      = NO;}return self;
}

所以我没指定端口, 如果想对端口进行更改可以对宏定义进行设置或者使用_socketHelper.tcpPort = (UInt16)的方式进行设置.
[_socketHelper startCycle]方法内部是一个定时器, 用来循环发送UDP数据包, 我采用的格式为NSDictionary类型, @{host:@(port)}, 通过数据来告知Client需要连接的IP地址与端口, 这个大家可以自己指定数据包内容. 当Client连接成功时关闭定时器, 直到Client断开时在将定时器开启.

Client

我们再来看一下Client的初始化.

- (IBAction)createTCPConnect:(id)sender {_socketHelper = [SocketHelper sharedHelper];_socketHelper.type = Client;// 指定设备类型[_socketHelper enableBroadcast:YES];// 允许开启广播服务[_socketHelper bindToPort:CLIENT_UDP_PORT];// 为设备绑定端口[_socketHelper beginReceiving];// 开始接受数据
}

这里我为了测试定时器状态, 所以用Storyboard去初始化一个Button, Client的SocketHelper的初始化我写在了Button的IB方法里. 点击按钮即可寻找Server进行连接.
以上Server与Client的初始化就完成了, 非常简单, 相互发送数据也是非常容易, 通过_socketHelper调用- (void)writeData:(NSData *)data withTag:(long)tag方法就可以了.
Server与Client能够相互发送数据后也可以进行文件传输, 这个就要大家根据自己的需求去实现了, 在此就不多说了.
技术有限, 就只能先写这么多了, 希望有精通Socket的大神加以指点, 也希望通过自己的学习能够帮助更多的人.

使用Socket进行设备间点对点连接传输数据相关推荐

  1. socket php(长连接传输数据)

    server_socket.php: <?php //创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP $socket = socket_create(AF_I ...

  2. 测试socket udp 单次传输数据上限(sendto()函数data不能超过65507字节。udp头占8字节,ip头占20字节,加起来正好65535字节)

    网传udp传输单次sendto()数据需小于64kb,即65536个字节? 于是我想实测一番 发送端 测试结果: 单次传输65507字节正常: 超过65507字节报错: 接收端 接收端只要大于等于发送 ...

  3. TCP/IP协议的一个具体实现Socket

    java 中Socket的用法 TCP/IP协议 两个不同的协议,放在一起说.IP协议是用来查找地址的,对应网际互连层:TCP协议是用来规范传输规则的,对应传输层. TCP在传输之前会进行三次沟通(三 ...

  4. java socket通信demo_Java Socket通信示例

    Socket分为ServerSocket和Socket两大类: 其中ServerSocket用于服务器端,可以通过accept方法监听请求,监听到请求后返回Socket: Socket用户具体完成数据 ...

  5. python socket udp并发_Python进阶----UDP协议使用socket通信,socketserver模块实现并发

    Python进阶----UDP协议使用socket通信,socketserver模块实现并发 一丶基于UDP协议的socket 实现UDP协议传输数据 代码如下:

  6. java中socket类_Java中的Socket的用法

    Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的网络通信时通过Socket实现的,Socket分为Server ...

  7. .net面试问题汇总(转)

    用.net做B/S结构的系统,您是用几层结构来开发,每一层之间的关系以及为什么要这样分层? 答: 从下至上分别为:数据访问层.业务逻辑层(又或成为领域层).表示层 数据访问层:有时候也称为是持久层,其 ...

  8. 2011面试题大汇总

    用.net做B/S结构的系统,您是用几层结构来开发,每一层之间的关系以及为什么要这样分层? 答: 从下至上分别为:数据访问层.业务逻辑层(又或成为领域层).表示层 数据访问层:有时候也 称为是持久层, ...

  9. .NET面试经典问答

    用.net做B/S结构的系统,您是用几层结构来开发,每一层之间的关系以及为什么要这样分层? 答: 从下至上分别为:数据访问层.业务逻辑层(又或成为领域层).表示层 数据访问层:有时候也称为是持久层,其 ...

最新文章

  1. Spring创建对象的三种方式以及创建时间
  2. SpringBoot取出信息
  3. Python OpenCV实例:图像直方图均衡化(数学公式简单实现)
  4. Jni Helloworld
  5. 2019 ICPC徐州站总结
  6. 浅谈接口对前后端测试的意义
  7. Django restframework 嵌套关系处理
  8. springboot之redis整合
  9. 网络爬虫研发重点介绍
  10. 图:广州南洋理工职业学院管理系“相聚南洋”朗诵(演讲)比赛尘埃落定
  11. 使用NeatUpload控件实现ASP.NET大文件上传
  12. (转载)你的个人信息是如何被盗走的?MySQL脱库,脱库的原理,怎么脱库,脱库的步骤,一库三表六字段
  13. python生成图文并茂的pdf--财务报表(三)--页面布局和排版
  14. 计算机网络第二章 韩立刚
  15. hazy的leetcode刷题笔记(五)
  16. 不是私密链接,如何继续前往?
  17. QT实现图片缩放的同时标记指定坐标
  18. Java实现office转PDF文件支持全部转换及Excel转换乱码和格式错乱解决
  19. R语言ggcharts包的dumbbell_chart函数可视化哑铃图(对比两个年份数据的差异)、scale_y_continuous函数中的labels参数自定义设置X轴坐标标签的格式
  20. 唐诗宋词学习·141~145节

热门文章

  1. 传感器与检测技术(一)
  2. python 读入文件列表_Python将文件读入列表
  3. php安装geoip,php扩展GeoIP安装
  4. 局域网访问mysql
  5. 【嵌入式实战】STM32+Lwip 实现 SNTP 网络授时(超详细)
  6. 科技向善,AI 助力老人听见天籁
  7. 链路聚合(动态捆绑链路)、负载均衡详解、全双工与半双工区别、LACP优先级详解(附图)
  8. 惠普HP LaserJet Pro P1606dn 打印机驱动
  9. Glide Picasso Fresco UIL 图片框架 MD
  10. 使用python制作zip口令破解程序