iOS - 开发技巧

iOS - App间的通信方式

2018-01-23  本文已影响21人  SkyMing一C
图片源于网络

iOS系统是相对封闭的系统,App各自在各自的沙盒(sandbox)中运行。每个App都只能读取iPhone上iOS系统为该应用程序程序创建的文件夹AppData下的内容,不能随意跨越自己的沙盒去访问别的App沙盒中的内容。

图片源于网络

iOS 的系统中进行App间通信的方式也比较固定,常见的app间通信方式有以下5种。

1. URL Scheme

这个是iOS app通信最常用到的通信方式,App1通过openURL的方法跳转到App2,并且在URL中带上想要的参数,有点类似http的get请求那样进行参数传递。

//1.打开Mail
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://info@icloud.com"]]
//2.打开电话
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://18688886666"]];

这种方式是使用最多的最常见的,使用方法也很简单只需要源App1在info.plist中配置LSApplicationQueriesSchemes,指定目标App2的scheme;然后在目标App2的info.plist中配置好URL types,表示该app接受何种URL scheme的唤起。

LSApplicationQueriesSchemes URL types

在appDelegate的代理中处理跳转

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url NS_DEPRECATED_IOS(2_0, 9_0, "Please use application:openURL:options:") __TVOS_PROHIBITED;
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation NS_DEPRECATED_IOS(4_2, 9_0, "Please use application:openURL:options:") __TVOS_PROHIBITED;
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options NS_AVAILABLE_IOS(9_0); // no equiv. notification. return NO if the application can't open for some reason

3个回调的功能基本一样,都是在别人通过URL Schemes打开应用的时候会执行的。不同之处:

URL Scheme 典型的使用场景就是各开放平台SDK的分享功能,如分享到微信朋友圈微博等,或者是支付场景。比如从滴滴打车结束行程跳转到微信进行支付。

2. Keychain

iOS系统的Keychain是一个安全的存储容器,可以用来为不同的app保存敏感信息,比如用户名,密码和证书等。存储这些信息可以免除用户重复输入用户名和密码的过程。Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。

iOS系统自己也用keychain来保存VPN凭证和Wi-Fi密码。它是独立于每个App的沙盒之外的,所以即使App被删除之后,Keychain里面的信息依然存在。

Keychain 的结构

Keychain 可以包含任意数量的 keychain item。每一个 keychain item 包含数据和一组属性。对于一个需要保护的 keychain item,比如密码或者私钥(用于加密或者解密的string字节)数据是加密的,会被 keychain 保护起来的;对于无需保护的 keychain item,例如,证书,数据未被加密。item可以指定为以下的类型:

extern CFTypeRef kSecClassGenericPassword
extern CFTypeRef kSecClassInternetPassword
extern CFTypeRef kSecClassCertificate
extern CFTypeRef kSecClassKey
extern CFTypeRef kSecClassIdentity OSX_AVAILABLE_STARTING(MAC_10_7, __IPHONE_2_0);

Keychain 的特点

Keychain 的使用

