关于Socket,看我这几篇就够了(二)之HTTP

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:在上一篇中,我们初步的讲述了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

关于Socket,看我这几篇就够了(二)之HTTP

上图为请求格式

请求行

GET / HTTP/1.1\r\n

首行也叫请求行,是用来告诉服务器,客户端调用的 请求类型请求资源路径请求协议类型

请求类型也就是我们常说的(面试官总问的) GETPOST 等等发送的位置,它位于请求的最开始

请求资源路径是提供给服务器内部的寻址路径,用来告诉服务器客户端希望访问什么资源,在浏览器中访问 www.jianshu.com/p/6cfbc63f3… (用简书做一波示范了),则我们请求的就是 /p/6cfbc63f3a2b

请求协议类型目前使用最多的是 HTTP/1.1 说不定在不远的未来,将会被 HTTP/2.0 所取代

注:

  1. 所使用链接为https链接,但是其内容与http一样,因此使用该链接做为例子,ssl 将会在接下来的几篇文章中讲述

  2. 请求行的不同内容需要用 " "空格符 来做分割

  3. 请求行的结尾需要添加 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 ,随后开始准备发送数据

发送的每段数据都有特定的格式,

格式为:

  1. 长度行:

每段数据的开头的文本为该段 真实发送的数据的16进制长度CRLF 分割符

  1. 数据行:

真实发送的数据加 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

关于Socket,看我这几篇就够了(二)之HTTP

相应结构

其实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的连接需要知道至少两个事情

  1. 对方的地址(host)
  2. 对方的门牌号(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:8056127.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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

素数之恋

素数之恋

(美)约翰·德比希尔 / 陈为蓬 / 上海科技教育出版社 / 2008-12-01 / 34.00元

1859年8月,没什么名气的32岁数学家黎曼向柏林科学院提交了一篇论文,题为“论小于一个给定值的素数的个数”。在这篇论文的中间部分,黎曼作了一个附带的备注——一个猜测,一个假设。他向那天被召集来审查论文的数学家们抛出的这个问题,结果在随后的年代里给无数的学者产生了近乎残酷的压力。时至今日,在经历了150年的认真研究和极力探索后,这个问题仍然悬而未决。这个假设成立还是不成立? 已经越来越清楚,......一起来看看 《素数之恋》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试