网络编程

2018-03-20  本文已影响79人  Gintok

一 基本概念

1,HTTP 超文本传输协议

访问远程的网络资源,规定了客户端和服务端的数据传输格式。格式http://

http1.0和1.1的区别

http1.1和2.0的区别

发送HTTP请求的方法

http/1.1协议中定义了8中发送http请求的方法,分别是get,post,options,head,put,delete,tarce,connect,patch.一般只用get和post。

GET和POST的区别

HTTP通信过程

http协议规定,1个完整的http请求中包含以下内容:
请求头:包含了对客户端的环境描述,客户端请求信息等

GET: /1.png HTTP/1.1        //包含请求方法,请求资源路径,HTTP协议版本
Host:10.62.232.226:8081 //客户端要访问的服务器主机地址
User-Agent:Mozilla/5.0  //客户端的类型,客户端的软件环境
Accept:text/html, */*       //客户端所能接收的数据类型
Accept-Language:zh-cn       //客户端的语言环境
Accept-Encoding:gzip        //客户端支持的数据压缩格式

请求体:客户端发服务器的具体数据。只有在POST请求中才使用,GET没有
响应头:包含了对服务器的描述,对返回数据的描述

HTTP/1.1 200 OK         //包含=HTTP协议版本,状态码,状态英文名称
Server:Apache-Coyote/1.1    //服务器类型
Content-Type:image/jpeg //返回数据的类型
Content-Length:56811        //返回数据的长度
Date:Mon,23,Jun,2018    //响应的时间

响应体:服务器返回给客户端的具体数据,比如文件数据


常见响应状态码

* 200 成功
* 3XX 表示重定向相关
* 400 客户端请求的语法错误,服务器无法解析
* 404 服务器无法根据客户端的请求找到资源
* 500 服务器内部错误,无法完成请求

2,TCP

TCP特点

TCP连接与套接字

TCP头部

tcp头部至少由20个字节组成(最多60字节,因为选项字段最多40字节),如下所示


TCP头部

各字段含义

  1. 源端口和目的端口
    传输层和网络层一大重要区别就是传输层指定了数据报发往的应用进程,因此需要端口号标识。

  2. 序号
    当前TCP数据报数据部分的第一个字节的序号。
    我们知道,TCP是面向字节的,它会对发送的每一个字节进行编号,而且不同数据报之间是连续编号的。
    由于本字段4字节,可以给[0,232-1]个字节进行编号(大约4G),而且序号循环使用,当发送完232-1个字节后,序号又从0开始。
    一般来说,当2^32-1个字节被发送的时候,前面的字节早就发送成功了,因此序号可以循环使用。

  3. 确认号
    表示当前主机作为接收端时,期望接收的下一个字节的编号是多少。
    也表示,当前主机已经正确接收的最后一个字节序号+1。

  4. 数据偏移(报头长度)
    它表明了数据报头部的长度。

  5. 保留字段

  6. 标识符
    TCP有7种标识符,用于表示TCP报文的性质。它们只能为0或1。

URG=1 
当URG字段被置1,表示本数据报的数据部分包含紧急信息,此时紧急指针有效。 
紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。 
如control+c:这个命令要求操作系统立即停止当前进程。此时,这条命令就会存放在数据包数据部分的开头,并由紧急指针标识命令的位置,并URG字段被置1。

ACK=1 
ACK被置1后确认号字段才有效。 
此外,TCP规定,在连接建立后传送的所有报文段都必须把ACK置1。

PSH=1 
当接收方收到PSH=1的报文后,会立即将数据交付给应用程序,而不会等到缓冲区满后再提交。 
一些交互式应用需要这样的功能,降低命令的响应时间。

RST=1 
当该值为1时,表示当前TCP连接出现严重问题,必须要释放重连。

SYN=1 
SYN在建立连接时使用。 
当SYN=1,ACK=0时,表示当前报文段是一个连接请求报文。 
当SYN=1,ACK=1时,表示当前报文段是一个同意建立连接的应答报文。

FIN=1 
FIN=1表示此报文段是一个释放连接的请求报文。

接收窗口大小 
该字段用于实现TCP的流量控制。 
它表示当前接收方的接收窗口的剩余容量,发送方收到该值后会将发送窗口调整成该值的大小。发送窗口的大小又决定了发送速率,所以接收方通过设置该值就可以控制发送放的发送速率。 
发送方每收到一个数据报都要调整当前的发送窗口。

检验和 
用于接收端检验整个数据包在传输过程中是否出错。

紧急指针 
用于标识紧急数据的尾部。

选项字段 
上述字段都是每个TCP头部必须要有的,而选项字段是可选的,且长度可变,最长40字节。 
最常用的选项字段为MMS:最大报文长度。

TCP三次握手

TCP三次握手

起初,服务器和客户端都为CLOSED状态。在通信开始前,双方都得创建各自的传输控制块(TCB)。
服务器创建完TCB后遍进入LISTEN状态,此时准备接收客户端发来的连接请求。

第一次握手 
客户端向服务端发送连接请求报文段。该报文段的头部中SYN=1,ACK=0,seq=x。请求发送后,客户端便进入SYN-SENT状态。
PS1:SYN=1,ACK=0表示该报文段为连接请求报文。
PS2:x为本次TCP通信的字节流的初始序号。 
TCP规定:SYN=1的报文段不能有数据部分,但要消耗掉一个序号。

第二次握手 
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答:SYN=1,ACK=1,seq=y,ack=x+1。 
该应答发送完成后便进入SYN-RCVD状态。
PS1:SYN=1,ACK=1表示该报文段为连接同意的应答报文。
PS2:seq=y表示服务端作为发送者时,发送字节流的初始序号。
PS3:ack=x+1表示服务端希望下一个数据报发送序号从x+1开始的字节。

第三次握手 
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。 
该报文段的头部为:ACK=1,seq=x+1,ack=y+1。 
客户端发完这个报文段后便进入ESTABLISHED状态,服务端收到这个应答后也进入ESTABLISHED状态,此时连接的建立完成!

为什么一定要是三次握手?

防止失效的连接请求报文段被服务端接收,从而产生错误。
想象这样一种情况,客户端给服务端发送了连接请求报文段,但是因为网络原因,报文段被阻塞,客户端收不到服务端的回应便超时重传,之后连接建立,双方便开始通信,通信结束后释放连接。此时第一次被网络阻塞的请求又到了服务端,因为只有两次握手,服务端以为客户端又重新请求建立连接,便进入连接建立状态等待客户端发送数据,但此时客户端并没有新的数据要发送,是关闭状态的,服务端将会一直等待下去,这样浪费服务端连接资源。

TCP四次挥手

TCP四次挥手

TCP连接是双向的,因此在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。

第一次挥手 
若A认为数据发送完成,则它需要向B发送连接释放请求。该请求只有报文头,头中携带的主要参数为: 
FIN=1,seq=u。此时,A将进入FIN-WAIT-1状态。

PS1:FIN=1表示该报文段是一个连接释放请求。
PS2:seq=u,u-1是A向B发送的最后一个字节的序号。
第二次挥手 
B收到连接释放请求后,会通知相应的应用程序,告诉它A向B这个方向的连接已经释放。此时B进入CLOSE-WAIT状态,并向A发送连接释放的应答,其报文头包含: 
ACK=1,seq=v,ack=u+1。

PS1:ACK=1:除TCP连接请求报文段以外,TCP通信过程中所有数据报的ACK都为1,表示应答。
PS2:seq=v,v-1是B向A发送的最后一个字节的序号。
PS3:ack=u+1表示希望收到从第u+1个字节开始的报文段,并且已经成功接收了前u个字节。
A收到该应答,进入FIN-WAIT-2状态,等待B发送连接释放请求。

第二次挥手完成后,A到B方向的连接已经释放,B不会再接收数据,A也不会再发送数据。但B到A方向的连接仍然存在,B可以继续向A发送数据。

第三次挥手 
当B向A发完所有数据后,向A发送连接释放请求,请求头:FIN=1,ACK=1,seq=w,ack=u+1。B便进入LAST-ACK状态。

第四次挥手 
A收到释放请求后,向B发送确认应答,此时A进入TIME-WAIT状态。该状态会持续2MSL时间,若该时间段内没有B的重发请求的话,就进入CLOSED状态,撤销TCB。当B收到确认应答后,也便进入CLOSED状态,撤销TCB。

为什么A要先进入TIME-WAIT状态,等待2MSL时间后才进入CLOSED状态?

为了保证服务端能收到客户端的应答。
如果客户端第四次挥手后直接关闭,但是应答丢失了,那么服务端等待超时后会重发请求,但此时客户端已经关闭了,所以服务端无法正常关闭。

TCP可靠传输的实现

TCP采用了流量控制、拥塞控制、连续ARQ等技术来保证它的可靠性。(PS:网络层传输的数据单元为『数据报』,传输层的数据单元为『报文段』,但为了方便起见,可以统称为『分组』。)

停止等待协议(ARQ协议)

TCP保证其可靠性采用的是更为复杂的滑动窗口协议,但停止等待协议是它的简化版,为了方便理解,这里先介绍停止等待协议。

AQR协议

ARQ(Automatic Repeat reQuest)自动重传请求。
顾名思义,当请求失败时它会自动重传,直到请求被正确接收为止。这种机制保证了每个分组都能被正确接收。停止等待协议是一种ARQ协议。

停止等待协议的原理

停止等待协议的注意点

滑动窗口协议(连续ARQ协议)

连续ARQ协议
在ARQ协议发送者每次只能发送一个分组,在应答到来前必须等待。而连续ARQ协议的发送者拥有一个发送窗口,发送者可以在没有得到应答的情况下连续发送窗口中的分组。这样降低了等待时间,提高了传输效率。

累计确认
在连续ARQ协议中,接收者也有个接收窗口,接收者并不需要每收到一个分组就返回一个应答,可以连续收到分组之后统一返回一个应答。这样能节省流量。
TCP头部的ack字段就是用来累计确认,它表示已经确认的字节序号+1,也表示期望发送者发送的下一个分组的起始字节号。

发送窗口

title

发送窗口的大小由接收窗口的剩余大小决定。接收者会把当前接收窗口的剩余大小写入应答TCP报文段的头部,发送者收到应答后根据该值和当前网络拥塞情况设置发送窗口的大小。发送窗口的大小是不断变化的。
发送窗口由三个指针构成:

发送者每收到一个应答,后沿就可以向前移动指定的字节。此时若窗口大小仍然没变,前沿也可以向前移动指定字节。
当p2和前沿重合时,发送者必须等待确认应答。

接收窗口

title

接收者收到的字节会存入接收窗口,接收者会对已经正确接收的有序字节进行累计确认,发送完确认应答后,接收窗口就可以向前移动指定字节。
如果某些字节并未按序收到,接收者只会确认最后一个有序的字节,从而乱序的字节就会被重新发送。

连续ARQ的注意点

  1. 同一时刻发送窗口的大小并不一定和接收窗口一样大。
    虽然发送窗口的大小是根据接收窗口的大小来设定的,但应答在网络中传输是有时间的,有可能t1时间接收窗口大小为m,但当确认应答抵达发送者时,接收窗口的大小已经发生了变化。
    此外发送窗口的大小还随网络拥塞情况影响。当网络出现拥塞时,发送窗口将被调小。

  2. TCP标准并未规定未按序到达的字节的处理方式。但TCP一般都会缓存这些字节,等缺少的字节到达后再交给应用层处理。这比直接丢弃乱序的字节要节约带宽。

  3. TCP标准规定接收方必须要有累计确认功能。接收方可以对多个TCP报文段同时确认,但不能拖太长时间,一般是0.5S以内。
    此外,TCP允许接收者在有数据要发送的时候捎带上确认应答。但这种情况一般较少,因为一般很少有两个方向都要发送数据的情况。

流量控制和拥塞控制

  1. 拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况; 通过慢开始和拥塞避免算法来控制
  2. 流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收。 由滑动窗口协议实现。

3,UDP

UDP的特点

UDP只在IP数据报服务的基础上增加了少量的功能:复用与分用、对整个报文的差错检测。

UDP报文头

UDP报文头

二 iOS中网络请求

1,NSURLConnection

屏幕快照 2018-03-19 下午3.42.49.png

同步请求代码示例—GET

//1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://10.25.226.186:8081/login?username=a&pwd=123&type=XML"];
//2.创建一个请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.把请求发送给服务器
//sendSynchronousRequest  阻塞式的方法,会卡住线程
NSHTTPURLResponse *response = nil;
NSError *error = nil;
    /*
     第一个参数:请求对象
     第二个参数:响应头信息,当该方法执行完毕之后,该参数被赋值
     第三个参数:错误信息,如果请求失败,则error有值
     */
     //该方法是阻塞式的,会卡住线程
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//4.解析服务器返回的数据
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];