Keychain提供了以下的操作
/*!
    @function SecItemAdd
    @abstract Add one or more items to a keychain.
    @param attributes A dictionary containing an item class specification and
        optional entries specifying the item's attribute values. See the
        "Attribute Key Constants" section for a description of currently defined
        attributes.
    @param result On return, a CFTypeRef reference to the newly added item(s).
        The exact type of the result is based on the values supplied
        in attributes, as discussed below. Pass NULL if this result is not
        required.
    @result A result code. See "Security Error Codes" (SecBase.h).
    @discussion Attributes defining an item are specified by adding key/value
        pairs to the attributes dictionary.  To add multiple items to a keychain
        at once use the kSecUseItemList key with an array of items as its value.
        This is currently only supported for non password items.

        On OSX, To add an item to a particular keychain, supply kSecUseKeychain
        with a SecKeychainRef as its value.

    Result types are specified as follows:

      * To obtain the data of the added item (CFDataRef), specify
        kSecReturnData with a value of kCFBooleanTrue.
      * To obtain all the attributes of the added item (CFDictionaryRef),
        specify kSecReturnAttributes with a value of kCFBooleanTrue.
      * To obtain a reference to the added item (SecKeychainItemRef, SecKeyRef,
        SecCertiicateRef, or SecIdentityRef), specify kSecReturnRef with a
        value of kCFBooleanTrue.
      * To obtain a persistent reference to the added item (CFDataRef), specify
        kSecReturnPersistentRef with a value of kCFBooleanTrue. Note that
        unlike normal references, a persistent reference may be stored on disk
        or passed between processes.
      * If more than one of these result types is specified, the result is
        returned as a CFDictionaryRef containing all the requested data.
      * On iOS, if a result type is not specified, no results are returned.
        On OSX, the added item is returned.
*/
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
/*!
    @function SecItemCopyMatching
    @abstract Returns one or more items which match a search query.
    @param query A dictionary containing an item class specification and
        optional attributes for controlling the search. See the "Keychain
        Search Attributes" section for a description of currently defined
        search attributes.
    @param result On return, a CFTypeRef reference to the found item(s). The
        exact type of the result is based on the search attributes supplied
        in the query, as discussed below.
    @result A result code. See "Security Error Codes" (SecBase.h).
    @discussion Attributes defining a search are specified by adding key/value
        pairs to the query dictionary.

    A typical query consists of:

      * a kSecClass key, whose value is a constant from the Class
        Constants section that specifies the class of item(s) to be searched
      * one or more keys from the "Attribute Key Constants" section, whose value
        is the attribute data to be matched
      * one or more keys from the "Search Constants" section, whose value is
        used to further refine the search
      * a key from the "Return Type Key Constants" section, specifying the type of
        results desired

   Result types are specified as follows:

      * To obtain the data of a matching item (CFDataRef), specify
        kSecReturnData with a value of kCFBooleanTrue.
      * To obtain the attributes of a matching item (CFDictionaryRef), specify
        kSecReturnAttributes with a value of kCFBooleanTrue.
      * To obtain a reference to a matching item (SecKeychainItemRef,
        SecKeyRef, SecCertificateRef, or SecIdentityRef), specify kSecReturnRef
        with a value of kCFBooleanTrue.
      * To obtain a persistent reference to a matching item (CFDataRef),
        specify kSecReturnPersistentRef with a value of kCFBooleanTrue. Note
        that unlike normal references, a persistent reference may be stored
        on disk or passed between processes.
      * If more than one of these result types is specified, the result is
        returned as a CFDictionaryRef containing all the requested data.
      * If a result type is not specified, no results are returned.

    By default, this function returns only the first match found. To obtain
    more than one matching item at a time, specify kSecMatchLimit with a value
    greater than 1. The result will be a CFArrayRef containing up to that
    number of matching items; the items' types are described above.

    To filter a provided list of items down to those matching the query,
    specify a kSecMatchItemList whose value is a CFArray of SecKeychainItemRef,
    SecKeyRef, SecCertificateRef, or SecIdentityRef items. The objects in the
    provided array must be of the same type.

    On iOS, to convert from a persistent item reference to a normal item reference,
    specify a kSecValuePersistentRef whose value a CFDataRef (the persistent
    reference), and a kSecReturnRef whose value is kCFBooleanTrue.

    On OSX, to convert from persistent item references to normal item references,
    specify a kSecMatchItemList whose value is a CFArray containing one or
    more CFDataRef elements (the persistent reference), and a kSecReturnRef
    whose value is kCFBooleanTrue. The objects in the provided array must be
    of the same type.
*/
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
/*!
    @function SecItemUpdate
    @abstract Modify zero or more items which match a search query.
    @param query A dictionary containing an item class specification and
        optional attributes for controlling the search. See the "Attribute
        Constants" and "Search Constants" sections for a description of
        currently defined search attributes.
    @param attributesToUpdate A dictionary containing one or more attributes
        whose values should be set to the ones specified. Only real keychain
        attributes are permitted in this dictionary (no "meta" attributes are
        allowed.) See the "Attribute Key Constants" section for a description of
        currently defined value attributes.
    @result A result code. See "Security Error Codes" (SecBase.h).
    @discussion Attributes defining a search are specified by adding key/value
        pairs to the query dictionary.
*/
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
/*!
    @function SecItemDelete
    @abstract Delete zero or more items which match a search query.
    @param query A dictionary containing an item class specification and
        optional attributes for controlling the search. See the "Attribute
        Constants" and "Search Constants" sections for a description of
        currently defined search attributes.
    @result A result code.  See "Security Error Codes" (SecBase.h).
    @discussion Attributes defining a search are specified by adding key/value
        pairs to the query dictionary.

    By default, this function deletes all items matching the specified query.
    You can change this behavior by specifying one of the follow keys:

      * To delete an item identified by a transient reference, on iOS, specify
        kSecValueRef with a item reference. On OS X, give a kSecMatchItemList
        containing an item reference.
      * To delete an item identified by a persistent reference, on iOS, specify
        kSecValuePersistentRef with a persistent reference returned by
        using the kSecReturnPersistentRef key to SecItemCopyMatching or
        SecItemAdd. on OSX, use kSecMatchItemList with a persistent reference
        returned by using the kSecReturnPersistentRef key with
        SecItemCopyMatching or SecItemAdd.
      * To delete multiple items specify kSecMatchItemList with an array
        of references.
      * If more than one of these result keys is specified, the behavior is
        undefined.
*/
OSStatus SecItemDelete(CFDictionaryRef query)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
根据特定的Service创建一个用于操作KeyChain的Dictionary
+ (NSMutableDictionary *)keyChainQueryDictionaryWithService:(NSString *)service{
    NSMutableDictionary *keyChainQueryDictaionary = [[NSMutableDictionary alloc]init];
    [keyChainQueryDictaionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    [keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrService];
    [keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrAccount];
    return keyChainQueryDictaionary;
}
添加数据
+ (BOOL)addData:(id)data forService:(NSString *)service{
    NSMutableDictionary *keychainQuery = [self keyChainQueryDictionaryWithService:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    OSStatus status= SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
    if (status == noErr) {
        return YES;
    }
    return NO;
}
搜索数据
+ (id)queryDataWithService:(NSString *)service {
    id result;
    NSMutableDictionary *keyChainQuery = [self keyChainQueryDictionaryWithService:service];
    [keyChainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keyChainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keyChainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            result = [NSKeyedUnarchiver  unarchiveObjectWithData:(__bridge NSData *)keyData];
        }
        @catch (NSException *exception) {
            NSLog(@"不存在数据");
        }
        @finally {
            
        }
    }
    if (keyData) {
        CFRelease(keyData);
    }
    return result;
}
更新数据
+ (BOOL)updateData:(id)data forService:(NSString *)service{
    NSMutableDictionary *searchDictionary = [self keyChainQueryDictionaryWithService:service];
    
    if (!searchDictionary) {
        return NO;
    }
    
    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
    [updateDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
                                    (CFDictionaryRef)updateDictionary);
    if (status == errSecSuccess) {
        return YES;
    }
    return NO;
}
删除数据
+ (BOOL)updateData:(id)data forService:(NSString *)service{
    NSMutableDictionary *searchDictionary = [self keyChainQueryDictionaryWithService:service];
    
    if (!searchDictionary) {
        return NO;
    }
    
    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
    [updateDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
                                    (CFDictionaryRef)updateDictionary);
    if (status == errSecSuccess) {
        return YES;
    }
    return NO;
}

Keychain 共享数据

Keychain用于App间通信的一个典型场景也和app的登录相关,就是统一账户登录平台。使用同一个账号平台的多个app,只要其中一个app用户进行了登录,其他app就可以实现自动登录不需要用户多次输入账号和密码。一般开放平台都会提供登录SDK,在这个SDK内部就可以把登录相关的信息都写到keychain中,这样如果多个app都集成了这个SDK,那么就可以实现统一账户登录了。

NSString *perfix = [[[NSBundle mainBundle]infoDictionary]objectForKey:@"AppIdentifierPrefix"];
NSString *groupString = [NSString stringWithFormat:@"%@cn.xiaozhi.KeyChainLearn",perfix];
[keyChainQueryDictaionary setObject:groupString forKey:(id)kSecAttrAccessGroup];

3. UIPasteboard

顾名思义, UIPasteboard是剪切板功能,因为iOS的原生控件UITextView,UITextField 、UIWebView,我们在使用时如果长按,就会出现复制、剪切、选中、全选、粘贴等功能,这个就是利用了系统剪切板功能来实现的。而每一个App都可以去访问系统剪切板,所以就能够通过系统剪贴板进行App间的数据传输了。

UIPasteboard初始化方法
//获取系统级别的剪切板
+ (UIPasteboard *)generalPasteboard;
//获取一个自定义的剪切板 name参数为此剪切板的名称 create参数用于设置当这个剪切板不存在时 是否进行创建
+ (nullable UIPasteboard *)pasteboardWithName:(NSString *)pasteboardName create:(BOOL)create;
//获取一个应用内可用的剪切板
+ (UIPasteboard *)pasteboardWithUniqueName;
UIPasteboard的常用属性
//剪切板的名称
@property(readonly,nonatomic) NSString *name;
//根据名称删除一个剪切板
+ (void)removePasteboardWithName:(NSString *)pasteboardName;
//是否进行持久化
@property(getter=isPersistent,nonatomic) BOOL persistent;
//此剪切板的改变次数 系统级别的剪切板只有当设备重新启动时 这个值才会清零
@property(readonly,nonatomic) NSInteger changeCount;
UIPasteboard的使用(复制图片的简单例子)
//.h
#import <UIKit/UIKit.h>
@interface CopyView : UIImageView
@end
//.m
#import "CopyView.h"
@interface CopyView ()
@property (strong, nonatomic) UIImageView* img1;
@property (strong, nonatomic) UIImageView* img2;
@end

@implementation CopyView
-(UIImageView *)img1{
    if (_img1 == nil) {
        _img1 = [[UIImageView alloc] initWithFrame:CGRectMake(10.0f, 20.0f, 100.0f, 100.0f)];
        NSString* path = [[NSBundle mainBundle] pathForResource:@"NetworldImage" ofType:@"jpg"];
        _img1.image = [UIImage imageWithContentsOfFile:path];
    }
    return _img1;
}

-(UIImageView *)img2{
    if (_img2 == nil) {
          _img2 = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetMaxX(self.img1.frame)+50.0f, 20.0f, 100.0f, 100.0f)];
        _img2.backgroundColor = [UIColor lightGrayColor];
    }
    return _img2;
}

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        [self addSubview:self.img1];
        [self addSubview:self.img2];
    }
    return self;
}

