内容简介:在上一篇中,我们初步的讲述了socket的定义,以及socket中的TCP的简单用法。这篇我们主要讲的是HTTP相关的东西。HTTP->
在上一篇中,我们初步的讲述了socket的定义,以及socket中的TCP的简单用法。
这篇我们主要讲的是HTTP相关的东西。
什么是HTTP
HTTP-> Hyper Text Transfer Protocol(超文本传输协议) ,它是基于TCP/IP协议的一种 无状态连接
特性
无状态
无状态是指,在标准情况下,客户端的发出每一次请求,都是独立的,服务器并不能直接通过标准http协议本身获得用户对话的上下文。
这里,可能很多人会有疑问,我们平时使用的http不是这样的啊,服务器能识别我们请求的身份啊,要不免登录怎么做啊?
所以额外解释下,我们说的这些 状态 ,如 cookie/session 是由服务器与客户端双方约定好,每次请求的时候,客户端填写,服务器获取到后查询自身记录(数据库、内存),为客户端确定身份,并返回对应的值。
从另一方面也可说,这个特性和http协议本身无关,因为服务器不是从这个协议本身获取对应的 状态 。
无状态也可这样理解: 从同一客户端连续发出两次http请求到服务器,服务器无法从http协议本身上获取两次请求之间的关系
无连接
无连接指的是,服务器在响应客户端的请求后,就主动断开连接,不继续维持连接
结构
http 是超文本传输协议,顾名思义,传输的是一定格式的文本,所以,我们接下来讲述一下这个协议的格式
在http中,一个很重要的分割符就是 CRLF(Carriage-Return Line-Feed) 也就是 \r 回车符 + \n 换行符,它是用来作为识别的字符
请求 Request
上图为请求格式
请求行
GET / HTTP/1.1\r\n
首行也叫请求行,是用来告诉服务器,客户端调用的 请求类型 , 请求资源路径 , 请求协议类型
请求类型也就是我们常说的(面试官总问的) GET , POST 等等发送的位置,它位于请求的最开始
请求资源路径是提供给服务器内部的寻址路径,用来告诉服务器客户端希望访问什么资源,在浏览器中访问 www.jianshu.com/p/6cfbc63f3… (用简书做一波示范了),则我们请求的就是 /p/6cfbc63f3a2b
请求协议类型目前使用最多的是 HTTP/1.1 说不定在不远的未来,将会被 HTTP/2.0 所取代
注:
-
所使用链接为https链接,但是其内容与http一样,因此使用该链接做为例子,ssl 将会在接下来的几篇文章中讲述
-
请求行的不同内容需要用 " "空格符 来做分割
-
请求行的结尾需要添加 CRLF 分割符
请求头Request Headers
请求行之后,一直到请求体(body),之间的部分,被我们成为请求头。
请求头的长度并不固定,我们可以放置无限多的内容到请求头中。
但是请求头的格式是固定的,我们可以把它看做是键值对。
格式:
key: value\r\n 复制代码
我们通常所说的 cookie 便是请求头中的一项
一些常用的http头的定义与作用: blog.csdn.net/philos3/art…
注:
当所有请求头都已经结束(即我们要发送body)的时候,我们需要额外增加一个空行( CRLF ) 告诉服务器请求头已经结束
请求体Request Body
如果说header我们没有那么多的使用机会的话,那么body则是几乎每个开发人员都必须接触的了。
通常,当我们进行 POST 请求的时候,我们上传的参数就在这里了。
服务器是如何获得我们上传的完整Body呢?换句话说,就是服务器怎么知道我们的body已经传输完毕了呢?
我们想一下,如果我们在需要实现这个协议的时候,我们会怎么做?
-
可以约定特殊字节作为终止字符,当读取到指定字符时,即认为读取完毕
-
发送方肯定知道要发送的数据的大小,直接告诉接收方,接收方只需要在收到指定大小的数据的时候就可以停止接收了
-
发送方也不知道数据的大小(或者他需要花很大成本才能知道数据的大小),就先告诉接收方,我现在也不知道有多少,等发送的时候看,真正发送的时候告诉接收方,"我这次要发送多少",最后告诉接收方,"我发完了",接收方以此停止接收。‘
也许你会有别的想法,那恭喜你,你可以自己实现类似的接收方法了。
目前,服务器是依靠上述三种方法接收的:
- 约定特殊字节:
客户端在发送完数据后,就调用关闭socket连接,服务器在收到关闭请求后开始解析数据,并返回结果,最后关闭连接
- 确定数据大小:
客户端在 请求头 中给定字段 Content-Length
,服务器解析到对应数据后接受body,当body数据达到指定长度后,服务器开始解析数据,并返回结果
- 不确定数据大小(Http/1.1 可用)
客户端在 请求头 中给定头 Transfer-Encoding: chunked
,随后开始准备发送数据
发送的每段数据都有特定的格式,
格式为:
- 长度行:
每段数据的开头的文本为该段 真实发送的数据的16进制长度 加 CRLF 分割符
- 数据行:
真实发送的数据加 CRLF 分割符
例:
12\r\n // 长度行 16进制下的12就是10进制下的 18 It is a chunk data\r\n // 数据行 CRLF 为分割符 复制代码
结尾段:
用以告诉服务器数据发送完成,开始解析或存储数据。
结尾段格式固定
0\r\n \r\n 复制代码
目前,客户端使用这种方法的不多。
到这里,如何告诉服务器应该接收多少数据的部分已经完成了
接下来就到了,告诉服务器,数据究竟是什么了
同样也是头部定义: Content-Type
Content-Type介绍: blog.csdn.net/qq_23994787…
到这里,Request的基本格式已经讲完
响应 Response
相应结构
其实Response 和 Request 从协议上分析,他们是一样的,但是他们是对Http协议中文本协议的不同的实现。
响应行
HTTP/1.1 200 OK\r\n
首行也叫 响应行 ,是用来告诉客户端当前请求的处理状况的,由 请求协议类型 , 服务器状态码 , 对应状态描述 构成
请求协议类型是用来告诉客户端,服务器采用的协议是什么,以便于客户端接下来的处理。
服务器状态码是一个很重要的返回值,它是用来通知服务器对本次客户端请求的处理结果。
状态码非常多,但是对于我们开发一般用到的是如下几个状态码
状态码 | 对应状态描述 | 含义 | 客户对应操作 |
---|---|---|---|
200 | OK | 标志着请求被服务器成功处理 | 无 |
400 | Bad Request | 标志着客户端请求出现了问题,服务器无法识别,客户端修改后服务器才能进行处理 | 修改request参数 |
401 | Unauthorized | 当前请求需要校验权限,客户端需要在下次请求头部提交对应权限信息 | 修改Header头并提交对应信息 |
403 | Forbidden | 当前请求被服务器拒绝执行(防火墙阻止或其他原因) | 等待一段时间后再次发起,无其他解决办法 |
404 | Not Found | 服务无法找到对应资源(最为常见的错误码) | 修改Request中的资源请求路径 |
405 | Method Not Allowed | 客户端当前请求方法不被允许 | 修改请求方法 |
408 | Request Timeout | 客户端请求超时(服务器没有在允许的时间内解析出全部的Request) | 重新发起请求 |
500 | Internal Server Error | 服务器自身错误(可能是未对操作过程中的异常进行处理) | 联系后台开发人员解决(谁要是说这是客户端问题就去找他理论) |
完整错误码请参照网址: baike.baidu.com/item/HTTP状态…
响应头Response Headers及响应体Response Body
这些内容与 Request 中对应部分并无区别,顾不赘述了
我们已经从特性与结构两部分讲述了Http相关的属性,到这里这篇文章的主要内容基本上算是结束了,接下来我要讲讲一些其他的http相关的知识
跨域
作为移动端开发人员,我们对这个的了解不是很多,也几乎用不到,但是我这里还是需要说明。因为现在已经到了前端的时代,万一我们以后需要踏足前端,了解跨域,至少能为我们解决不少事情。
这篇文章不会详细讲解如何解决跨域,只会讲解跨域形成的原因
什么是 跨域
在讲跨域的时候,需要先讲什么是 域
什么是域
在上一课讲解socket的过程中,我们已经发现了,想建立一个TCP/IP的连接需要知道至少两个事情
- 对方的地址(host)
- 对方的门牌号(port)
我们只有依靠这两个才能建立TCP/IP 的连接,其中host标明我们该怎么找到对方,port表示,我们应该连接具体的那个端口。
服务器应用是一直在监听着这个端口的,这样才能保证在有连接进入的时候,服务器直接响应对应的信息
向上聊聊吧,我们通常讲的服务器指的是 服务器应用 ,比如常说Tomcat,Apache 等等,他们启动的时候一般会绑定好一个指定的端口(通常不会同时绑定两个端口)。所以呢,作为客户端,就可以用 host+port 来确定一个指定的 服务器应用
由此, 域 的概念就此生成,就是 host + port
举个例子: http://127.0.0.1:8056/
这个网址所属的域就是 127.0.0.1+8056 也可以写成 127.0.0.1:8056
这时候有人就会问了,那 localhost:8056 和 127.0.0.1:8056 是同一域么,他们实际是等价的啊。
他们不属于同一域,规定的很死,因为他们的host的表示不同,所以不是。
跨域
我们已经知道域了,跨域也就出现了,就是一个 域 访问另一个 域 。
我们从http协议中可以发现,服务器并不任何强制规定域,也就是说,服务器并不在乎这个访问是从哪个域访问过来的,同时,作为客户端,我们也并没有域这么一说。
那么 跨域 究竟是什么呢?
这就要说跨域的来源了,我们日常访问的网站,它实际上就是html代码,服务器将代码下发到了浏览器,由浏览器渲染并展示给我们。
开发浏览器的 程序员 在开发的时候,也不知道这个网页究竟要做什么,但是他们为了安全着想,不能给网页和客户端(socket)同样的权限,因此他们限制了某些操作,在本 域 的网页的某些请求操作在对方的服务器没有添加允许该 域 的访问权限的时候,访问操作将不会被执行,这些操作会对浏览器的安全性有很大到的影响。
所以跨域就此产生。
跨域从头到尾都只是一个客户端的操作行为,从某种角度上说,它与服务器毫无关系,因为服务器无法得知某次请求是否来自于某一网页(在客户端不配合的情况下),也就无从禁止了
对于我们移动端,了解跨域后我们至少可以说,跨域与我们无关-_-
socket实现简单的http请求
事实上,一篇文章如果没有代码上的支撑,只是纯理念上的阐述,终究还是感觉缺点什么,本文将在上篇文章代码的基础上做些小的改进。
这里就以菜鸟教程网的http教程作为本篇文章的测试( www.runoob.com/http/http-t… )(ip:47.246.3.228:80)
// MARK: - Create 建立 let socketFD = Darwin.socket(AF_INET, SOCK_STREAM, 0) func converIPToUInt32(a: Int, b: Int, c: Int, d: Int) -> in_addr { return Darwin.in_addr(s_addr: __uint32_t((a << 0) | (b << 8) | (c << 16) | (d << 24))) } // MARK: - Connect 连接 var sock4: sockaddr_in = sockaddr_in() sock4.sin_len = __uint8_t(MemoryLayout.size(ofValue: sock4)) // 将ip转换成UInt32 sock4.sin_addr = converIPToUInt32(a: 47, b: 246, c: 3, d: 228) // 因内存字节和网络通讯字节相反,顾我们需要交换大小端 我们连接的端口是80 sock4.sin_port = CFSwapInt16HostToBig(80) // 设置sin_family 为 AF_INET表示着这个为IPv4 连接 sock4.sin_family = sa_family_t(AF_INET) // Swift 中指针强转比OC要复杂 let pointer: UnsafePointer<sockaddr> = withUnsafePointer(to: &sock4, {$0.withMemoryRebound(to: sockaddr.self, capacity: 1, {$0})}) var result = Darwin.connect(socketFD, pointer, socklen_t(MemoryLayout.size(ofValue: sock4))) guard result != -1 else { fatalError("Error in connect() function code is \(errno)") } // 组装文本协议 访问 菜鸟教程Http教程 let sendMessage = "GET /http/http-tutorial.html HTTP/1.1\r\n" + "Host: www.runoob.com\r\n" + "Connection: keep-alive\r\n" + "USer-Agent: Socket-Client\r\n\r\n" //转换成二进制 guard let data = sendMessage.data(using: .utf8) else { fatalError("Error occur when transfer to data") } // 转换指针 let dataPointer = data.withUnsafeBytes({UnsafeRawPointer($0)}) let status = Darwin.write(socketFD, dataPointer, data.count) guard status != -1 else { fatalError("Error in write() function code is \(errno)") } // 设置32Kb字节存储防止溢出 let readData = Data(count: 64 * 1024) let readPointer = readData.withUnsafeBytes({UnsafeMutableRawPointer(mutating: $0)}) // 记录当前读取多少字节 var currentRead = 0 while true { // 读取socket数据 let result = Darwin.read(socketFD, readPointer + currentRead, readData.count - currentRead) guard result >= 0 else { fatalError("Error in read() function code is \(errno)") } // 这里睡眠是减少调用频率 sleep(2) if result == 0 { print("无新数据") continue } // 记录最新读取数据 currentRead += result // 打印 print(String(data: readData, encoding: .utf8) ?? "") } 复制代码
对应代码例子已经放在github上,地址: github.com/chouheiwa/S…
以上所述就是小编给大家介绍的《关于Socket,看我这几篇就够了(二)之HTTP》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C程序设计的抽象思维
Eric S.Roberts / 闪四清 / 机械工业出版社 / 2012-5 / 99.00元
Eric S. Roberts所著的《C程序设计的抽象思维》是一本关于C语言的经典图书。本书共计17章,分为4部分,第一部分概述计算机导论课程中涉及的基本编程概念;第二部分讨论递归算法,其中结合大量示例,有助于读者轻松理解和掌握晦涩的概念;第三部分不仅介绍了用非递归算法实现的抽象数据类型,还提供了一些工具,有助于读者理解数据抽象的概念;第四部分重点介绍采用递归算法实现的抽象数据类型。本书重点突出,......一起来看看 《C程序设计的抽象思维》 这本书的介绍吧!