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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liangshi1/article/details/51584754

前言

最近在做一套点对点传输的软件, 需要用到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: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的使用

初始化

以上就是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的大神加以指点, 也希望通过自己的学习能够帮助更多的人.

展开阅读全文

没有更多推荐了,返回首页