-(BOOL)canBecomeFirstResponder{
    return YES;
}
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender{
    NSArray* methodNameArr = @[@"copy:",@"cut:",@"select:",@"selectAll:",@"paste:"];
    if ([methodNameArr containsObject:NSStringFromSelector(action)]) {
        return YES;
    }
    return [super canPerformAction:action withSender:sender];
}

-(void)copy:(id)sender{
    UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
    [pasteboard setImage:self.img1.image];
}

-(void)paste:(id)sender{
    UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
    self.img2.image = [pasteboard image];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self becomeFirstResponder];
    UIMenuController* menuController = [UIMenuController sharedMenuController];
    [menuController setTargetRect:self.img1.frame inView:self];
    [menuController setMenuVisible:YES animated:YES];
}

@end
UIPasteboard在App间的使用

UIPasteboard典型的使用场景就是淘宝跟微信/QQ的链接分享。由于腾讯和阿里的公司战略,腾讯在微信和qq中都屏蔽了淘宝的链接。那如果淘宝用户想通过QQ或者微信跟好友分享某个淘宝商品,怎么办呢? 阿里的工程师就巧妙的利用剪贴板实现了这个功能。首先淘宝app中将链接自定义成淘口令,引导用户进行复制,并去QQ好友对话中粘贴。然后QQ好友收到消息后再打开自己的淘宝app,淘宝app每次从后台切到前台时,就会检查系统剪切板中是否有淘口令,如果有淘口令就进行解析并跳转到对于的商品页面。

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];//创建剪贴板
[pasteboard setString:@"口令"];//写入口令
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"口令" message:pasteboard.string delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];//弹出
[alert show];

