iOS - App间的通信方式
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打开应用的时候会执行的。不同之处:
-
第一种回调是在iOS2.0的时候推出的,参数只有url。
-
第二种回到是在iOS4.2的时候推出的,参数有url sourceApplication annotation.
-
第三种回调是iOS9.0的时候推出的,参数有url options。options有下面几个key
// Keys for application:openURL:options: UIKIT_EXTERN NSString *const UIApplicationOpenURLOptionsSourceApplicationKey NS_AVAILABLE_IOS(9_0); // value is an NSString containing the bundle ID of the originating application UIKIT_EXTERN NSString *const UIApplicationOpenURLOptionsAnnotationKey NS_AVAILABLE_IOS(9_0); // value is a property-list typed object corresponding to what the originating application passed in UIDocumentInteractionController's annotation property UIKIT_EXTERN NSString *const UIApplicationOpenURLOptionsOpenInPlaceKey NS_AVAILABLE_IOS(9_0); // value is a bool NSNumber, set to YES if the file needs to be copied before use
-
这几个回调是有优先级的。三>二>一。也就是说,如果你3个回调都实现了,那么程序只会执行第三种回调。其他回调是不会执行的。(当然,iOS9以下只会执行第二种回调)
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数据并不存放在App的Sanbox中,即使删除了App,资料依然保存在keychain中。如果重新安装了app,还可以从keychain获取数据。
-
keychain的数据可以用过group方式,让程序可以在App间共享。不过得要相同TeamID
-
keychain的数据是经过加密的
Keychain 的使用
Keychain提供了以下的操作
- SecItemAdd 添加一个item
/*!
@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);
- SecItemCopyMatching 搜索一个已存在的item
/*!
@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);
- SecItemUpdate 更新已存在的item
/*!
@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);
- SecItemDelete 删除一个keychain item
/*!
@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,那么就可以实现统一账户登录了。
-
先开启Keychain share,选中项目的Target -> Capabilities -> Keychain Groups。打开这个选项。
open_keychain -
同时在你的项目会生成一个entitlements文件。里面会有Access group,值应该是$(AppIdentifierPrefix)cn.xxx.KeyChainLearn 其中,AppIdentifierPrefix表示发布者的一个身份,这个可以在苹果开发者后台可以查詢到。
-
在Info.plist文件中新增一组key-value,方便在runtime時取得App Identifier Prefix
Key: AppIdentifierPrefix Value: $(AppIdentifierPrefix)
-
最后创建Keychain Item的时候,需要指定的相应的group
NSString *perfix = [[[NSBundle mainBundle]infoDictionary]objectForKey:@"AppIdentifierPrefix"];
NSString *groupString = [NSString stringWithFormat:@"%@cn.xiaozhi.KeyChainLearn",perfix];
[keyChainQueryDictaionary setObject:groupString forKey:(id)kSecAttrAccessGroup];
- 在其他需要共享数据的应用也是重复以上的操作,不过得保证AppIdentifierPrefix相同。
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 * )generalPasteboard 系统级别的剪切板在整个设备中共享,即是应用程序被删掉,其向系统级的剪切板中写入的数据依然在。
-
+ (nullable UIPasteboard * )pasteboardWithName:(NSString * )pasteboardName create:(BOOL)create 自定义的剪切板通过一个特定的名称字符串进行创建,它在应用程序内或者同一开发者开发的其他应用程序中可以进行数据共享。举个例子:比如你开发了多款应用,用户全部下载了,在A应用中用户拷贝了一些数据(为了数据安全,不用系统级别的Pasteboard),在打开B应用时就会自动识别,提高用户体验。
-
+ (UIPasteboard * )pasteboardWithUniqueName第3个方法创建的剪切板等价为使用第2个方法创建的剪切板,只是其名称字符串为nil,它通常用于当前应用内部。(当然也可以跨应用使用,但必须Bundle Identifier 例com.maoshaoqian.* ,其中* 星号前部一样)
-
注意:使用第3个方法创建的剪切板默认是不进行数据持久化的,及当应用程序退出后,剪切板中内容将别抹去。若要实现持久化,需要设置persistent属性为YES。
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在把数据传给硬件进行处理。
图片源于网络创建服务端
- 首先用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;
}
- 绑定本机地址和端口号
// 地址结构体数据,记录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;
}
- 监听绑定的地址
/*
* listen函数使用主动连接套接接口变为被连接接口,使得可以接受其他进程的请求,返回一个int值,-1为失败
* 第一个参数是之前socket函数返回的套接字
* 第二个参数可以理解为连接的最大限制
*/
int ls = listen(sock,20);
if(ls == -1){
close(sock);
NSLog(@"listen error : %d",ls);
return;
}
- 下面就是等待客户端的连接,使用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;
}
}
}
- 发送数据
- (void)sendMessage{
char sendData[32] = "hello client";
ssize_t size_t = send(self.newSocket, sendData, strlen(sendData), 0);
}
创建客户端
- 和服务端一样用socket函数创建套接字
int sock = socket(AF_INET, SOCK_STREAM,0);
if(sock == -1){
NSLog(@"socket error : %d",sock);
return;
}
- 获取主机的地址
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]);
- 使用connect()函数连接主机
/*
* connect函数通常用于客户端简历tcp连接,连接指定地址的主机,函数返回一个int值,-1为失败
* 第一个参数为socket函数创建的套接字,代表这个套接字要连接指定主机
* 第二个参数为套接字sock想要连接的主机地址和端口号
* 第三个参数为主机地址大小
*/
int con = connect(sock, (struct sockaddr *) &socketPram, sizeof(socketPram));
if(con == -1){
close(sock);
NSLog(@"连接失败");
return;
}
NSLog("连接成功"); // 来到这代表连接成功;
- 连接成功之后就可以收发数据了
- (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;
}
}
}