4、http TCP/IP UDP/IP 协议,以及socket
“我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”
socket本身并不是协议,而是一个调用接口(API),通过scoket,我们才能使用TCP/IP协议,实际上,scoket跟TCP/TP协议没有必然的联系。“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”
优快云上有个比较形象的描述:HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
用一句话来总结上面这些东西就是传输层的tcp事基于ip协议的,而应用层的http协议是基于传输层的tcp协议的,而scoket本身不算是协议,它只提供了一个针对tcp或者udp编程的接口。
下面是一些在网络上搜集的面试会遇到的问题
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
下面是一些经常在笔试或者面试中碰到的重要的概念,特在此做摘抄和总结。
一。什么是TCP连接的三次握手
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭 连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客 户端交互,最终确定断开)
二。利用Socket建立网络连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3。连接确认:当服 务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端 确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
三。HTTP链接的特点
HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
四。TCP和UDP的区别
1。TCP是面向链 接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的 可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重 发,所以说UDP是无连接的、不可靠的一种数据传输协议。
2。也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。
知道了TCP和 UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,因为程序员可以手动对UDP 的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。
5、多线程状态,线程资源抢夺?
6、线程池(详细请看http://blog.youkuaiyun.com/itjobtxq/article/details/8000581)
编写一个NSOperation的子类,只需实现main方法。这里非常类似Java的Thread,你可以继承它,并覆盖run方法,在该方法里面写入需要执行的代码。这里的main方法和run方法作用是相似的。
头文件:
@interface MyTask : NSOperation {
int operationId;
}
@property int operationId;
@end
复制代码
这里的operationId属性不是必须的,是我想在后面标识区分多个Task的标识位。
m文件:
@implementation MyTask
@synthesize operationId;
- (void)main{
NSLog(@"task %i run … ",operationId);
[NSThread sleepForTimeInterval:10];
NSLog(@"task %i is finished. ",operationId);
}
@end
复制代码
这里模拟了一个耗时10秒钟的操作。
下面需要把Task加入到队列中:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
复制代码
我直接找了个Controller的方法写上了。运行结果是,界面出现了,而task还未执行完,说明是多线程的。10秒钟后,日志打印完毕,类似这样:
2011-07-18 15:59:14.622 MultiThreadTest[24271:6103] task 1 run …
2011-07-18 15:59:24.623 MultiThreadTest[24271:6103] task 1 is finished.
可以向操作队列(NSOperationQueue)增加多个操作,比如这样:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
}
复制代码
那么打印出的内容是不定的,有可能是这样:
2011-07-18 15:49:48.087 MultiThreadTest[24139:6203] task 1 run …
2011-07-18 15:49:48.087 MultiThreadTest[24139:1903] task 2 run …
2011-07-18 15:49:58.122 MultiThreadTest[24139:6203] task 1 is finished.
2011-07-18 15:49:58.122 MultiThreadTest[24139:1903] task 2 is finished.
甚至有可能是这样:
2011-07-18 15:52:24.686 MultiThreadTest[24168:1b03] task 2 run …
2011-07-18 15:52:24.685 MultiThreadTest[24168:6003] task 1 run …
2011-07-18 15:52:34.708 MultiThreadTest[24168:1b03] task 2 is finished.
2011-07-18 15:52:34.708 MultiThreadTest[24168:6003] task 1 is finished.
因为两个操作提交的时间间隔很近,线程池中的线程,谁先启动是不定的。
那么,如果需要严格意义的顺序执行,怎么办呢?
处理操作之间的依赖关系
如果操作直接有依赖关系,比如第二个操作必须等第一个操作结束后再执行,需要这样写:
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
if ([[queue operations] count]>0) {
MyTask *theBeforeTask=[[queue operations] lastObject];
[task addDependency:theBeforeTask];
}
[queue addOperation:task];
复制代码
这样,即使是多线程情况下,可以看到操作是严格按照先后次序执行的。
控制线程池中的线程数
可以通过类似下面的代码:
[queue setMaxConcurrentOperationCount:2];
复制代码
来设置线程池中的线程数,也就是并发操作数。默认情况下是-1,也就是没有限制,同时运行队列中的全部操作。
7、block回调,持有block的对象销毁后会出现什么现象?
block回调:(http://blog.youkuaiyun.com/mobanchengshuang/article/details/11751671)<——具体参照这个博文;举个例子:手机充好电了通过回调的方式让我继续玩手机,也可以认为手机充好电了通知我可以继续玩手机,然后我主动继续玩手机。设置block,我们没有把充电函数内部的实现写死,也就是说当我完成之后无论做什么都无所谓,调的地方不同,传不同的代码过去就可以了,这个跟函数指针类似吧。
持有block的对象销毁后:可能会引发retain cycle问题,根源在于Block和对象可能会相互强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。解决这个问题的方法是使用弱引用打断retain cycle(___block)。这样对象被持有者释放后,retainCount变成0,request被dealloc,request释放持有的block,导致retainCount变成0,也就被销毁,这样这两个对象内存都被回收。
8、runtime和runloop
2、获取列表
下面我们来更加通俗的说一下runtime。其实我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[self createView];会被转化成objc_msgSend(target,@selector(doSomething));
有时候会有这样的需求,我们需要知道当前类中的每个属性的名字(比如字典转模型,字典的key和模型对象的属性名字不匹配)。
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议表)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for
(unsigned int i=0; i<count; i++) { const char *propertyname =
" property_getName(propertyList[i]);"
nslog(@
"property----="
">%@"
, [NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for
(unsigned int i; i<count; i++) { method method =
" methodList[i];"
nslog(@
"method----="
">%@"
, NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for
(unsigned int i; i<count; i++) { ivar myivar =
" ivarList[i];"
const char *ivarname =
" ivar_getName(myIvar);"
nslog(@
"ivar----="
">%@"
, [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for
(unsigned int i; i<count; i++) { protocol *myprotocal =
" protocolList[i];"
const char *protocolname =
" protocol_getName(myProtocal);"
nslog(@
"protocol----="
">%@"
, [NSString stringWithUTF8String:protocolName]);
}</count; i++) {></count; i++) {></count; i++) {></count; i++) {>
|
然后然后,我们来说一下这高大上的runtime怎么用吧
在开发项目的过程中,产品经理可能会有这样的要求:可以根据后台返回规则任意跳转相应的界面吗?
推送:根据服务端推送过来的数据规则,跳转到对应的控制器
这样的要求我们内心肯定是拒绝的,但是我们必须接受,如果你用switch 语句写,估计要写死,情况那么多,怎么办?
在这里可以用一下runtime
利用runtime动态生成对象、属性、方法这特性,我们可以跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性id、type,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了
比如根据推送规则跳转对应界面HSFeedsViewController
HSFeedsViewController:
进入该界面需要传得属性
1
2
3
4
5
6
7
|
@interface HSFeedsViewController : UIViewController
// 注:根据下面的两个属性,可以从服务器获取对应的频道列表数据
/** 频道ID */
@property (nonatomic, copy) NSString *ID;
/** 频道type */
@property (nonatomic, copy) NSString *type;
@end
|
AppDelegate.m:
-
推送过来的消息规则
1
2
3
4
5
6
7
8
|
// 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
NSDictionary *userInfo = @{
@
"class"
: @
"HSFeedsViewController"
,
@
"property"
: @{
@
"ID"
: @
"123"
,
@
"type"
: @
"12"
}
};
|
-
接收推送消息
1
2
3
4
|
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self push:userInfo];
}
|
-
跳转界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
- (void)push:(NSDictionary *)params
{
// 类名
NSString *class =[NSString stringWithFormat:@
"%@"
, params[@
"class"
]];
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
// 从一个字串返回一个类
Class newClass = objc_getClass(className);
if
(!newClass)
{
// 创建一个类
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注册你创建的这个类
objc_registerClassPair(newClass);
}
// 创建对象
id instance = [[newClass alloc] init];
// 对该对象赋值属性
NSDictionary * propertys = params[@
"property"
];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 检测这个对象是否存在该属性
if
([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用kvc赋值
[instance setValue:obj forKey:key];
}
}];
// 获取导航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
// 跳转到对应的控制器
[pushClassStance pushViewController:instance animated:YES];
}
|
-
检测对象是否存在该属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance
class], &outCount);
for
(i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断该属性是否存在
if
([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return
YES;
}
}
free(properties);
return
NO;
}
|
3、私有API与runtime
1、获取类
2、iOS9白名单的上限是50个,如果想绕过这个上限,扫描系统中所有app的状态,只有使用私有API,需要用到的类有两个:LSApplicationWorksspace、LSApplicationProxy,知道类的名字我们就可以依靠runtime得到这个类,以及这个类的所有方法。
-
得到LSApplicationWorkspace、LSApplicationProxy
1
2
|
Class LSApplicationWorkspace_class = objc_getClass(
"LSApplicationWorkspace"
);
Class LSApplicationProxy_class = object_getClass(@
"LSApplicationProxy"
);
|
得到类的所有方法与成员变量(编程小翁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//获取不到成员变量
int count = 0;
Ivar *members = class_copyIvarList([LSApplicationProxy_class class], &count);
for
(int i = 0 ; i < count; i++) {
Ivar
var
= members[i]; const char *memberName = ivar_getName(
var
); const char *memberType = ivar_getTypeEncoding(
var
);
NSLog(@
"%s: %s"
,memberType,memberName);
}
NSLog(@
"count: %d"
,count);
//获取不到有用的方法
count = 0;
Method *memberMethods = class_copyMethodList(LSApplicationProxy_class, &count);
for
(int i = 0; i < count; i++) {
SEL name = method_getName(memberMethods[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@
"member method:%@"
, methodName);
}
NSLog(@
"count: %d"
,count);
|
因为函数class_copyIvarList、class_copyMethodList有时不能返回有用的结果,所以我们使用class-dump(有朋友反映xcode7的库导不出来,大家用源码自己build一个吧),导出类的头文件。
导出MobileCoreServices.framework的所有头文件:
1
|
class-dump -H /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/MobileCoreServices.framework -o /Users/credit/Desktop/header004
|
-o后面是输出路径 改成你需要的。
MobileCoreServices.framework的所有头文件
LSApplicationProxy
LSApplicationWorkspace