4. UIDocumentInteractionController

UIDocumentInteractionController主要是用来实现同设备上app之间的共享文档,以及文档预览、打印、发邮件和复制等功能。它的使用非常简单.

首先通过调用它唯一的类方法 interactionControllerWithURL:,并传入一个URL(NSURL),为你想要共享的文件来初始化一个实例对象。然后UIDocumentInteractionControllerDelegate,然后显示菜单和预览窗口。

NSURL *url = [[NSBundle mainBundle] URLForResource:@"sample" withExtension:@"pdf"];
    if (url) {
        //初始化
        UIDocumentInteractionController *documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:url];
        //代理
        documentInteractionController.delegate = self;
        //调用
        [documentInteractionController presentPreviewAnimated:YES];
    }
效果图

5. local socket

这种方式不太常见,也是很容易被iOS开发者所忽略但是特别实用的一种方法。它的原理很简单,一个App1在本地的端口port1234进行TCP的bind和listen,另外一个App2在同一个端口port1234发起TCP的connect连接,这样就可以建立正常的TCP连接,进行TCP通信了,那么就想传什么数据就可以传什么数据了。

这种方式最大的特点就是灵活,只要连接保持着,随时都可以传任何相传的数据,而且带宽足够大。它的缺点就是因为iOS系统在任意时刻只有一个app在前台运行,那么就要通信的另外一方具备在后台运行的权限,像导航或者音乐类app。