异步请求代码示例—GET

//1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it"];
//2.创建一个请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.把请求发送给服务器,发送一个异步请求
    /*
     第一个参数:请求对象
     第二个参数:回调方法在哪个线程中执行,如果是主队列则block在主线程中执行,非主队列则在子线程中执行
     第三个参数:completionHandlerBlock块:接受到响应的时候执行该block中的代码
        response:响应头信息
        data:响应体
        connectionError:错误信息,如果请求失败,那么该参数有值
     */
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * __nullable response, NSData * __nullable data, NSError * __nullable connectionError) {
        //4.解析服务器返回的数据
        NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        //转换并打印响应头信息
        NSHTTPURLResponse *r = (NSHTTPURLResponse *)response;
        NSLog(@"--%zd---%@--",r.statusCode,r.allHeaderFields);
    }];

通过代理实现异步请求—GET

1)步骤
1. 确定请求路径
2. 创建请求对象
3. 创建NSURLConnection对象并设置代理
4. 遵守NSURLConnectionDataDelegate协议,并实现相应的代理方法
5. 在代理方法中监听网络请求的响应
2)设置代理的几种方法

/*
     设置代理的第一种方式:自动发送网络请求
     [[NSURLConnection alloc]initWithRequest:request delegate:self];
     */

    /*
     设置代理的第二种方式:
     第一个参数:请求对象
     第二个参数:谁成为NSURLConnetion对象的代理
     第三个参数:是否马上发送网络请求,如果该值为YES则立刻发送,如果为NO则不会发送网路请求
     NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

     //调用该方法控制网络请求的发送
     [conn start];
     */

    //设置代理的第三种方式:使用类方法设置代理,会自动发送网络请求
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //取消网络请求
    //[conn cancel];

