最近在做一套点对点传输的软件, 需要用到Socket进行设备间通讯. 去网上查了查, 对Socket分装比较好的就是目前特别火的GCDAsyncSocket这个类了, 这篇文章就GDCAsyncSocket与GCDAsyncUdpSocket进行单例封装, 一台设备通过UDP广播, 对外发送自己的IP地址与端口号, 另一台设备做接收, 接收后连接到IP地址与端口号, 从而进行TCP连接进行数据传输. 说明一下, SocketHelper单例类是Server与Client两用的, 使用时需指定设备类型. 下面先来看一下SocketHelper.
#import
#import
#import
#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 {
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
#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:0
dispatch_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 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;
}
#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的.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两种情况来看使用方法
首先在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的初始化.
- (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的大神加以指点, 也希望通过自己的学习能够帮助更多的人.