它是常用使用场景就是某个App1具有特殊的能力,比如能够跟硬件进行通信,在硬件上处理相关数据。而App2则没有这个能力,但是它能给App1提供相关的数据,这样APP2跟App1建立本地socket连接,传输数据到App1,然后App1在把数据传给硬件进行处理。

图片源于网络
创建服务端
  1. 首先用socket()函数创建一个套接字
/*
 * socket返回一个int值,-1为创建失败
 * 第一个参数指明了协议族/域 ,通常有AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL
 * 第二个参数指定一个套接口类型:SOCK_STREAM,SOCK_DGRAM、SOCK_SEQPACKET等
 * 第三个参数指定相应的传输协议,诸如TCP/UDP等,一般设置为0来使用这个默认的值
 */
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1){
 close(sock);
 NSLog(@"socket error : %d",sock);<br> return;
}
  1. 绑定本机地址和端口号
// 地址结构体数据,记录ip和端口号
struct sockaddr_in sockAddr;
// 声明使用的协议
sockAddr.sin_family = AF_INET;
// 获取本机的ip,转换成char类型的
const char *ip = [[self getIPAddress] cStringUsingEncoding:NSASCIIStringEncoding];
// 将ip赋值给结构体,inet_addr()函数是将一个点分十进制的IP转换成一个长整数型数
sockAddr.sin_addr.s_addr = inet_addr(ip);
// 设置端口号,htons()是将整型变量从主机字节顺序转变成网络字节顺序
sockAddr.sin_port = htons(12345);
/*
 * bind函数用于将套接字关联一个地址,返回一个int值,-1为失败
 * 第一个参数指定套接字,就是前面socket函数调用返回额套接字
 * 第二个参数为指定的地址
 * 第三个参数为地址数据的大小
 */
int bd = bind(sock,(struct sockaddr *) &sockAddr, sizeof(sockAddr));
if(bd == -1){
 close(sock);
 NSLog(@"bind error : %d",bd);
 return;
}
  1. 监听绑定的地址
/*
 * listen函数使用主动连接套接接口变为被连接接口,使得可以接受其他进程的请求,返回一个int值,-1为失败
 * 第一个参数是之前socket函数返回的套接字
 * 第二个参数可以理解为连接的最大限制
 */
int ls = listen(sock,20);
if(ls == -1){
 close(sock);
 NSLog(@"listen error : %d",ls);
 return;
}
  1. 下面就是等待客户端的连接,使用accept()(由于accept函数会阻塞线程,在等待连接的过程中会一直卡着,所以建议将其放在子线程里面)