3)相关的代理方法

/*
 1.当接收到服务器响应的时候调用
 第一个参数connection:监听的是哪个NSURLConnection对象
 第二个参数response:接收到的服务器返回的响应头信息
 */
- (void)connection:(nonnull NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response

/*
 2.当接收到数据的时候调用,该方法会被调用多次
 第一个参数connection:监听的是哪个NSURLConnection对象
 第二个参数data:本次接收到的服务端返回的二进制数据(可能是片段)
 */
- (void)connection:(nonnull NSURLConnection *)connection didReceiveData:(nonnull NSData *)data
/*

 3.当服务端返回的数据接收完毕之后会调用
 通常在该方法中解析服务器返回的数据
 */
-(void)connectionDidFinishLoading:(nonnull NSURLConnection *)connection

/*4.当请求错误的时候调用(比如请求超时)
 第一个参数connection:NSURLConnection对象
 第二个参数:网络请求的错误信息,如果请求失败,则error有值
 */
- (void)connection:(nonnull NSURLConnection *)connection didFailWithError:(nonnull NSError *)error

异步请求—POST

1)发送POST请求步骤
1. 确定URL路径
2. 创建请求对象(可变对象)
3. 修改请求对象的方法为POST,设置请求体(Data)
4. 发送一个异步请求
5. 补充:设置请求超时,处理错误信息,设置请求头(如获取客户端的版本等等,请求头是可设置可不设置的)