// 1,开启一个子线程
NSTread *recvThread = [[NSThread alloc] initwithTarget:self selector:@selector(recvData) object: nil];
[recvThread start];
 
- (void)recvData{
 
// 2,等待客户端连接
// 声明一个地址结构体,用于后面接收客户端返回的地址 
  struct sockaddr_in recvAddr;
// 地址大小
  socklen_t recv_size = sizeof(struct sockaddr_in);
/*
 * accept()函数在连接成功后会返回一个新的套接字(self.newSock),用于之后和这个客户端之前收发数据
 * 第一个参数为之前监听的套接字,之前是局部变量,现在需要改为全局的
 * 第二个参数是一个结果参数,它用来接收一个返回值,这个返回值指定客户端的地址
 * 第三个参数也是一个结果参数,它用来接收recvAddr结构体的代销,指明其所占的字节数
 */
self.newSock = accept(self.sock,(struct sockaddr *) &recvAddr, &recv_size);
// 3,来到这里就代表已经连接到一个新的客户端,下面就可以进行收发数据了,主要用到了send()和recv()函数
  ssize_t bytesRecv = -1; // 返回数据字节大小
  char recvData[128] = ""; // 返回数据缓存区
// 如果一端断开连接,recv就会马上返回,bytesrecv等于0,然后while循环就会一直执行,所以判断等于0是跳出去
  while(1){
  bytesRecv = recv(self.newSocket,recvData,128,0); // recvData为收到的数据
  if(bytesRecv == 0){
    break;    
  }
  }
}
  1. 发送数据
- (void)sendMessage{
     
    char sendData[32] = "hello client";
    ssize_t size_t = send(self.newSocket, sendData, strlen(sendData), 0);
  
}
创建客户端
  1. 和服务端一样用socket函数创建套接字
int sock = socket(AF_INET, SOCK_STREAM,0);
if(sock == -1){
 
  NSLog(@"socket error : %d",sock);
  return;
}
  1. 获取主机的地址
NSString *host = [self getIPAddress]; // 获取本机ip地址
// 返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
struct hostent *remoteHostEnt = gethostbyname([host UTF8String]);
if(remoteHostEnt == NULL){
 
  close(sock);
  NSLog(@"无法解析服务器主机名");
  return;
}<br>// 配置套接字将要连接主机的ip地址和端口号,用于connect()函数
struct in_addr *remoteInAddr = (struct in_addr *)remoteHost->h_addr_list[0];
struct sockaddr_in socktPram;
socketPram.sin_family = AF_INT;
socketPram.sin_addr = *remoteInAddr;
socketPram.sin_port = htons([port intValue]);
  1. 使用connect()函数连接主机
/*
 * connect函数通常用于客户端简历tcp连接,连接指定地址的主机,函数返回一个int值,-1为失败
 * 第一个参数为socket函数创建的套接字,代表这个套接字要连接指定主机
 * 第二个参数为套接字sock想要连接的主机地址和端口号
 * 第三个参数为主机地址大小
 */
int con = connect(sock, (struct sockaddr *) &socketPram, sizeof(socketPram));
if(con == -1){
  close(sock);
  NSLog(@"连接失败");
  return;
}
NSLog("连接成功"); // 来到这代表连接成功;
  1. 连接成功之后就可以收发数据了
- (IBAction)senddata:(id)sender {
    // 发送数据
    char sendData[32] = "hello service";
    ssize_t size_t = send(self.sock, sendData, strlen(sendData), 0);
    NSLog(@"%zd",size_t);
}
 
- (void)recvData{
    // 接受数据,放在子线程
    ssize_t bytesRecv = -1;
    char recvData[32] = "";
    while (1) {
     
        bytesRecv = recv(self.sock, recvData, 32, 0);
        NSLog(@"%zd %s",bytesRecv,recvData);
        if (bytesRecv == 0) {
            break;
        }
    }
}

参考

打开另一个APP

iOS Keychain理解

iOS App之间的通信 -local socket

上一篇下一篇

猜你喜欢

热点阅读