//1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
//2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //2.1更改请求方法
    request.HTTPMethod = @"POST";
    //2.2设置请求体
    request.HTTPBody = [@"username=520it&pwd=520it" dataUsingEncoding:NSUTF8StringEncoding];
    //2.3请求超时
    request.timeoutInterval = 5;
    //2.4设置请求头
    [request setValue:@"ios 9.0" forHTTPHeaderField:@"User-Agent"];
//3.发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * __nullable response, NSData * __nullable data, NSError * __nullable connectionError) {
        //4.解析服务器返回的数据
        if (connectionError) {
            NSLog(@"--请求失败-");
        }else{
            NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
        }
    }];

2,NSURLSession

NSURLSessionTask是一个抽象类,本身不能使用,只能使用它的子类
NSURLSessionDataTask\NSURLSessionUploadTask\NSURLSessionDownloadTask
    //1.创建NSURLSession对象(可以获取单例对象)
    NSURLSession *session = [NSURLSession sharedSession];

    //2.根据NSURLSession对象创建一个Task

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=ss&pwd=ss&type=JSON"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //方法参数说明
    /*
    注意:该block是在子线程中调用的,如果拿到数据之后要做一些UI刷新操作,那么需要回到主线程刷新
    第一个参数:需要发送的请求对象
    block:当请求结束拿到服务器响应的数据时调用block
    block-NSData:该请求的响应体
    block-NSURLResponse:存放本次请求的响应信息,响应头,真实类型为NSHTTPURLResponse
    block-NSErroe:请求错误信息
     */
   NSURLSessionDataTask * dataTask =  [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {

        //拿到响应头信息
        NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;

        //4.解析拿到的响应数据
        NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);
    }];

    //3.执行Task
    //注意:刚创建出来的task默认是挂起状态的,需要调用该方法来启动任务(执行任务)
    [dataTask resume];
//注意:该方法内部默认会把URL对象包装成一个NSURLRequest对象(默认是GET请求) 
//方法参数说明 
/* 
//第一个参数:发送请求的URL地址 
//block:当请求结束拿到服务器响应的数据时调用block 
//block-NSData:该请求的响应体 
//block-NSURLResponse:存放本次请求的响应信息,响应头,真实类型为NSHTTPURLResponse 
//block-NSErroe:请求错误信息 
*/ 
- (nullable NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error))completionHandler; 
 //1.创建NSURLSession对象(可以获取单例对象) 
NSURLSession *session = [NSURLSession sharedSession];
//2.根据NSURLSession对象创建一个Task
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
//创建一个请求对象,并设置请求方法为POST,把参数放在请求体中传递
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {
    //拿到响应头信息
    NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
    //解析拿到的响应数据
    NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);
}];
//3.执行Task
//注意:刚创建出来的task默认是挂起状态的,需要调用该方法来启动任务(执行任务)
[dataTask resume];

3,第三方框架 AFN

将单独写一篇文章来解析AFN源码分析...

参考资料

计算机网络传输层知识点全覆盖

HTTP工作原理

上一篇下一篇

猜你喜欢

热点阅读